Skip to content

Commit

Permalink
Merge pull request #151 from lsst-dm/tickets/DM-48081
Browse files Browse the repository at this point in the history
DM-48081 : Refactor Server Daemon
  • Loading branch information
tcjennings authored Jan 14, 2025
2 parents 74e8c65 + f8ca007 commit 016065a
Show file tree
Hide file tree
Showing 54 changed files with 803 additions and 703 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
# Except
!pyproject.toml
!uv.lock
!alembic.ini
!src/lsst/
!examples/
!alembic/
38 changes: 4 additions & 34 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: "CI"
"on":

on:
push:
pull_request:
branches:
Expand Down Expand Up @@ -42,7 +43,7 @@ jobs:
python-version: ${{ matrix.python-version }}

- name: Install packages for testing
run: uv sync --dev
run: uv sync --dev --frozen

- name: Run tests
run: |
Expand All @@ -66,39 +67,8 @@ jobs:
python-version: "3.11"

- name: Install packages for testing
run: uv sync --dev
run: uv sync --dev --frozen

- name: Run tests
run: |
uv run make typing
build:
runs-on: ubuntu-latest
needs: [test]
timeout-minutes: 20

# Only do Docker builds of tagged releases and pull requests from ticket
# branches. This will still trigger on pull requests from untrusted
# repositories whose branch names match our tickets/* branch convention,
# but in this case the build will fail with an error since the secret
# won't be set.
if: >
github.event_name != 'merge_group'
&& (startsWith(github.ref, 'refs/tags/')
|| startsWith(github.head_ref, 'tickets/'))
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: lsst-sqre/build-and-push-to-ghcr@v1
id: build-service
with:
dockerfile: docker/Dockerfile
image: ${{ github.repository }}
github_token: ${{ secrets.GITHUB_TOKEN }}

