Skip to content

agiangrant/openstack-vm-api

Repository files navigation

OpenStack VM Lifecycle Management REST API

A production-oriented REST API for managing OpenStack virtual machine lifecycle operations, built with FastAPI, async SQLAlchemy, and PostgreSQL. The API models the VM lifecycle after OpenStack Nova's state machine, providing a complete set of operations for creating, managing, and destroying virtual machines along with supporting resources like snapshots, volumes, and networks.

Features

  • JWT Authentication -- Register, login, token refresh, and protected endpoints with access + refresh token flow
  • Virtual Machine Management -- Full CRUD with async lifecycle operations (create, start, stop, reboot, resize, delete)
  • VM State Machine -- Deterministic state transitions modeled after OpenStack Nova, with task state tracking
  • Snapshots -- Create, list, and restore VM snapshots with async processing
  • Block Storage Volumes -- Create volumes and attach/detach them to VMs
  • Networks -- Pre-seeded network catalog with interface attachment (MAC/IP auto-generation)
  • Flavors -- Read-only instance type catalog (vCPUs, RAM, disk)
  • Pagination -- Consistent paginated list responses with total counts
  • Background Task Processing -- Async operations return 202 Accepted with state polling
  • Mock OpenStack Backend -- Pluggable backend abstraction with a mock implementation for local development
  • OpenAPI Documentation -- Auto-generated interactive docs at /api/v1/docs

Quick Start

Docker Compose (recommended)

git clone https://github.com/your-org/openstack-vm-api.git
cd openstack-vm-api
docker-compose up --build

The API will be available at http://localhost:8000. Interactive API docs are at http://localhost:8000/api/v1/docs.

Database migrations run automatically on startup. The compose stack includes:

  • api -- FastAPI application with hot reload
  • db -- PostgreSQL 16 (Alpine) with health checks

Verify It Works

# Health check
curl http://localhost:8000/api/v1/health

# Register a user
curl -X POST http://localhost:8000/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username": "demo", "email": "demo@example.com", "password": "SecurePass123"}'

# Login and get tokens
curl -X POST http://localhost:8000/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "demo", "password": "SecurePass123"}'

# Create a VM (use access_token from login response)
curl -X POST http://localhost:8000/api/v1/vms \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-server", "flavor_id": "<flavor_uuid>"}'

# Poll VM status until ACTIVE
curl http://localhost:8000/api/v1/vms/<vm_id> \
  -H "Authorization: Bearer <access_token>"

Development Setup

Prerequisites

  • Python 3.13+
  • uv (package manager)
  • PostgreSQL 16+ (or use Docker Compose for the database)

Local Development

# Install dependencies
uv sync

# Copy and configure environment
cp .env.example .env
# Edit .env with your database URL and secret key

# Run database migrations
uv run alembic upgrade head

# Start the development server
uv run uvicorn app.main:app --reload --port 8000

Environment Variables

Variable Default Description
APP_NAME OpenStack VM API Application display name
DEBUG false Enable debug mode
LOG_LEVEL INFO Logging level (DEBUG, INFO, WARNING, ERROR)
DATABASE_URL postgresql+asyncpg://postgres:postgres@localhost:5432/vmapi Async PostgreSQL connection string
DB_POOL_SIZE 10 SQLAlchemy connection pool size
DB_MAX_OVERFLOW 20 Max overflow connections beyond pool size
DB_ECHO false Log all SQL statements
SECRET_KEY change-me-in-production JWT signing secret (change this!)
ALGORITHM HS256 JWT signing algorithm
ACCESS_TOKEN_EXPIRE_MINUTES 30 Access token TTL
REFRESH_TOKEN_EXPIRE_DAYS 7 Refresh token TTL
MOCK_BUILD_DELAY 5.0 Simulated VM build time (seconds)
MOCK_START_DELAY 2.0 Simulated VM start time
MOCK_STOP_DELAY 2.0 Simulated VM stop time
MOCK_REBOOT_DELAY 3.0 Simulated VM reboot time
MOCK_RESIZE_DELAY 5.0 Simulated VM resize time
MOCK_DELETE_DELAY 2.0 Simulated VM delete time
MOCK_SNAPSHOT_DELAY 4.0 Simulated snapshot creation time
MOCK_ERROR_RATE 0.0 Probability of simulated operation failure (0.0-1.0)
DEFAULT_PAGE_SIZE 20 Default pagination page size
MAX_PAGE_SIZE 100 Maximum allowed page size

