-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: mlserver-xgboost rock canonical/seldon-core-operator#133 Summary of changes: - mlserver-xgboost rockcraft - Unit tests. - tox.ini ready for integration tests. --------- Co-authored-by: Andrew Scribner <[email protected]>
- Loading branch information
1 parent
99f993d
commit 8872e41
Showing
3 changed files
with
238 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# Based on https://github.com/SeldonIO/MLServer/blob/1.2.0/Dockerfile | ||
name: mlserver-xgboost | ||
summary: An image for Seldon MLServer XGBoost | ||
description: | | ||
This image is used as part of the Charmed Kubeflow product. | ||
version: 1.2.0_22.04_1 # <upstream-version>_<base-version>_<Charmed-KF-version> | ||
license: Apache-2.0 | ||
base: ubuntu:22.04 | ||
run-user: _daemon_ | ||
services: | ||
mlserver-xgboost: | ||
override: replace | ||
summary: "mlserver-xgboost service" | ||
startup: enabled | ||
# We need to build and activate the "hot-loaded" environment before MLServer starts | ||
command: bash -c 'export PATH=/opt/conda/bin/:/opt/mlserver/.local/bin:${PATH} && \ | ||
export PYTHONPATH=/opt/mlserver/.local/lib/python3.8/site-packages/:${PYTHONPATH} && \ | ||
source /opt/conda/etc/profile.d/conda.sh && \ | ||
source /hack/activate-env.sh $MLSERVER_ENV_TARBALL && \ | ||
mlserver start $MLSERVER_MODELS_DIR' | ||
environment: | ||
MLSERVER_ENV_TARBALL: "/mnt/models" | ||
MLSERVER_MODELS_DIR: "/mnt/models/environment.tar.gz" | ||
platforms: | ||
amd64: | ||
|
||
parts: | ||
mlserver-xgboost: | ||
plugin: nil | ||
source: https://github.com/SeldonIO/MLServer | ||
source-type: git | ||
source-tag: 1.2.0 | ||
build-packages: | ||
- bash | ||
- tar | ||
- gzip | ||
# TO-DO: Verify need for the packages below | ||
# - libgomp | ||
# - mesa-libGL | ||
# - glib2-devel | ||
# - shadow-utils | ||
stage-packages: | ||
- bash | ||
override-build: | | ||
mkdir -p ${CRAFT_PART_INSTALL}/opt/mlserver/dist | ||
cp ${CRAFT_PART_SRC}/setup.py . | ||
cp ${CRAFT_PART_SRC}/README.md . | ||
./hack/build-wheels.sh ${CRAFT_PART_INSTALL}/opt/mlserver/dist | ||
override-stage: | | ||
export ROCK_RUNTIME="xgboost" | ||
export PYTHON_VERSION="3.8.13" | ||
export CONDA_VERSION="4.13.0" | ||
export RUNTIMES="mlserver_${ROCK_RUNTIME}" | ||
export MINIFORGE_VERSION="${CONDA_VERSION}-1" | ||
export MLSERVER_PATH=opt/mlserver | ||
export CONDA_PATH=opt/conda | ||
export PATH=/opt/mlserver/.local/bin:/opt/conda/bin:$PATH | ||
# Install Conda, Python 3.8 and FFmpeg | ||
curl -L -o ~/miniforge3.sh https://github.com/conda-forge/miniforge/releases/download/${MINIFORGE_VERSION}/Miniforge3-${MINIFORGE_VERSION}-Linux-x86_64.sh | ||
bash ~/miniforge3.sh -b -u -p ${CONDA_PATH} | ||
rm ~/miniforge3.sh | ||
${CONDA_PATH}/bin/conda install --yes conda=${CONDA_VERSION} python=${PYTHON_VERSION} ffmpeg | ||
${CONDA_PATH}/bin/conda clean -tipy | ||
mkdir -p etc/profile.d | ||
ln -sf ${CONDA_PATH}/etc/profile.d/conda.sh etc/profile.d/conda.sh | ||
echo ". ${CONDA_PATH}/etc/profile.d/conda.sh" >> ~/.bashrc | ||
echo "PATH=${PATH}" >> ~/.bashrc | ||
bash -c "${CONDA_PATH}/bin/conda init bash" | ||
echo "conda activate base" >> ~/.bashrc | ||
chgrp -R root opt/conda && chmod -R g+rw opt/conda | ||
# conda writes shebangs with its path everywhere, and in crafting, that will be, for example: | ||
# #!/root/stage/opt/conda/... | ||
# | ||
# Snip off the /root/stage part | ||
bash -c "grep -R -E '/root/stage' opt/ 2>/dev/null | grep -v Bin | awk '{split(\$0,out,\":\"); print out[1]}' | uniq | xargs -I{} sed -i -e 's/\/root\/stage//' {}" | ||
# install required wheels | ||
mkdir -p $MLSERVER_PATH | ||
mkdir -p ./wheels | ||
cp -p ${CRAFT_PART_INSTALL}/opt/mlserver/dist/mlserver-*.whl ./wheels | ||
cp -p ${CRAFT_PART_INSTALL}/opt/mlserver/dist/mlserver_${ROCK_RUNTIME}-*.whl ./wheels | ||
pip install --prefix ${MLSERVER_PATH}/.local $(ls "./wheels/mlserver-"*.whl) | ||
pip install --prefix ${MLSERVER_PATH}/.local $(ls "./wheels/mlserver_${ROCK_RUNTIME}-"*.whl) | ||
# replace first line of mlserver script with reference to installed Conda python | ||
export CONDA_PYTHON="#\!\/opt\/conda\/bin\/python" | ||
sed -i "1s/.*/${CONDA_PYTHON}/" ${MLSERVER_PATH}/.local/bin/mlserver | ||
# clean wheels | ||
rm -rf ./wheels | ||
override-prime: | | ||
# copy all artifacts | ||
cp -rp ${CRAFT_STAGE}/opt . | ||
# copy required files | ||
mkdir -p licenses | ||
cp ${CRAFT_PART_SRC}/licenses/license.txt licenses/ | ||
mkdir -p hack | ||
cp ${CRAFT_PART_SRC}/hack/build-env.sh hack/ | ||
cp ${CRAFT_PART_SRC}/hack/generate_dotenv.py hack/ | ||
cp ${CRAFT_PART_SRC}/hack/activate-env.sh hack/ | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# Copyright 2022 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 pytest_operator.plugin import OpsTest | ||
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(ops_test: OpsTest, rock_test_env): | ||
"""Test rock.""" | ||
temp_dir, container_name = rock_test_env | ||
check_rock = CheckRock("rockcraft.yaml") | ||
rock_image = check_rock.get_image_name() | ||
rock_version = check_rock.get_version() | ||
LOCAL_ROCK_IMAGE = f"{check_rock.get_image_name()}:{check_rock.get_version()}" | ||
|
||
# TO-DO uncomment when updated chisme is published | ||
#rock_services = check_rock.get_services() | ||
#assert rock_services["mlserver-xgboost"] | ||
#assert rock_services["mlserver-xgboost"]["startup"] == "enabled" | ||
|
||
# create ROCK filesystem | ||
subprocess.run(["docker", "run", LOCAL_ROCK_IMAGE, "exec", "ls", "-la", "/opt/mlserver/.local/lib/python3.8/site-packages/mlserver"], check=True) | ||
subprocess.run(["docker", "run", LOCAL_ROCK_IMAGE, "exec", "ls", "-la", "/opt/mlserver/.local/bin/mlserver"], check=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# Copyright 2022 Canonical Ltd. | ||
# See LICENSE file for licensing details. | ||
[tox] | ||
skipsdist = True | ||
skip_missing_interpreters = True | ||
|
||
[testenv] | ||
setenv = | ||
PYTHONPATH={toxinidir} | ||
PYTHONBREAKPOINT=ipdb.set_trace | ||
CHARM_REPO=https://github.com/canonical/seldon-core-operator.git | ||
CHARM_BRANCH=main | ||
LOCAL_CHARM_DIR=charm_repo | ||
|
||
[testenv:unit] | ||
passenv = * | ||
allowlist_externals = | ||
bash | ||
tox | ||
rockcraft | ||
deps = | ||
juju~=2.9.0 | ||
pytest | ||
pytest-operator | ||
ops | ||
charmed_kubeflow_chisme | ||
commands = | ||
# build and pack rock | ||
rockcraft pack | ||
bash -c 'NAME=$(yq eval .name rockcraft.yaml) && \ | ||
VERSION=$(yq eval .version rockcraft.yaml) && \ | ||
ARCH=$(yq eval ".platforms | keys" rockcraft.yaml | awk -F " " '\''{ print $2 }'\'') && \ | ||
ROCK="$\{NAME\}_$\{VERSION\}_$\{ARCH\}" && \ | ||
sudo skopeo --insecure-policy copy oci-archive:$ROCK.rock docker-daemon:$ROCK:$VERSION' | ||
# run rock tests | ||
pytest -vvv --tb native --show-capture=all --log-cli-level=INFO {posargs} {toxinidir}/tests | ||
[testenv:integration] | ||
passenv = * | ||
allowlist_externals = | ||
bash | ||
git | ||
rm | ||
tox | ||
rockcraft | ||
sed | ||
deps = | ||
juju~=2.9.0 | ||
pytest | ||
pytest-operator | ||
ops | ||
commands = | ||
# build and pack rock | ||
rockcraft pack | ||
# clone related charm | ||
rm -rf {env:LOCAL_CHARM_DIR} | ||
git clone --branch {env:CHARM_BRANCH} {env:CHARM_REPO} {env:LOCAL_CHARM_DIR} | ||
# replace jinja2 templated value with yq safe placeholder | ||
sed -i "s/namespace: {{ namespace }}/namespace: YQ_SAFE/" {env:LOCAL_CHARM_DIR}/src/templates/configmap.yaml.j2 | ||
# upload rock to docker and microk8s cache, replace charm's container with local rock reference | ||
bash -c 'NAME=$(yq eval .name rockcraft.yaml) && \ | ||
VERSION=$(yq eval .version rockcraft.yaml) && \ | ||
ARCH=$(yq eval ".platforms | keys" rockcraft.yaml | awk -F " " '\''{ print $2 }'\'') && \ | ||
ROCK="$\{NAME\}_$\{VERSION\}_$\{ARCH\}" && \ | ||
sudo skopeo --insecure-policy copy oci-archive:$ROCK.rock docker-daemon:$ROCK:$VERSION && \ | ||
docker save $ROCK > $ROCK.tar && \ | ||
microk8s ctr image import $ROCK.tar --digests=true && \ | ||
predictor_servers=$(yq e ".data.predictor_servers" {env:LOCAL_CHARM_DIR}/src/templates/configmap.yaml.j2) && \ | ||
predictor_servers=$(jq --arg jq_rock $ROCK -r '\''.XGBOOST_SERVER.protocols.v2.image=$jq_rock'\'' <<< $predictor_servers) && \ | ||
predictor_servers=$(jq --arg jq_version $VERSION -r '\''.XGBOOST_SERVER.protocols.v2.defaultImageVersion=$jq_version'\'' <<< $predictor_servers) yq e -i ".data.predictor_servers=strenv(predictor_servers)" {env:LOCAL_CHARM_DIR}/src/templates/configmap.yaml.j2' | ||
# replace yq safe placeholder with original value | ||
sed -i "s/namespace: YQ_SAFE/namespace: {{ namespace }}/" {env:LOCAL_CHARM_DIR}/src/templates/configmap.yaml.j2 | ||
# run charm integration test with rock | ||
tox -c {env:LOCAL_CHARM_DIR} -e integration | ||