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

feat(testing): add spread testing template files #1514

Closed
Closed
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
3 changes: 3 additions & 0 deletions charmcraft/application/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
SetResourceArchitecturesCommand,
UploadResourceCommand,
)
from charmcraft.application.commands.test import TestCommand
from charmcraft.application.commands.version import Version


Expand Down Expand Up @@ -117,6 +118,7 @@ def fill_command_groups(app: craft_application.Application) -> None:
[
Analyse,
Analyze,
TestCommand,
Version,
],
)
Expand Down Expand Up @@ -156,5 +158,6 @@ def fill_command_groups(app: craft_application.Application) -> None:
"ListResourcesCommand",
"ListResourceRevisionsCommand",
"SetResourceArchitecturesCommand",
"TestCommand",
"UploadResourceCommand",
]
8 changes: 8 additions & 0 deletions charmcraft/application/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,17 @@
│ linting tools
├── README.md - Frontpage for your charmhub.io/charm/
├── requirements.txt - PyPI dependencies for your charm, with `ops`
├── spread.yaml - Spread testing configuration file
├── src
│ └── charm.py - Minimal operator using Python operator framework
├── tests
│ ├── spread
│ │ ├── lib
│ │ │ ├── cloud-config.yaml
│ │ │ └── test-helpers.yaml
│ │ └── general
│ │ └── integration
│ │ └── task.yaml
│ ├── integration
│ │ └── test_charm.py - Integration tests
│ └── unit
Expand Down
62 changes: 62 additions & 0 deletions charmcraft/application/commands/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright 2024 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# For further info, check https://github.com/canonical/charmcraft

"""Infrastructure for the 'test' command."""
import argparse
import os
import subprocess
import sys

from craft_cli import CraftError, emit

from charmcraft.application.commands import base

_overview = """
Run charm tests in different back-ends.

This command will run charm test suites using the spread tool. See
the spread documentation for further information.
"""


class TestCommand(base.CharmcraftCommand):
"""Initialize a directory to be a charm project."""

name = "test"
help_msg = "Execute charm test suites"
overview = _overview
common = True

def fill_parser(self, parser):
"""Specify command's specific parameters."""
parser.add_argument(
"spread_args",
metavar="spread arguments",
nargs=argparse.REMAINDER,
help="Arguments to spread",
)

def run(self, parsed_args: argparse.Namespace):
"""Execute command's actual functionality."""
spread_args = parsed_args.spread_args
if len(spread_args) > 0 and spread_args[0] == "--":
spread_args = spread_args[1:]
try:
cmd = f"{os.environ['SNAP']}/bin/spread"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
cmd = f"{os.environ['SNAP']}/bin/spread"
# TODO: Make this work without Charmcraft being snapped
cmd = f"{os.environ['SNAP']}/bin/spread"

Thinking of our users on macos :-)

with emit.pause():
subprocess.run([cmd, *spread_args], check=True)
except subprocess.CalledProcessError as err:
raise CraftError(f"test error: {err}")
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ from pytest_operator.plugin import OpsTest

logger = logging.getLogger(__name__)

METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
METADATA = yaml.safe_load(Path("./charmcraft.yaml").read_text())
APP_NAME = METADATA["name"]


Expand Down
139 changes: 139 additions & 0 deletions charmcraft/templates/init-machine/spread.yaml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
project: {{ name }}-tests

environment:
PROVIDER: lxd
CHARMCRAFT_CHANNEL: latest/stable
JUJU_CHANNEL: 3/stable
LXD_CHANNEL: latest/stable

JUJU_BOOTSTRAP_OPTIONS: --model-default test-mode=true --model-default automatically-retry-hooks=false --model-default
JUJU_EXTRA_BOOTSTRAP_OPTIONS: ""
JUJU_BOOTSTRAP_CONSTRAINTS: ""

# important to ensure adhoc and linode/qemu behave the same
SUDO_USER: ""
SUDO_UID: ""

LANG: "C.UTF-8"
LANGUAGE: "en"

PROJECT_PATH: /home/spread/proj
CRAFT_TEST_LIB_PATH: /home/spread/proj/tests/spread/lib

backends:
multipass:
type: adhoc
allocate: |
sleep 0.$RANDOM
sleep 0.$RANDOM
sleep 0.$RANDOM