API Endpoints

All endpoints are prefixed with /api/v1/. JWT authentication is required unless noted otherwise.

Health

Method Path Description Auth
GET /health Health check No

Authentication

Method Path Description Auth
POST /auth/register Register a new user No
POST /auth/login Login and get JWT tokens No
POST /auth/refresh Refresh access token No
GET /auth/me Get current user profile Yes

Virtual Machines

Method Path Description Auth
POST /vms Create a VM (returns 202) Yes
GET /vms List VMs (paginated, filterable by status/name) Yes
GET /vms/{id} Get VM details Yes
DELETE /vms/{id} Delete a VM (returns 204) Yes
POST /vms/{id}/actions/start Start a stopped VM (returns 202) Yes
POST /vms/{id}/actions/stop Stop a running VM (returns 202) Yes
POST /vms/{id}/actions/reboot Reboot a VM (returns 202) Yes
POST /vms/{id}/actions/resize Resize a VM to new flavor (returns 202) Yes
POST /vms/{id}/actions/confirm-resize Confirm a pending resize Yes
POST /vms/{id}/actions/revert-resize Revert a pending resize Yes

Flavors

Method Path Description Auth
GET /flavors List available flavors Yes
GET /flavors/{id} Get flavor details Yes

Snapshots

Method Path Description Auth
POST /vms/{id}/snapshots Create a snapshot (returns 202) Yes
GET /vms/{id}/snapshots List snapshots for a VM (paginated) Yes
GET /vms/{id}/snapshots/{snap_id} Get snapshot details Yes
DELETE /vms/{id}/snapshots/{snap_id} Delete a snapshot Yes
POST /vms/{id}/snapshots/{snap_id}/restore Restore VM from snapshot (returns 202) Yes

Volumes

Method Path Description Auth
POST /volumes Create a volume Yes
GET /volumes List volumes (paginated) Yes
GET /volumes/{id} Get volume details Yes
DELETE /volumes/{id} Delete a volume Yes
POST /vms/{id}/volumes Attach a volume to a VM Yes
DELETE /vms/{id}/volumes/{vol_id} Detach a volume from a VM Yes
GET /vms/{id}/volumes List volume attachments for a VM Yes

Networks

Method Path Description Auth
GET /networks List available networks Yes
GET /networks/{id} Get network details Yes
POST /vms/{id}/interfaces Attach a network interface to a VM Yes
DELETE /vms/{id}/interfaces/{iface_id} Detach a network interface Yes
GET /vms/{id}/interfaces List interfaces for a VM Yes

VM State Machine

The VM lifecycle follows a deterministic state machine modeled after OpenStack Nova. Async operations set a task_state and return 202 Accepted; clients poll GET /vms/{id} for the final state.

                                +---------+
                    +---------->|  ERROR  |<----------+
                    |           +---------+           |
                    | build_failed    resize_failed   |
                    |                                 |
               +---------+                      +-----------+
  create ----->| BUILDING|                      |  RESIZING  |
               +---------+                      +-----------+
                    |                                 ^
                    | build_complete           resize |
                    v                                 |
               +---------+    stop    +---------+     |
               |  ACTIVE |----------->| SHUTOFF |     |
               +---------+            +---------+     |
                ^  ^  ^  ^                 |          |
                |  |  |  |           start |          |
                |  |  |  +<----------------+          |
                |  |  |                               |
                |  |  +--- confirm_resize ---+        |
                |  |                         |        |
                |  +--- revert_resize ---+   |        |
                |                        |   |        |
                |  reboot_complete  +----------+      |
                |                   |  VERIFY  |------+
                +<---+              |  RESIZE  |
                     |              +----------+
               +-----------+
               | REBOOTING |
               +-----------+

  delete (from ACTIVE, SHUTOFF, ERROR, BUILDING, VERIFY_RESIZE)
                    |
                    v
               +-----------+    delete_complete   +---------+
               |  DELETING |--------------------->| DELETED |
               +-----------+                      +---------+