- name: Report result
run: |
echo Pushed ghcr.io/${{ github.repository }}:${{ steps.build-service.outputs.tag }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ test_cm.db

build/
bruno/
prod_area/
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ psql: run-compose
test: PGPORT=$(shell docker compose port postgresql 5432 | cut -d: -f2)
test: export DB__URL=postgresql://cm-service@localhost:${PGPORT}/cm-service
test: export DB__PASSWORD=INSECURE-PASSWORD
test: export DB__SCHEMA=cm_service_test
test: export DB__TABLE_SCHEMA=cm_service_test
test: run-compose
alembic upgrade head
pytest -vvv --asyncio-mode=auto --cov=lsst.cmservice --cov-branch --cov-report=term --cov-report=html ${PYTEST_ARGS}
Expand All @@ -110,7 +110,7 @@ run: export DB__PASSWORD=INSECURE-PASSWORD
run: export DB__ECHO=true
run: run-compose
alembic upgrade head
python3 -m lsst.cmservice.cli.server run
python3 -m lsst.cmservice.main

.PHONY: run-worker
run-worker: PGPORT=$(shell docker compose port postgresql 5432 | cut -d: -f2)
Expand Down Expand Up @@ -148,7 +148,7 @@ run-sqlite: export DB__URL=sqlite+aiosqlite://///test_cm.db
run-sqlite: export DB__ECHO=true
run-sqlite:
alembic -x cm_database_url=sqlite:///test_cm.db upgrade head
python3 -m lsst.cmservice.cli.server run
python3 -m lsst.cmservice.main

.PHONY: run-worker-sqlite
run-worker-sqlite: export DB__URL=sqlite+aiosqlite://///test_cm.db
Expand Down Expand Up @@ -191,7 +191,7 @@ run-usdf-dev: export DB__PASSWORD=$(shell kubectl --cluster=usdf-cm-dev -n cm-se
run-usdf-dev: export DB__ECHO=true
run-usdf-dev:
alembic upgrade head
python3 -m lsst.cmservice.cli.server run
python3 -m lsst.cmservice.main

.PHONY: run-worker-usdf-dev
run-worker-usdf-dev: DB__HOST=$(shell kubectl --cluster=usdf-cm-dev -n cm-service get svc/cm-pg-lb -o jsonpath='{..ingress[0].ip}')
Expand Down
46 changes: 28 additions & 18 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ services:
build:
context: .
dockerfile: docker/Dockerfile
target: cmservice
entrypoint:
- python3
- -m
- alembic
command:
- lsst.cmservice.cli.server
- init
- upgrade
- head
environment: &cmenv
DB__HOST: postgresql
DB__ECHO: true
DB__URL: postgresql://cm-service@postgresql:5432/cm-service
DB__PASSWORD: INSECURE-PASSWORD
DB__TABLE_SCHEMA: public
networks:
- cmservice
depends_on:
postgresql:
condition: service_healthy
Expand All @@ -33,17 +36,15 @@ services:
build:
context: .
dockerfile: docker/Dockerfile
entrypoint:
- uvicorn
command:
- lsst.cmservice.main:app
- --host
- "0.0.0.0"
- --port
- "8080"
target: cmservice
env_file:
- path: .env
required: false
environment: *cmenv
ports:
- "8080:8080"
networks:
- cmservice
depends_on:
init-db:
condition: service_completed_successfully
Expand All @@ -55,12 +56,15 @@ services:
build:
context: .
dockerfile: docker/Dockerfile
entrypoint:
- /opt/venv/bin/python3
- -m
command:
- lsst.cmservice.daemon
target: cmworker
env_file:
- path: .env
required: false
environment: *cmenv
volumes:
- "./prod_area:/prod_area"
networks:
- cmservice
depends_on:
init-db:
condition: service_completed_successfully
Expand All @@ -73,7 +77,9 @@ services:
POSTGRES_USER: "cm-service"
POSTGRES_DB: "cm-service"
ports:
- "5432"
- "65432:5432"
networks:
- cmservice
volumes:
- "pgsql:/var/lib/postgresql/data"
healthcheck:
Expand All @@ -87,3 +93,7 @@ services:

volumes:
pgsql:

networks:
cmservice:
driver: bridge
57 changes: 35 additions & 22 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
# syntax=docker/dockerfile:1

ARG PYTHON_VERSION=3.11
ARG UV_VERSION=0.5
ARG PYTHON_VERSION="3.11"
ARG UV_VERSION="0.5"
ARG ASGI_PORT="8080"

#==============================================================================
# UV SOURCE IMAGE
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv


#==============================================================================
# HTCONDOR SOURCE IMAGE
# - The easiest way to obtain an architecture-appropriate set of htcondor client
# binaries is to install them into an otherwise bare conda environment.
FROM continuumio/miniconda3:latest AS htcondor
RUN conda create -p /opt/htcondor -c conda-forge htcondor-utils --no-default-packages


#==============================================================================
# BASE IMAGE
# - The base image is an updated debian python container with a nonroot user
# created. This can be extended into include any *runtime* system dependencies
# during the apt-get step.
FROM python:${PYTHON_VERSION}-slim-bookworm AS base-image
ARG ASGI_PORT

# Upgrade base packages
RUN <<ENDRUN
Expand All @@ -34,6 +47,9 @@ groupadd --gid 3967 lsstsvc1
useradd lsstsvc1 --uid 17951 --no-user-group --gid gu --groups rubin-users,lsst,lsstsvc1 --create-home --shell /bin/bash
ENDRUN

# Expose the API port
EXPOSE ${ASGI_PORT}


#==============================================================================
# BUILDER IMAGE
Expand Down Expand Up @@ -69,11 +85,8 @@ ENDRUN


#==============================================================================
# RUNTIME IMAGE
FROM base-image AS runtime-image

# Expose the API port
EXPOSE 8080
# RUNTIME IMAGE - CM-Service
FROM base-image AS cmservice

# Make sure frontend output won't go into the layer store
VOLUME /output
Expand All @@ -91,19 +104,19 @@ WORKDIR /home/lsstsvc1
ENV CM_CONFIGS=/home/lsstsvc1/examples
COPY examples ./examples
COPY src/lsst ./lsst
COPY alembic ./alembic
COPY alembic.ini .

# Run the Server
# TODO refactor module main to start unicorn instead of using a bash script
# after eliminating db migration requirement here
COPY --chmod=755 <<EOF ./docker-entrypoint.sh
#!/bin/bash
python -m lsst.cmservice.cli.server init
uvicorn "\$@"
EOF

# Both the entrypoint and cmd can be disambiguated in the Helm chart / Compose file
ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["lsst.cmservice.main:app", "--host", "0.0.0.0", "--port", "8080"]

# ENTRYPOINT ["/opt/venv/bin/python3", "-m"]
# CMD ["lsst.cmservice.daemon"]
ENTRYPOINT ["/opt/venv/bin/python3", "-m"]
CMD ["lsst.cmservice.main"]


#==============================================================================
# RUNTIME IMAGE - Daemon
FROM cmservice AS cmworker

USER ROOT
COPY --from=htcondor /opt/htcondor /opt/htcondor

USER lsstsvc1
CMD ["lsst.cmservice.daemon"]
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dynamic = ["version"]

dependencies = [
"alembic==1.14.*",
"anyio==4.7.*",
"asyncpg==0.30.*",
"click==8.1.*",
"fastapi==0.115.*",
Expand All @@ -37,9 +38,8 @@ dependencies = [
"pause==0.3.*",
"psycopg2-binary==2.9.*",
"pydantic==2.10.*",
"pydantic-settings==2.6.*",
"pydantic-settings==2.7.*",
"python-multipart==0.0.*",
"starlette==0.41.*",
"structlog==24.4.*",
"tabulate==0.9.*",
"sqlalchemy[asyncio]==2.0.*",
Expand All @@ -61,7 +61,7 @@ dev = [
"pytest-cov>=6.0.0",
"pytest-playwright>=0.5.2",
"pytest-timeout>=2.3.1",
"ruff>=0.7.4",
"ruff>=0.8.0",
"python-semantic-release==9.14.*",
"sqlalchemy[mypy]>=2.0.36",
"types-pyyaml>=6.0.12.20240917",
Expand Down
2 changes: 0 additions & 2 deletions src/lsst/cmservice/cli/campaign.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,6 @@ def action() -> None:

action_retry_script = wrappers.get_element_retry_script_command(action_command, sub_client)

get_sleep_time = wrappers.get_element_estimate_sleep_time_command(action_command, sub_client)

get_wms_task_reports = wrappers.get_element_wms_task_reports_command(get_command, sub_client)

get_tasks = wrappers.get_element_tasks_command(get_command, sub_client)
Expand Down
2 changes: 0 additions & 2 deletions src/lsst/cmservice/cli/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ def action() -> None:

action_retry_script = wrappers.get_element_retry_script_command(action_command, sub_client)

get_sleep_time = wrappers.get_element_estimate_sleep_time_command(action_command, sub_client)

get_wms_task_reports = wrappers.get_element_wms_task_reports_command(get_command, sub_client)

get_tasks = wrappers.get_element_tasks_command(get_command, sub_client)
Expand Down
2 changes: 0 additions & 2 deletions src/lsst/cmservice/cli/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,6 @@ def action() -> None:

action_retry_script = wrappers.get_element_retry_script_command(action_command, sub_client)

get_sleep_time = wrappers.get_element_estimate_sleep_time_command(action_command, sub_client)

get_wms_task_reports = wrappers.get_element_wms_task_reports_command(get_command, sub_client)

get_tasks = wrappers.get_element_tasks_command(get_command, sub_client)
Expand Down
22 changes: 16 additions & 6 deletions src/lsst/cmservice/cli/server.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import warnings

import click
import uvicorn
from safir.asyncio import run_with_asyncio

from .. import __version__
Expand All @@ -9,7 +10,11 @@
@click.group()
@click.version_option(version=__version__)
def server() -> None:
"""Administrative command-line interface for cm-service."""
"""Administrative command-line interface for cm-service.
.. deprecated:: v0.2.0
The `server` command is deprecated in v0.2.0
"""


@server.command(deprecated=True)
Expand All @@ -18,17 +23,22 @@ def server() -> None:
async def init(*, reset: bool) -> None: # pragma: no cover
"""Initialize the service database.
.. deprecated:: v1.5.0
.. deprecated:: v0.2.0
The `init` command is deprecated in v0.2.0; it is replaced by alembic.
"""
print("Use `alembic upgrade head` instead.")
warnings.warn("Use `alembic upgrade head` instead.", DeprecationWarning)


@server.command()
@click.option("--port", default=8080, type=int, help="Port to run the application on.")
def run(port: int) -> None: # pragma: no cover
"""Run the service application (for testing only)."""
uvicorn.run("lsst.cmservice.main:app", host="0.0.0.0", port=port, reload=True, reload_dirs=["src"])
"""Run the service application (for testing only).
.. deprecated:: v0.2.0
The `run` command is deprecated in v0.2.0. Launch the server with a
module entrypoint instead.
"""
warnings.warn("Use `python3 -m lsst.cmservice.main` instead.", DeprecationWarning)


# Build the client CLI
Expand Down
2 changes: 0 additions & 2 deletions src/lsst/cmservice/cli/step.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,6 @@ def action() -> None:

action_retry_script = wrappers.get_element_retry_script_command(action_command, sub_client)

get_sleep_time = wrappers.get_element_estimate_sleep_time_command(action_command, sub_client)

get_wms_task_reports = wrappers.get_element_wms_task_reports_command(get_command, sub_client)

get_tasks = wrappers.get_element_tasks_command(get_command, sub_client)
Expand Down
Loading

0 comments on commit 016065a

Please sign in to comment.