mkdir -p "$HOME/.spread"
export counter_file="$HOME/.spread/multipass-count"
instance_num=$(flock -x $counter_file bash -c '
[ -s $counter_file ] || echo 0 > $counter_file
num=$(< $counter_file)
echo $num
echo $(( $num + 1 )) > $counter_file')

multipass_image=$(echo "${SPREAD_SYSTEM}" | sed -e s/ubuntu-// -e s/-64//)

system=$(echo "${SPREAD_SYSTEM}" | tr . -)
instance_name="spread-${SPREAD_BACKEND}-${instance_num}-${system}"

multipass launch -vv --cpus 2 --disk 20G --memory 4G --name "${instance_name}" \
--cloud-init tests/spread/lib/cloud-config.yaml "${multipass_image}"

# Get the IP from the instance
ip=$(multipass info --format csv "$instance_name" | tail -1 | cut -d\, -f3)
ADDRESS "$ip"

discard: |
instance_name=$(multipass list --format csv | grep $SPREAD_SYSTEM_ADDRESS | cut -f1 -d\,)
multipass delete --purge "${instance_name}"

systems:
- ubuntu-22.04:
username: spread
password: spread
workers: 1

- ubuntu-20.04:
username: spread
password: spread
workers: 1

github-ci:
type: adhoc

allocate: |
echo "Allocating ad-hoc $SPREAD_SYSTEM"
if [ -z "${GITHUB_RUN_ID:-}" ]; then
FATAL "this back-end only works inside GitHub CI"
exit 1
fi
echo 'ubuntu ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/99-spread-users
ADDRESS localhost:22

discard: |
echo "Discarding ad-hoc $SPREAD_SYSTEM"

systems:
- ubuntu-22.04-amd64:
username: ubuntu
password: ubuntu
workers: 1


suites:
tests/spread/general/:
summary: Charm functionality tests

systems:
- ubuntu-22.04

prepare: |
set -e
. "$CRAFT_TEST_LIB_PATH"/test-helpers.sh
apt update -y
apt install -y python3-pip
pip3 install tox

install_lxd
install_charmcraft
install_juju
bootstrap_juju

juju add-model testing

restore: |
set -e
. "$CRAFT_TEST_LIB_PATH"/test-helpers.sh
rm -f "$PROJECT_PATH"/*.charm
charmcraft clean -p "$PROJECT_PATH"

restore_juju
restore_charmcraft
restore_lxd

exclude:
- .git
- .tox

path: /home/spread/proj

prepare: |
snap refresh --hold

if systemctl is-enabled unattended-upgrades.service; then
systemctl stop unattended-upgrades.service
systemctl mask unattended-upgrades.service
fi

restore: |
apt autoremove -y --purge
rm -Rf "$PROJECT_PATH"
mkdir -p "$PROJECT_PATH"


kill-timeout: 1h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ from pytest_operator.plugin import OpsTest

logger = logging.getLogger(__name__)

METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
METADATA = yaml.safe_load(Path("./charmcraft.yaml").read_text())
APP_NAME = METADATA["name"]


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
summary: Run the charm integration test

execute: |
tox -e integration
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#cloud-config

ssh_pwauth: true

users:
- default
- name: spread
plain_text_passwd: spread
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a way to do this using spread's generated passwords?

If you don't have a simple way to do it immediately, please make an issue so we don't forget about it - I don't want spread/spread to become one of the username/password pairs bad guys start scanning cloud services with :-)

lock_passwd: false
sudo: ALL=(ALL) NOPASSWD:ALL
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

export PATH=/snap/bin:$PROJECT_PATH/tests/spread/lib/tools:$PATH
export CONTROLLER_NAME="craft-test-$PROVIDER"


install_lxd() {
snap install lxd --channel "$LXD_CHANNEL"
snap refresh lxd --channel "$LXD_CHANNEL"
lxd waitready
lxd init --auto
chmod a+wr /var/snap/lxd/common/lxd/unix.socket
lxc network set lxdbr0 ipv6.address none
usermod -a -G lxd "$USER"

# Work-around clash between docker and lxd on jammy
# https://github.com/docker/for-linux/issues/1034
iptables -F FORWARD
iptables -P FORWARD ACCEPT
}


install_charmcraft() {
snap install charmcraft --classic --channel "$CHARMCRAFT_CHANNEL"
snap refresh charmcraft --classic --channel "$CHARMCRAFT_CHANNEL"
}


install_juju() {
snap install juju --classic --channel "$JUJU_CHANNEL"
snap refresh juju --classic --channel "$JUJU_CHANNEL"
mkdir -p "$HOME"/.local/share/juju
snap install juju-crashdump --classic
}


bootstrap_juju() {
juju bootstrap --verbose "$PROVIDER" "$CONTROLLER_NAME" \
$JUJU_BOOTSTRAP_OPTIONS $JUJU_EXTRA_BOOTSTRAP_OPTIONS \
--bootstrap-constraints=$JUJU_BOOTSTRAP_CONSTRAINTS
}


restore_charmcraft() {
snap remove --purge charmcraft
}


restore_lxd() {
snap stop lxd
snap remove --purge lxd
}


restore_juju() {
juju controllers --refresh ||:
juju destroy-controller -v --no-prompt --show-log \
--destroy-storage --destroy-all-models "$CONTROLLER_NAME"
snap stop juju
snap remove --purge juju
snap remove --purge juju-crashdump
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ from pytest_operator.plugin import OpsTest

logger = logging.getLogger(__name__)

METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
METADATA = yaml.safe_load(Path("./charmcraft.yaml").read_text())
APP_NAME = METADATA["name"]


Expand Down
17 changes: 17 additions & 0 deletions snap/snapcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ apps:
# same for config
XDG_CONFIG_HOME: $SNAP_USER_COMMON/config

spread:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd rather not have a separate charmcraft.spread application for now.

command: bin/spread

confinement: classic

build-packages:
Expand Down Expand Up @@ -135,6 +138,20 @@ parts:
organize:
bin/craftctl: libexec/charmcraft/craftctl

spread:
plugin: go
source: https://github.com/snapcore/spread
source-type: git
source-depth: 1
build-packages:
- golang-go
build-attributes:
- enable-patchelf
build-environment:
- CGO_ENABLED: 0
stage:
- -bin/humbox

hooks:
configure:
environment:
Expand Down
Loading