Key concepts:

  • status -- The stable VM state (ACTIVE, SHUTOFF, ERROR, etc.)
  • task_state -- Transient operation indicator (spawning, powering_on, etc.); null when the VM is in a stable state
  • Async operations (create, start, stop, reboot, resize, delete) return immediately with 202 Accepted
  • Background tasks simulate processing delays, then update status and clear task_state
  • Invalid transitions return 409 Conflict with allowed actions listed

Testing

# Run the full test suite with coverage
uv run pytest --cov=app

# Run specific test modules
uv run pytest tests/test_auth.py -v
uv run pytest tests/test_vms.py -v

# Run with verbose output
uv run pytest -v --tb=short

The test suite uses:

  • pytest-asyncio for async test support
  • httpx.AsyncClient for integration tests against the FastAPI app
  • SQLite in-memory database for test isolation
  • Session-scoped event loop for performance

Linting & Formatting

# Check for lint errors
uv run ruff check src/ tests/

# Auto-fix lint issues
uv run ruff check --fix src/ tests/

# Check formatting
uv run ruff format --check src/ tests/

# Auto-format
uv run ruff format src/ tests/

Ruff is configured in pyproject.toml with select rules for:

  • E, F -- pycodestyle and pyflakes
  • I -- isort import sorting
  • N -- pep8-naming
  • UP -- pyupgrade
  • B -- bugbear
  • SIM -- simplify
  • RUF -- Ruff-specific rules

Project Structure

openstack-vm-api/
├── pyproject.toml              # Project metadata, dependencies, tool config
├── uv.lock                     # Lockfile
├── Dockerfile                  # Multi-stage container build
├── docker-compose.yml          # API + PostgreSQL stack
├── alembic.ini                 # Alembic migration config
├── .env.example                # Environment variable template
├── docs/
│   ├── architecture.md         # Architecture deep-dive
│   └── adr/                    # Architecture Decision Records
│       ├── 001-framework-selection.md
│       ├── 002-mock-backend.md
│       └── 003-background-tasks.md
├── alembic/
│   ├── env.py                  # Async Alembic environment
│   └── versions/               # Migration scripts
├── src/app/
│   ├── main.py                 # App factory, lifespan, router registration
│   ├── config.py               # Pydantic Settings (env-based config)
│   ├── database.py             # Async engine, session factory, get_db
│   ├── auth/                   # Authentication domain
│   │   ├── router.py           # Auth endpoints
│   │   ├── schemas.py          # Pydantic request/response models
│   │   ├── models.py           # User SQLAlchemy model
│   │   ├── service.py          # Auth business logic (JWT, bcrypt)
│   │   ├── dependencies.py     # get_current_user dependency
│   │   └── exceptions.py       # Auth-specific errors
│   ├── vms/                    # Virtual machine domain
│   │   ├── router.py           # VM endpoints
│   │   ├── schemas.py          # VM request/response models
│   │   ├── models.py           # VirtualMachine model + VMStatus enum
│   │   ├── service.py          # VM business logic
│   │   ├── state_machine.py    # VMStateMachine transition table
│   │   ├── dependencies.py     # Backend injection
│   │   └── exceptions.py       # VM-specific errors
│   ├── flavors/                # Instance type catalog
│   ├── snapshots/              # VM snapshots
│   ├── volumes/                # Block storage + attachments
│   ├── networks/               # Networks + interfaces
│   ├── backend/
│   │   ├── base.py             # AbstractComputeBackend (ABC)
│   │   └── mock.py             # MockOpenStackBackend
│   └── common/
│       ├── models.py           # Base SQLAlchemy model (UUID PK, timestamps)
│       ├── schemas.py          # Shared Pydantic models (pagination, errors)
│       └── exceptions.py       # Exception hierarchy + handler
└── tests/                      # Test suite (mirrors src structure)
    ├── conftest.py             # Fixtures (async client, test DB, factories)
    └── test_*.py               # Test modules

Architecture

For a detailed architecture writeup covering design decisions, module structure, state machine design, authentication flow, and the production roadmap, see docs/architecture.md.

Architecture Decision Records (ADRs):

License

MIT

About

API for OpenStack VM management

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages