diff --git a/artserver/dummy_pyproject.toml b/artserver/dummy_pyproject.toml new file mode 100644 index 0000000..2bd2e2b --- /dev/null +++ b/artserver/dummy_pyproject.toml @@ -0,0 +1,12 @@ +[tool.poetry] +name = "workaround-for-editable-install" +version = "0.0.1" +description = "" +authors = ["none"] + +[tool.poetry.dependencies] +# This range should match that used upstream: https://github.com/kserve/kserve/blob/v0.11.2/python/artexplainer/pyproject.toml#L13 +python = ">=3.9,<3.12" +kserve = { path = "../python/kserve", develop = false } +artserver = { path = "../python/artexplainer", develop = false } + diff --git a/artserver/rockcraft.yaml b/artserver/rockcraft.yaml new file mode 100644 index 0000000..e3369cb --- /dev/null +++ b/artserver/rockcraft.yaml @@ -0,0 +1,82 @@ +# Based on https://github.com/kserve/kserve/blob/v0.13.0/python/pmml.Dockerfile +# +# See ../CONTRIBUTING.md for more details about the patterns used in this rock. +# This rock is implemented with some atypical patterns due to the native of the upstream +# Dockerfile. +name: artserver +summary: Art server for Kserve deployments +description: "Kserve Art server" +version: "0.13.0" +license: Apache-2.0 +base: ubuntu@22.04 +run-user: _daemon_ + +platforms: + amd64: + + +services: + artserver: + override: replace + summary: "Art server service" + startup: enabled + command: "python -m artserver [ ]" +entrypoint-service: artserver + +parts: + security-team-requirement: + plugin: nil + override-build: | + mkdir -p ${CRAFT_PART_INSTALL}/usr/share/rocks + (echo "# os-release" && cat /etc/os-release && echo "# dpkg-query" && \ + dpkg-query --root=${CRAFT_PROJECT_DIR}/../bundles/ubuntu-22.04/rootfs/ -f '${db:Status-Abbrev},${binary:Package},${Version},${source:Package},${Source:Version}\n' -W) \ + > ${CRAFT_PART_INSTALL}/usr/share/rocks/dpkg.query + + python: + plugin: nil + source: https://github.com/kserve/kserve.git + source-subdir: python + source-tag: v0.13.0 + build-packages: + - build-essential + - libgomp1 + overlay-packages: + - python3.10 + # Including python3-pip here means pip also gets primed for the final rock + - python3-pip + override-build: | + # Populate the build system's python environment with all packages needed for + # the server in the final rock + + # Setup poetry + pip install poetry==1.7.1 + poetry config virtualenvs.create false + + # Install the kserve package, this specific server package, and their dependencies. + mkdir -p ./python_env_builddir + cp -rf $CRAFT_PROJECT_DIR/dummy_pyproject.toml ./python_env_builddir/pyproject.toml + (cd python_env_builddir && poetry install --no-interaction --no-root) + + # Promote the packages we've installed from the local env to the primed image + mkdir -p $CRAFT_PART_INSTALL/usr/local/lib/python3.10/dist-packages + cp -fr /usr/local/lib/python3.10/dist-packages/* $CRAFT_PART_INSTALL/usr/local/lib/python3.10/dist-packages/ + + # TODO: why do we need this? + mkdir -p $CRAFT_PART_INSTALL/usr/local/share + cp -fr /usr/local/share/* $CRAFT_PART_INSTALL/usr/local/share/ + + # Ensure `python` is an executable command in our primed image by making + # a symbolic link + mkdir -p $CRAFT_PART_INSTALL/usr/bin/ + ln -s /usr/bin/python3.10 $CRAFT_PART_INSTALL/usr/bin/python + + # Copy licenses + third-party: + plugin: nil + after: [python] + source: https://github.com/kserve/kserve.git + source-subdir: python + source-tag: v0.13.0 + override-build: | + cp -fr third_party/* ${CRAFT_PART_INSTALL}/third_party + diff --git a/artserver/tests/test_rock.py b/artserver/tests/test_rock.py new file mode 100644 index 0000000..07666fc --- /dev/null +++ b/artserver/tests/test_rock.py @@ -0,0 +1,78 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + +from pathlib import Path + +import os +import logging +import random +import pytest +import string +import subprocess +import yaml + +from charmed_kubeflow_chisme.rock import CheckRock + + +@pytest.fixture() +def rock_test_env(tmpdir): + """Yields a temporary directory and random docker container name, then cleans them up after.""" + container_name = "".join( + [str(i) for i in random.choices(string.ascii_lowercase, k=8)] + ) + yield tmpdir, container_name + + try: + subprocess.run(["docker", "rm", container_name]) + except Exception: + pass + # tmpdir fixture we use here should clean up the other files for us + + +@pytest.mark.abort_on_fail +def test_rock(rock_test_env): + """Test rock.""" + temp_dir, container_name = rock_test_env + check_rock = CheckRock("rockcraft.yaml") + rock_image = check_rock.get_name() + rock_version = check_rock.get_version() + LOCAL_ROCK_IMAGE = f"{rock_image}:{rock_version}" + + # assert we have the expected files + subprocess.run( + [ + "docker", + "run", + "--entrypoint", + "/bin/bash", + LOCAL_ROCK_IMAGE, + "-c", + "ls -la /usr/local/lib/python3.10/dist-packages/artserver", + ], + check=True, + ) + subprocess.run( + [ + "docker", + "run", + "--entrypoint", + "/bin/bash", + LOCAL_ROCK_IMAGE, + "-c", + "ls -la /usr/local/lib/python3.10/dist-packages/kserve", + ], + check=True, + ) + subprocess.run( + [ + "docker", + "run", + "--entrypoint", + "/bin/bash", + LOCAL_ROCK_IMAGE, + "-c", + "ls -la /third_party", + ], + check=True, + ) + diff --git a/artserver/tox.ini b/artserver/tox.ini new file mode 100644 index 0000000..98c7168 --- /dev/null +++ b/artserver/tox.ini @@ -0,0 +1,54 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. +[tox] +skipsdist = True +skip_missing_interpreters = True +envlist = pack, export-to-docker, sanity, integration + +[testenv] +setenv = + PYTHONPATH={toxinidir} + PYTHONBREAKPOINT=ipdb.set_trace + CHARM_REPO=https://github.com/canonical/kserve-operators.git + CHARM_BRANCH=main + LOCAL_CHARM_DIR=charm_repo + +[testenv:pack] +passenv = * +allowlist_externals = + rockcraft +commands = + rockcraft pack + +[testenv:export-to-docker] +passenv = * +allowlist_externals = + bash + skopeo + yq +commands = + # pack rock and export to docker + bash -c 'NAME=$(yq eval .name rockcraft.yaml) && \ + VERSION=$(yq eval .version rockcraft.yaml) && \ + ARCH=$(yq eval ".platforms | keys | .[0]" rockcraft.yaml) && \ + ROCK="$\{NAME\}_$\{VERSION\}_$\{ARCH\}.rock" && \ + DOCKER_IMAGE=$NAME:$VERSION && \ + echo "Exporting $ROCK to docker as $DOCKER_IMAGE" && \ + skopeo --insecure-policy copy oci-archive:$ROCK docker-daemon:$DOCKER_IMAGE' + +[testenv:sanity] +passenv = * +deps = + pytest + charmed-kubeflow-chisme +commands = + # run rock tests + pytest -s -v --tb native --show-capture=all --log-cli-level=INFO {posargs} {toxinidir}/tests + +[testenv:integration] +passenv = * +allowlist_externals = + echo +commands = + # TODO: Implement integration tests here + echo "WARNING: This is a placeholder test - no test is implemented here."