From 5868ee8dd4c1dbb72d7c78ba906390d6c60be372 Mon Sep 17 00:00:00 2001 From: Andrew Scribner Date: Wed, 6 Mar 2024 11:34:55 -0500 Subject: [PATCH 1/2] add lgbserver rock (v0.11.2) and tests Closes #31 --- lgbserver/README.md | 47 ++++++++++++++++++++ lgbserver/dummy_pyproject.toml | 11 +++++ lgbserver/rockcraft.yaml | 81 ++++++++++++++++++++++++++++++++++ lgbserver/tests/test_rock.py | 73 ++++++++++++++++++++++++++++++ lgbserver/tox.ini | 54 +++++++++++++++++++++++ 5 files changed, 266 insertions(+) create mode 100644 lgbserver/README.md create mode 100644 lgbserver/dummy_pyproject.toml create mode 100644 lgbserver/rockcraft.yaml create mode 100644 lgbserver/tests/test_rock.py create mode 100644 lgbserver/tox.ini diff --git a/lgbserver/README.md b/lgbserver/README.md new file mode 100644 index 0000000..da64582 --- /dev/null +++ b/lgbserver/README.md @@ -0,0 +1,47 @@ +## Testing + +### Prerequisites + +* docker +* Google Cloud CLI tools ([installation guide](https://cloud.google.com/sdk/docs/install)) + +### Instructions + +From the [upstream usage example](https://kserve.github.io/website/master/modelserving/v1beta1/lightgbm/), this rock can be tested locally using: + +Launch the server with: +``` +# download the model locally +mkdir sample_model +gsutil cp -r gs://kfserving-examples/models/lightgbm/v2/iris ./sample_model/ + +# mount the model into the container at runtime +docker run -p 8080:8080 -v $(pwd)/sample_model/iris:/mnt/models lgbserver: --model_name test_model --model_dir=/mnt/models --http_port=8080 + +``` + +Test the server with: +``` +cat <> iris-input-v2.json +{ + "inputs": [ + { + "name": "input-0", + "shape": [2, 4], + "datatype": "FP32", + "data": [ + [6.8, 2.8, 4.8, 1.4], + [6.0, 3.4, 4.5, 1.6] + ] + } + ] +} +EOF + +curl -v \ + -H "Content-Type: application/json" \ + -d @./iris-input-v2.json \ + localhost:8080/v2/models/test_model/infer +``` + +which should return the expected output described in the docs. diff --git a/lgbserver/dummy_pyproject.toml b/lgbserver/dummy_pyproject.toml new file mode 100644 index 0000000..bce8a92 --- /dev/null +++ b/lgbserver/dummy_pyproject.toml @@ -0,0 +1,11 @@ +[tool.poetry] +name = "workaround-for-editable-install" +version = "0.0.1" +description = "" +authors = ["none"] + +[tool.poetry.dependencies] +python = ">=3.8,<3.12" +kserve = { path = "../python/kserve", develop = false } +lgbserver = { path = "../python/lgbserver", develop = false } + diff --git a/lgbserver/rockcraft.yaml b/lgbserver/rockcraft.yaml new file mode 100644 index 0000000..d535cc9 --- /dev/null +++ b/lgbserver/rockcraft.yaml @@ -0,0 +1,81 @@ +# Based on https://github.com/kserve/kserve/blob/v0.11.2/python/lgb.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: lgbserver +summary: LightGBM server for Kserve deployments +description: "Kserve LightGBM server" +version: "0.11.2" +license: Apache-2.0 +base: ubuntu@22.04 +platforms: + amd64: +run-user: _daemon_ +services: + lgbserver: + override: replace + summary: "LightGBM server service" + startup: enabled + command: "python -m lgbserver [ ]" +entrypoint-service: lgbserver + +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.11.2 + build-packages: + - build-essential + - libgomp1 + stage-packages: + - 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.4.0 + 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.11.2 + override-build: | + cp -fr third_party/* ${CRAFT_PART_INSTALL}/third_party + diff --git a/lgbserver/tests/test_rock.py b/lgbserver/tests/test_rock.py new file mode 100644 index 0000000..77aed84 --- /dev/null +++ b/lgbserver/tests/test_rock.py @@ -0,0 +1,73 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + +import random +import pytest +import string +import subprocess + +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/lgbserver", + ], + 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/lgbserver/tox.ini b/lgbserver/tox.ini new file mode 100644 index 0000000..de23c73 --- /dev/null +++ b/lgbserver/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." \ No newline at end of file From cc1570d6874c5bc9945d5a5895b21f8123853134 Mon Sep 17 00:00:00 2001 From: Andrew Scribner Date: Thu, 7 Mar 2024 08:15:57 -0500 Subject: [PATCH 2/2] add comment to dummy pyproject.toml --- lgbserver/dummy_pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/lgbserver/dummy_pyproject.toml b/lgbserver/dummy_pyproject.toml index bce8a92..21746ca 100644 --- a/lgbserver/dummy_pyproject.toml +++ b/lgbserver/dummy_pyproject.toml @@ -5,6 +5,7 @@ description = "" authors = ["none"] [tool.poetry.dependencies] +# This range should match that used in upstream's server pyproject.toml python = ">=3.8,<3.12" kserve = { path = "../python/kserve", develop = false } lgbserver = { path = "../python/lgbserver", develop = false }