diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 61af795..b129baf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -64,12 +64,16 @@ jobs: integration-test: strategy: - fail-fast: true + fail-fast: false matrix: - bases: + bases: - ubuntu@22.04 - name: Integration tests (LXD) | ${{ matrix.bases }} + local: [true, false] + name: Integration tests (LXD) ${{ matrix.local && '|' || '| Charmhub (edge) |'}} ${{ matrix.bases }} runs-on: ubuntu-latest + # Testing against Charmhub will probably yield errors when doing breaking changes, so don't + # block CI on that. + continue-on-error: ${{ !matrix.local }} needs: - inclusive-naming-check - lint @@ -78,10 +82,33 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + path: main + - name: Fetch slurmd + uses: actions/checkout@v4 + if: ${{ matrix.local }} + with: + repository: charmed-hpc/slurmd-operator + path: slurmd-operator + - name: Fetch slurmdbd + uses: actions/checkout@v4 + if: ${{ matrix.local }} + with: + repository: charmed-hpc/slurmdbd-operator + path: slurmdbd-operator + - name: Fetch slurmrestd + uses: actions/checkout@v4 + if: ${{ matrix.local }} + with: + repository: charmed-hpc/slurmrestd-operator + path: slurmrestd-operator - name: Setup operator environment uses: charmed-kubernetes/actions-operator@main with: provider: lxd - juju-channel: 3.1/stable + juju-channel: 3.4/stable - name: Run tests - run: tox run -e integration -- --charm-base=${{ matrix.bases }} + run: | + cd main && tox run -e integration -- \ + --charm-base=${{ matrix.bases }} \ + ${{ matrix.local && '--use-local' || '' }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index dc0a8a2..f060d34 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -32,10 +32,10 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Select charmhub channel - uses: canonical/charming-actions/channel@2.2.0 + uses: canonical/charming-actions/channel@2.5.0-rc id: channel - name: Upload charm to charmhub - uses: canonical/charming-actions/upload-charm@2.2.0 + uses: canonical/charming-actions/upload-charm@2.5.0-rc with: credentials: "${{ secrets.CHARMCRAFT_AUTH }}" github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index ac9ac77..64a4704 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -26,6 +26,7 @@ logger = logging.getLogger(__name__) SLURMD_DIR = Path(os.getenv("SLURMD_DIR", "../slurmd-operator")) SLURMDBD_DIR = Path(os.getenv("SLURMDBD_DIR", "../slurmdbd-operator")) +SLURMRESTD_DIR = Path(os.getenv("SLURMRESTD_DIR", "../slurmrestd-operator")) def pytest_addoption(parser) -> None: @@ -93,3 +94,23 @@ async def slurmdbd_charm(request, ops_test: OpsTest) -> Union[str, Path]: ) return "slurmdbd" + + +@pytest.fixture(scope="module") +async def slurmrestd_charm(request, ops_test: OpsTest) -> Union[str, Path]: + """Pack slurmrestd charm to use for integration tests when --use-local is specified. + + Returns: + `str` "slurmrestd" if --use-local not specified or if SLURMRESTD_DIR does not exist. + """ + if request.config.option.use_local: + logger.info("Using local slurmrestd operator rather than pulling from Charmhub") + if SLURMRESTD_DIR.exists(): + return await ops_test.build_charm(SLURMRESTD_DIR) + else: + logger.warning( + f"{SLURMRESTD_DIR} not found. " + f"Defaulting to latest/edge slurmrestd operator from Charmhub" + ) + + return "slurmrestd" diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index c34dba3..e5f274b 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -27,22 +27,29 @@ SLURMCTLD = "slurmctld" SLURMD = "slurmd" SLURMDBD = "slurmdbd" +SLURMRESTD = "slurmrestd" DATABASE = "mysql" ROUTER = "mysql-router" -UNIT_NAME = f"{SLURMCTLD}/0" @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed @pytest.mark.order(1) async def test_build_and_deploy_against_edge( - ops_test: OpsTest, charm_base: str, slurmctld_charm, slurmd_charm, slurmdbd_charm + ops_test: OpsTest, + charm_base: str, + slurmctld_charm, + slurmd_charm, + slurmdbd_charm, + slurmrestd_charm, ) -> None: - """Test that the slurmctld charm can stabilize against slurmd, slurmdbd, and MySQL.""" - logger.info(f"Deploying {SLURMCTLD} against {SLURMD}, {SLURMDBD}, and {DATABASE}") + """Test that the slurmctld charm can stabilize against slurmd, slurmdbd, slurmrestd, and MySQL.""" + logger.info( + f"Deploying {SLURMCTLD} against {SLURMD}, {SLURMDBD}, {SLURMRESTD}, and {DATABASE}" + ) # Pack charms and download NHC resource for the slurmd operator. - slurmctld, slurmd, slurmdbd = await asyncio.gather( - slurmctld_charm, slurmd_charm, slurmdbd_charm + slurmctld, slurmd, slurmdbd, slurmrestd = await asyncio.gather( + slurmctld_charm, slurmd_charm, slurmdbd_charm, slurmrestd_charm ) # Deploy the test Charmed SLURM cloud. await asyncio.gather( @@ -66,6 +73,13 @@ async def test_build_and_deploy_against_edge( num_units=1, base=charm_base, ), + ops_test.model.deploy( + str(slurmrestd), + application_name=SLURMRESTD, + channel="edge" if isinstance(slurmrestd, str) else None, + num_units=1, + base=charm_base, + ), ops_test.model.deploy( ROUTER, application_name=f"{SLURMDBD}-{ROUTER}", @@ -84,12 +98,13 @@ async def test_build_and_deploy_against_edge( # Set integrations for charmed applications. await ops_test.model.integrate(f"{SLURMCTLD}:{SLURMD}", f"{SLURMD}:{SLURMCTLD}") await ops_test.model.integrate(f"{SLURMCTLD}:{SLURMDBD}", f"{SLURMDBD}:{SLURMCTLD}") + await ops_test.model.integrate(f"{SLURMCTLD}:{SLURMRESTD}", f"{SLURMRESTD}:{SLURMCTLD}") await ops_test.model.integrate(f"{SLURMDBD}-{ROUTER}:backend-database", f"{DATABASE}:database") await ops_test.model.integrate(f"{SLURMDBD}:database", f"{SLURMDBD}-{ROUTER}:database") # Reduce the update status frequency to accelerate the triggering of deferred events. async with ops_test.fast_forward(): await ops_test.model.wait_for_idle(apps=[SLURMCTLD], status="active", timeout=1000) - assert ops_test.model.units.get(UNIT_NAME).workload_status == "active" + assert ops_test.model.applications["slurmctld"].units[0].workload_status == "active" @pytest.mark.abort_on_fail @@ -102,7 +117,7 @@ async def test_build_and_deploy_against_edge( async def test_slurmctld_is_active(ops_test: OpsTest) -> None: """Test that slurmctld is active inside Juju unit.""" logger.info("Checking that slurmctld is active inside Juju unit") - slurmctld_unit = ops_test.model.units.get(UNIT_NAME) + slurmctld_unit = ops_test.model.applications["slurmctld"].units[0] res = (await slurmctld_unit.ssh("systemctl is-active slurmctld")).strip("\n") assert res == "active" @@ -117,7 +132,7 @@ async def test_slurmctld_is_active(ops_test: OpsTest) -> None: async def test_slurmctld_port_listen(ops_test: OpsTest) -> None: """Test that slurmctld is listening on port 6817.""" logger.info("Checking that slurmctld is listening on port 6817") - slurmctld_unit = ops_test.model.units.get(UNIT_NAME) + slurmctld_unit = ops_test.model.applications["slurmctld"].units[0] res = await slurmctld_unit.ssh("sudo lsof -t -n -iTCP:6817 -sTCP:LISTEN") assert res != "" @@ -132,6 +147,6 @@ async def test_slurmctld_port_listen(ops_test: OpsTest) -> None: async def test_munge_is_active(ops_test: OpsTest) -> None: """Test that munge is active inside Juju unit.""" logger.info("Checking that munge is active inside Juju unit") - slurmctld_unit = ops_test.model.units.get(UNIT_NAME) + slurmctld_unit = ops_test.model.applications["slurmctld"].units[0] res = (await slurmctld_unit.ssh("systemctl is-active munge")).strip("\n") assert res == "active"