Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-48081 : Refactor Server Daemon #151

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading