From 7798a492bcfb6d27b97b6762398ee0bc0d5180b8 Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Wed, 8 May 2024 15:47:43 +0200 Subject: [PATCH 1/6] compatible(integration_test_charm.yaml): Add support for arm64 --- .github/workflows/integration_test_charm.yaml | 52 +++++++++++++++---- .../pytest_operator_cache/_plugin.py | 29 ++++++++--- .../pytest_operator_groups/_plugin.py | 8 ++- 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/.github/workflows/integration_test_charm.yaml b/.github/workflows/integration_test_charm.yaml index 17514b0d..88dd52df 100644 --- a/.github/workflows/integration_test_charm.yaml +++ b/.github/workflows/integration_test_charm.yaml @@ -13,8 +13,16 @@ on: Use canonical/data-platform-workflows build_charm.yaml to build the charm(s) required: true type: string + architecture: + # Keep description synchronized with "Parse architecture input" step + description: | + Processor architecture + + Must be one of "amd64", "arm64" + default: amd64 + type: string cloud: - # Keep description synchronized with "Validate input" step + # Keep description synchronized with "Parse cloud input" step description: | Juju cloud @@ -102,13 +110,34 @@ jobs: # ci.yaml will not show up on the GitHub Actions sidebar. # If this workflow is called with a matrix (e.g. to test multiple juju versions), the ci.yaml # job name containing the Juju version will be lost. - # So, we add the Juju version to one of the first jobs in this workflow. + # So, we add the Juju version & architecture to one of the first jobs in this workflow. # (In the UI, when this workflow is called with a matrix, GitHub will separate each matrix # combination and preserve job ordering within a matrix combination.) - name: ${{ inputs.juju-agent-version || inputs.juju-snap-channel }} | Collect integration test groups + name: ${{ inputs.juju-agent-version || inputs.juju-snap-channel }} | ${{ inputs.architecture }} | Collect integration test groups runs-on: ubuntu-latest timeout-minutes: 5 steps: + - name: Parse architecture input + id: parse-architecture + shell: python + # Keep synchronized with inputs.architecture description + run: | + import json + import os + + DEFAULT_RUNNERS = { + "amd64": "ubuntu-latest", + "arm64": "Ubuntu_ARM64_4C_16G_01", + } + ARCHITECTURE = "${{ inputs.architecture }}" + try: + default_runner = DEFAULT_RUNNERS[ARCHITECTURE] + except KeyError: + raise ValueError(f"`architecture` input not recognized: {ARCHITECTURE}") + output = f"default_runner={json.dumps(default_runner)}" + print(output) + with open(os.environ["GITHUB_OUTPUT"], "a") as file: + file.write(output) - name: Checkout uses: actions/checkout@v4 - name: Install tox & poetry @@ -135,21 +164,22 @@ jobs: run: tox run -e integration -- tests/integration -m '${{ steps.select-test-stability.outputs.mark_expression }}' --collect-groups outputs: groups: ${{ steps.collect-groups.outputs.groups }} + default_runner: ${{ steps.parse-architecture.outputs.default_runner }} integration-test: strategy: fail-fast: false matrix: groups: ${{ fromJSON(needs.collect-integration-tests.outputs.groups) }} - name: ${{ matrix.groups.job_name }} + name: ${{ format(matrix.groups.job_name, inputs.architecture) }} needs: - get-workflow-version - collect-integration-tests - runs-on: ${{ matrix.groups.runner || 'ubuntu-latest' }} + runs-on: ${{ matrix.groups.runner || needs.collect-integration-tests.outputs.default_runner }} timeout-minutes: 120 steps: - name: Free up disk space - if: ${{ !matrix.groups.self_hosted }} + if: ${{ !(inputs.architecture == 'arm64' || matrix.groups.self_hosted) }} run: | printf '\nDisk usage before cleanup\n' df --human-readable @@ -159,10 +189,10 @@ jobs: printf '\nDisk usage after cleanup\n' df --human-readable - name: (self-hosted) Disk usage - if: ${{ matrix.groups.self_hosted }} + if: ${{ inputs.architecture == 'arm64' || matrix.groups.self_hosted }} run: df --human-readable - name: (self-hosted) Install pipx - if: ${{ matrix.groups.self_hosted }} + if: ${{ inputs.architecture == 'arm64' || matrix.groups.self_hosted }} run: | sudo apt-get update sudo apt-get install python3-pip python3-venv -y @@ -319,7 +349,7 @@ jobs: if: ${{ (success() || (failure() && steps.tests.outcome == 'failure')) && inputs._beta_allure_report && github.event_name == 'schedule' && github.run_attempt == '1' }} uses: actions/upload-artifact@v4 with: - name: allure-results-integration-test-charm-${{ inputs.cloud }}-juju-${{ inputs.juju-agent-version || steps.parse-versions.outputs.snap_channel_for_artifact }}-${{ matrix.groups.artifact_group_id }} + name: allure-results-integration-test-charm-${{ inputs.cloud }}-juju-${{ inputs.juju-agent-version || steps.parse-versions.outputs.snap_channel_for_artifact }}-${{ inputs.architecture }}-${{ matrix.groups.artifact_group_id }} path: allure-results/ if-no-files-found: error - name: Select model @@ -348,7 +378,7 @@ jobs: if: ${{ success() || (failure() && steps.tests.outcome == 'failure') }} uses: actions/upload-artifact@v4 with: - name: logs-intergration-test-charm-${{ inputs.cloud }}-juju-${{ inputs.juju-agent-version || steps.parse-versions.outputs.snap_channel_for_artifact }}-${{ matrix.groups.artifact_group_id }} + name: logs-intergration-test-charm-${{ inputs.cloud }}-juju-${{ inputs.juju-agent-version || steps.parse-versions.outputs.snap_channel_for_artifact }}-${{ inputs.architecture }}-${{ matrix.groups.artifact_group_id }} path: ~/logs/ if-no-files-found: error - name: Disk usage @@ -393,7 +423,7 @@ jobs: uses: actions/download-artifact@v4 with: path: allure-results/ - pattern: allure-results-integration-test-charm-${{ inputs.cloud }}-juju-${{ inputs.juju-agent-version || needs.integration-test.outputs.juju-snap-channel-for-artifact }}-* + pattern: allure-results-integration-test-charm-${{ inputs.cloud }}-juju-${{ inputs.juju-agent-version || needs.integration-test.outputs.juju-snap-channel-for-artifact }}-${{ inputs.architecture }}-* merge-multiple: true - name: Load test report history run: | diff --git a/python/pytest_plugins/pytest_operator_cache/pytest_operator_cache/_plugin.py b/python/pytest_plugins/pytest_operator_cache/pytest_operator_cache/_plugin.py index 06f7cd30..f7e1b26d 100644 --- a/python/pytest_plugins/pytest_operator_cache/pytest_operator_cache/_plugin.py +++ b/python/pytest_plugins/pytest_operator_cache/pytest_operator_cache/_plugin.py @@ -1,5 +1,6 @@ import os import pathlib +import subprocess import typing import yaml @@ -22,17 +23,31 @@ async def build_charm( self, charm_path: typing.Union[str, os.PathLike], bases_index: int = None ) -> pathlib.Path: charm_path = pathlib.Path(charm_path) - # TODO: add support for multiple architectures + architecture = subprocess.run( + ["dpkg", "--print-architecture"], + capture_output=True, + check=True, + encoding="utf-8", + ).stdout.strip() + assert architecture in ("amd64", "arm64") if bases_index is not None: charmcraft_yaml = yaml.safe_load((charm_path / "charmcraft.yaml").read_text()) assert charmcraft_yaml["type"] == "charm" base = charmcraft_yaml["bases"][bases_index] # Handle multiple base formats # See https://discourse.charmhub.io/t/charmcraft-bases-provider-support/4713 - version = base.get("build-on", [base])[0]["channel"] - packed_charms = list(charm_path.glob(f"*{version}-amd64.charm")) + build_on = base.get("build-on", [base])[0] + version = build_on["channel"] + architectures = build_on.get("architectures", ["amd64"]) + assert ( + len(architectures) == 1 + ), f"Multiple architectures ({architectures}) in one (charmcraft.yaml) base not supported. Use one base per architecture" + assert ( + architectures[0] == architecture + ), f"Architecture for {bases_index=} ({architectures[0]}) does not match host architecture ({architecture})" + packed_charms = list(charm_path.glob(f"*{version}-{architecture}.charm")) else: - packed_charms = list(charm_path.glob("*-amd64.charm")) + packed_charms = list(charm_path.glob(f"*-{architecture}.charm")) if len(packed_charms) == 1: # python-libjuju's model.deploy(), juju deploy, and juju bundle files expect local charms # to begin with `./` or `/` to distinguish them from Charmhub charms. @@ -43,13 +58,11 @@ async def build_charm( # `pathlib.Path`.) return packed_charms[0].resolve(strict=True) elif len(packed_charms) > 1: - message = f"More than one matching .charm file found at {charm_path=}: {packed_charms}." + message = f"More than one matching .charm file found at {charm_path=} for {architecture=}: {packed_charms}." if bases_index is None: message += " Specify `bases_index`" - else: - message += " Does charmcraft.yaml contain non-amd64 architecture?" raise ValueError(message) else: raise ValueError( - f"Unable to find amd64 .charm file for {bases_index=} at {charm_path=}" + f"Unable to find .charm file for {architecture=} and {bases_index=} at {charm_path=}" ) diff --git a/python/pytest_plugins/pytest_operator_groups/pytest_operator_groups/_plugin.py b/python/pytest_plugins/pytest_operator_groups/pytest_operator_groups/_plugin.py index 34d2f0cc..73fc2c7c 100644 --- a/python/pytest_plugins/pytest_operator_groups/pytest_operator_groups/_plugin.py +++ b/python/pytest_plugins/pytest_operator_groups/pytest_operator_groups/_plugin.py @@ -129,8 +129,12 @@ def from_group(cls, group: Group, *, runner: typing.Optional[_Runner]): assert name.split(".")[0] == "integration" # Example: "tests/integration/relations/test_database.py" path_to_test_file = f"tests/{name.replace('.', '/')}.py" - # Example: "relations/test_database.py | group 1" - job_name = f"{'/'.join(path_to_test_file.split('/')[2:])} | group {group_id}" + # Example: "relations/test_database.py | {0} | group 1" + # "{0}" used for formatting (to add architecture) in workflow YAML file + # (https://docs.github.com/en/actions/learn-github-actions/expressions#format) + job_name = ( + f"{'/'.join(path_to_test_file.split('/')[2:])} | {{0}} | group {group_id}" + ) # Example: "relations-test_database.py-group-1" artifact_group_id = ( f"{'-'.join(path_to_test_file.split('/')[2:])}-group-{group_id}" From bb50cc4592389a13a3a7508a0e3fa84fed8133ea Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Wed, 8 May 2024 16:49:19 +0200 Subject: [PATCH 2/6] Add python link --- .github/workflows/integration_test_charm.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/integration_test_charm.yaml b/.github/workflows/integration_test_charm.yaml index 88dd52df..a8b647ba 100644 --- a/.github/workflows/integration_test_charm.yaml +++ b/.github/workflows/integration_test_charm.yaml @@ -191,6 +191,9 @@ jobs: - name: (self-hosted) Disk usage if: ${{ inputs.architecture == 'arm64' || matrix.groups.self_hosted }} run: df --human-readable + - name: (arm64 GitHub-hosted) Link python to python3 + if: ${{ inputs.architecture == 'arm64' && !matrix.groups.self_hosted }} + run: sudo ln -s /usr/bin/python3 /usr/bin/python - name: (self-hosted) Install pipx if: ${{ inputs.architecture == 'arm64' || matrix.groups.self_hosted }} run: | From 25de283c35a81275f441f5bb91f67c891c967eff Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Wed, 8 May 2024 16:49:31 +0200 Subject: [PATCH 3/6] Add lxd architecture constraint --- .github/workflows/integration_test_charm.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/integration_test_charm.yaml b/.github/workflows/integration_test_charm.yaml index a8b647ba..b7ebf6dc 100644 --- a/.github/workflows/integration_test_charm.yaml +++ b/.github/workflows/integration_test_charm.yaml @@ -308,6 +308,11 @@ jobs: juju add-model test pipx install tox pipx install poetry + - name: Add architecture model constraint + if: ${{ inputs.cloud == 'lxd' }} + # Unable to set constraint on all models because of Juju bug: + # https://bugs.launchpad.net/juju/+bug/2065050 + run: juju set-model-constraints arch='${{ inputs.architecture }}' - name: Update python-libjuju version if: ${{ inputs.libjuju-version-constraint }} run: poetry add --lock --group integration juju@'${{ inputs.libjuju-version-constraint }}' From 83998d4f233fc8620f7a4a419aa51a6732c68f7b Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Wed, 8 May 2024 17:19:52 +0200 Subject: [PATCH 4/6] remove architecture from job name --- .github/workflows/integration_test_charm.yaml | 2 +- .../pytest_operator_groups/_plugin.py | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/integration_test_charm.yaml b/.github/workflows/integration_test_charm.yaml index b7ebf6dc..65598dfc 100644 --- a/.github/workflows/integration_test_charm.yaml +++ b/.github/workflows/integration_test_charm.yaml @@ -171,7 +171,7 @@ jobs: fail-fast: false matrix: groups: ${{ fromJSON(needs.collect-integration-tests.outputs.groups) }} - name: ${{ format(matrix.groups.job_name, inputs.architecture) }} + name: ${{ matrix.groups.job_name }} needs: - get-workflow-version - collect-integration-tests diff --git a/python/pytest_plugins/pytest_operator_groups/pytest_operator_groups/_plugin.py b/python/pytest_plugins/pytest_operator_groups/pytest_operator_groups/_plugin.py index 73fc2c7c..34d2f0cc 100644 --- a/python/pytest_plugins/pytest_operator_groups/pytest_operator_groups/_plugin.py +++ b/python/pytest_plugins/pytest_operator_groups/pytest_operator_groups/_plugin.py @@ -129,12 +129,8 @@ def from_group(cls, group: Group, *, runner: typing.Optional[_Runner]): assert name.split(".")[0] == "integration" # Example: "tests/integration/relations/test_database.py" path_to_test_file = f"tests/{name.replace('.', '/')}.py" - # Example: "relations/test_database.py | {0} | group 1" - # "{0}" used for formatting (to add architecture) in workflow YAML file - # (https://docs.github.com/en/actions/learn-github-actions/expressions#format) - job_name = ( - f"{'/'.join(path_to_test_file.split('/')[2:])} | {{0}} | group {group_id}" - ) + # Example: "relations/test_database.py | group 1" + job_name = f"{'/'.join(path_to_test_file.split('/')[2:])} | group {group_id}" # Example: "relations-test_database.py-group-1" artifact_group_id = ( f"{'-'.join(path_to_test_file.split('/')[2:])}-group-{group_id}" From 7af2f102404034e796b3a4ed34d3563c01e08cb0 Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Fri, 10 May 2024 10:46:52 +0200 Subject: [PATCH 5/6] test --- .github/workflows/integration_test_charm.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test_charm.yaml b/.github/workflows/integration_test_charm.yaml index 65598dfc..264283ba 100644 --- a/.github/workflows/integration_test_charm.yaml +++ b/.github/workflows/integration_test_charm.yaml @@ -175,7 +175,7 @@ jobs: needs: - get-workflow-version - collect-integration-tests - runs-on: ${{ matrix.groups.runner || needs.collect-integration-tests.outputs.default_runner }} + runs-on: ${{ matrix.groups.runner || 'ubuntu-latest' }} timeout-minutes: 120 steps: - name: Free up disk space From b94c569a53e69ae624df7514e230301742aebdc2 Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Fri, 10 May 2024 10:49:28 +0200 Subject: [PATCH 6/6] fromJSON fix --- .github/workflows/integration_test_charm.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test_charm.yaml b/.github/workflows/integration_test_charm.yaml index 264283ba..6f559d62 100644 --- a/.github/workflows/integration_test_charm.yaml +++ b/.github/workflows/integration_test_charm.yaml @@ -175,7 +175,7 @@ jobs: needs: - get-workflow-version - collect-integration-tests - runs-on: ${{ matrix.groups.runner || 'ubuntu-latest' }} + runs-on: ${{ matrix.groups.runner || fromJSON(needs.collect-integration-tests.outputs.default_runner) }} timeout-minutes: 120 steps: - name: Free up disk space