Compare commits

..

No commits in common. "6dbd39b76ceb7af37fe987888f3e6ae2e715ff62" and "0c5fb7566ca8f4eff7dfb79a2781ddc372ca3be3" have entirely different histories.

13 changed files with 146 additions and 239 deletions

View File

@ -3,7 +3,7 @@
WORKING_DIR=/usr/share/fastapi-dls WORKING_DIR=/usr/share/fastapi-dls
CONFIG_DIR=/etc/fastapi-dls CONFIG_DIR=/etc/fastapi-dls
if [ ! -f $CONFIG_DIR/instance.private.pem ]; then if [[ ! -f $CONFIG_DIR/instance.private.pem ]]; then
echo "> Create dls-instance keypair ..." echo "> Create dls-instance keypair ..."
openssl genrsa -out $CONFIG_DIR/instance.private.pem 2048 openssl genrsa -out $CONFIG_DIR/instance.private.pem 2048
openssl rsa -in $CONFIG_DIR/instance.private.pem -outform PEM -pubout -out $CONFIG_DIR/instance.public.pem openssl rsa -in $CONFIG_DIR/instance.private.pem -outform PEM -pubout -out $CONFIG_DIR/instance.public.pem
@ -12,8 +12,8 @@ else
fi fi
while true; do while true; do
[ -f $CONFIG_DIR/webserver.key ] && default_answer="N" || default_answer="Y" [[ -f $CONFIG_DIR/webserver.key ]] && default_answer="N" || default_answer="Y"
[ $default_answer == "Y" ] && V="Y/n" || V="y/N" [[ $default_answer == "Y" ]] && V="Y/n" || V="y/N"
read -p "> Do you wish to create self-signed webserver certificate? [${V}]" yn read -p "> Do you wish to create self-signed webserver certificate? [${V}]" yn
yn=${yn:-$default_answer} # ${parameter:-word} If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted. yn=${yn:-$default_answer} # ${parameter:-word} If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted.
case $yn in case $yn in
@ -27,7 +27,7 @@ while true; do
esac esac
done done
if [ -f $CONFIG_DIR/webserver.key ]; then if [[ -f $CONFIG_DIR/webserver.key ]]; then
echo "> Starting service ..." echo "> Starting service ..."
systemctl start fastapi-dls.service systemctl start fastapi-dls.service

View File

@ -1,9 +1,8 @@
#!/bin/bash #!/bin/bash
# is removed automatically if [[ -f /etc/systemd/system/fastapi-dls.service ]]; then
#if [ "$1" = purge ] && [ -d /usr/share/fastapi-dls ]; then echo "> Removing service file."
# echo "> Removing app." rm /etc/systemd/system/fastapi-dls.service
# rm -r /usr/share/fastapi-dls fi
#fi
echo -e "> Done." # todo

View File

@ -1,3 +1,5 @@
#!/bin/bash #!/bin/bash
echo -e "> Starting uninstallation of 'fastapi-dls'!" echo -e "> Starting uninstallation of 'fastapi-dls'!"
# todo

View File

@ -50,7 +50,7 @@ build:apt:
- cp .DEBIAN/env.default build/etc/fastapi-dls/env - cp .DEBIAN/env.default build/etc/fastapi-dls/env
# create service file # create service file
- mkdir -p build/etc/systemd/system - mkdir -p build/etc/systemd/system
- cp .DEBIAN/fastapi-dls.service build/etc/systemd/system/fastapi-dls.service - cp .DEBIAN/fastapi-dls.service build/etc/systemd/system
# cd into "build/" # cd into "build/"
- cd build/ - cd build/
script: script:
@ -98,7 +98,7 @@ build:pacman:
- "*.pkg.tar.zst" - "*.pkg.tar.zst"
test: test:
image: python:3.11-slim-bullseye image: python:3.10-slim-bullseye
stage: test stage: test
rules: rules:
- if: $CI_COMMIT_BRANCH - if: $CI_COMMIT_BRANCH
@ -114,9 +114,6 @@ test:
- cd test - cd test
script: script:
- pytest main.py - pytest main.py
artifacts:
reports:
dotenv: version.env
.test:linux: .test:linux:
stage: test stage: test
@ -275,11 +272,24 @@ deploy:pacman:
- 'echo "EXPORT_NAME: ${EXPORT_NAME}"' - 'echo "EXPORT_NAME: ${EXPORT_NAME}"'
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${EXPORT_NAME} "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/${EXPORT_NAME}"' - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${EXPORT_NAME} "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/${EXPORT_NAME}"'
release:prepare:
stage: .pre
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- source version.env
- echo &VERSION
artifacts:
reports:
dotenv: version.env
release: release:
image: registry.gitlab.com/gitlab-org/release-cli:latest image: registry.gitlab.com/gitlab-org/release-cli:latest
stage: .post stage: .post
needs: needs:
- job: test - job: release:prepare
artifacts: true artifacts: true
rules: rules:
- if: $CI_COMMIT_TAG - if: $CI_COMMIT_TAG
@ -288,7 +298,7 @@ release:
script: script:
- echo "Running release-job for $VERSION" - echo "Running release-job for $VERSION"
release: release:
name: $CI_PROJECT_TITLE $VERSION name: $CI_PROJECT_TITLE $version
description: Release of $CI_PROJECT_TITLE version $VERSION description: Release of $CI_PROJECT_TITLE version $VERSION
tag_name: $VERSION tag_name: $VERSION
ref: $CI_COMMIT_SHA ref: $CI_COMMIT_SHA

View File

@ -1,4 +1,4 @@
FROM python:3.11-alpine FROM python:3.10-alpine
COPY requirements.txt /tmp/requirements.txt COPY requirements.txt /tmp/requirements.txt

17
FAQ.md
View File

@ -1,17 +0,0 @@
# FAQ
## `Failed to acquire license from <ip> (Info: <license> - Error: The allowed time to process response has expired)`
- Did your timezone settings are correct on fastapi-dls **and your guest**?
- Did you download the client-token more than an hour ago?
Please download a new client-token. The guest have to register within an hour after client-token was created.
## `jose.exceptions.JWTError: Signature verification failed.`
- Did you recreated `instance.public.pem` / `instance.private.pem`?
Then you have to download a **new** client-token on each of your guests.

220
README.md
View File

@ -9,6 +9,68 @@ Only the clients need a connection to this service on configured port.
[[_TOC_]] [[_TOC_]]
## ToDo's
- check why windows guests display "can't acquire license" although in log there is no message displayed and license is
also acquired successfully
## Endpoints
### `GET /`
Redirect to `/-/readme`.
### `GET /-/health`
Status endpoint, used for *healthcheck*.
### `GET /-/config`
Shows current runtime environment variables and their values.
### `GET /-/readme`
HTML rendered README.md.
### `GET /-/docs`, `GET /-/redoc`
OpenAPI specifications rendered from `GET /-/openapi.json`.
### `GET /-/manage`
Shows a very basic UI to delete origins or leases.
### `GET /-/origins?leases=false`
List registered origins.
| Query Parameter | Default | Usage |
|-----------------|---------|--------------------------------------|
| `leases` | `false` | Include referenced leases per origin |
### `DELETE /-/origins`
Deletes all origins and their leases.
### `GET /-/leases?origin=false`
List current leases.
| Query Parameter | Default | Usage |
|-----------------|---------|-------------------------------------|
| `origin` | `false` | Include referenced origin per lease |
### `DELETE /-/lease/{lease_ref}`
Deletes an lease.
### `GET /-/client-token`
Generate client token, (see [installation](#installation)).
### Others
There are some more internal api endpoints for handling authentication and lease process.
# Setup (Service) # Setup (Service)
@ -34,8 +96,6 @@ openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $WORKING_DIR/webse
**Start container** **Start container**
To test if everything is set up properly you can start container as following:
```shell ```shell
docker volume create dls-db docker volume create dls-db
docker run -e DLS_URL=`hostname -i` -e DLS_PORT=443 -p 443:443 -v $WORKING_DIR:/app/cert -v dls-db:/app/database collinwebdesigns/fastapi-dls:latest docker run -e DLS_URL=`hostname -i` -e DLS_PORT=443 -p 443:443 -v $WORKING_DIR:/app/cert -v dls-db:/app/database collinwebdesigns/fastapi-dls:latest
@ -43,7 +103,7 @@ docker run -e DLS_URL=`hostname -i` -e DLS_PORT=443 -p 443:443 -v $WORKING_DIR:/
**Docker-Compose / Deploy stack** **Docker-Compose / Deploy stack**
Goto [`docker-compose.yml`](docker-compose.yml) for more advanced example (with reverse proxy usage). Goto [`docker-compose.yml`](docker-compose.yml) for more advanced example.
```yaml ```yaml
version: '3.9' version: '3.9'
@ -70,7 +130,7 @@ volumes:
dls-db: dls-db:
``` ```
## Debian/Ubuntu (manual method using `git clone` and python virtual environment) ## Debian/Ubuntu (manual method using `git clone`)
Tested on `Debian 11 (bullseye)`, Ubuntu may also work. Tested on `Debian 11 (bullseye)`, Ubuntu may also work.
@ -175,11 +235,6 @@ Successful tested with:
- Debian 12 (Bookworm) (works but not recommended because it is currently in *testing* state) - Debian 12 (Bookworm) (works but not recommended because it is currently in *testing* state)
- Ubuntu 22.10 (Kinetic Kudu) - Ubuntu 22.10 (Kinetic Kudu)
Not working with:
- Debian 11 (Bullseye) and lower (missing `python-jose` dependency)
- Ubuntu 22.04 (Jammy Jellyfish) (not supported as for 15.01.2023 due to [fastapi - uvicorn version missmatch](https://bugs.launchpad.net/ubuntu/+source/fastapi/+bug/1970557))
**Run this on your server instance** **Run this on your server instance**
First go to [GitLab-Registry](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/packages) and select your First go to [GitLab-Registry](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/packages) and select your
@ -206,17 +261,13 @@ Packages are available here:
```shell ```shell
pacman -Sy pacman -Sy
FILENAME=/opt/fastapi-dls.pkg.tar.zst FILENAME=/opt/fastapi-dls.pkg.tar.zst
url -o $FILENAME <download-url>
curl -o $FILENAME <download-url>
# or
wget -O $FILENAME <download-url>
pacman -U --noconfirm fastapi-dls.pkg.tar.zst pacman -U --noconfirm fastapi-dls.pkg.tar.zst
``` ```
Start with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`. Start with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`.
## Let's Encrypt Certificate (optional) ## Let's Encrypt Certificate
If you're using installation via docker, you can use `traefik`. Please refer to their documentation. If you're using installation via docker, you can use `traefik`. Please refer to their documentation.
@ -247,8 +298,8 @@ After first success you have to replace `--issue` with `--renew`.
| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid | | `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid |
| `INSTANCE_REF` | `10000000-0000-0000-0000-000000000001` | Instance identification uuid | | `INSTANCE_REF` | `10000000-0000-0000-0000-000000000001` | Instance identification uuid |
| `ALLOTMENT_REF` | `20000000-0000-0000-0000-000000000001` | Allotment identification uuid | | `ALLOTMENT_REF` | `20000000-0000-0000-0000-000000000001` | Allotment identification uuid |
| `INSTANCE_KEY_RSA` | `<app-dir>/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs \*3 | | `INSTANCE_KEY_RSA` | `<app-dir>/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs |
| `INSTANCE_KEY_PUB` | `<app-dir>/cert/instance.public.pem` | Site-wide public key \*3 | | `INSTANCE_KEY_PUB` | `<app-dir>/cert/instance.public.pem` | Site-wide public key |
\*1 For example, if the lease period is one day and the renewal period is 20%, the client attempts to renew its license \*1 For example, if the lease period is one day and the renewal period is 20%, the client attempts to renew its license
every 4.8 hours. If network connectivity is lost, the loss of connectivity is detected during license renewal and the every 4.8 hours. If network connectivity is lost, the loss of connectivity is detected during license renewal and the
@ -256,8 +307,6 @@ client has 19.2 hours in which to re-establish connectivity before its license e
\*2 Always use `https`, since guest-drivers only support secure connections! \*2 Always use `https`, since guest-drivers only support secure connections!
\*3 If you recreate instance keys you need to **recreate client-token for each guest**!
# Setup (Client) # Setup (Client)
**The token file has to be copied! It's not enough to C&P file contents, because there can be special characters.** **The token file has to be copied! It's not enough to C&P file contents, because there can be special characters.**
@ -270,128 +319,27 @@ Successfully tested with this package versions:
## Linux ## Linux
Download *client-token* and place it into `/etc/nvidia/ClientConfigToken`:
```shell
curl --insecure -L -X GET https://<dls-hostname-or-ip>/-/client-token -o /etc/nvidia/ClientConfigToken/client_configuration_token_$(date '+%d-%m-%Y-%H-%M-%S').tok
# or
wget --no-check-certificate -O /etc/nvidia/ClientConfigToken/client_configuration_token_$(date '+%d-%m-%Y-%H-%M-%S').tok https://<dls-hostname-or-ip>/-/client-token
```
Restart `nvidia-gridd` service:
```shell ```shell
curl --insecure -L -X GET https://<dls-hostname-or-ip>/client-token -o /etc/nvidia/ClientConfigToken/client_configuration_token_$(date '+%d-%m-%Y-%H-%M-%S').tok
service nvidia-gridd restart service nvidia-gridd restart
```
Check licensing status:
```shell
nvidia-smi -q | grep "License" nvidia-smi -q | grep "License"
``` ```
Output should be something like:
```text
vGPU Software Licensed Product
License Status : Licensed (Expiry: YYYY-M-DD hh:mm:ss GMT)
```
Done. For more information check [troubleshoot section](#troubleshoot).
## Windows ## Windows
**Power-Shell** (run as administrator!) Download file and place it into `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`.
Now restart `NvContainerLocalSystem` service.
Download *client-token* and place it into `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`: **Power-Shell**
```shell
curl.exe --insecure -L -X GET https://<dls-hostname-or-ip>/-/client-token -o "C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken\client_configuration_token_$($(Get-Date).tostring('dd-MM-yy-hh-mm-ss')).tok"
```
Restart `NvContainerLocalSystem` service:
```Shell ```Shell
curl.exe --insecure -L -X GET https://<dls-hostname-or-ip>/client-token -o "C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken\client_configuration_token_$($(Get-Date).tostring('dd-MM-yy-hh-mm-ss')).tok"
Restart-Service NVDisplay.ContainerLocalSystem Restart-Service NVDisplay.ContainerLocalSystem
'C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe' -q | Select-String "License"
``` ```
Check licensing status:
```shell
& 'C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe' -q | Select-String "License"
```
Output should be something like:
```text
vGPU Software Licensed Product
License Status : Licensed (Expiry: YYYY-M-DD hh:mm:ss GMT)
```
Done. For more information check [troubleshoot section](#troubleshoot).
# Endpoints
### `GET /`
Redirect to `/-/readme`.
### `GET /-/health`
Status endpoint, used for *healthcheck*.
### `GET /-/config`
Shows current runtime environment variables and their values.
### `GET /-/readme`
HTML rendered README.md.
### `GET /-/docs`, `GET /-/redoc`
OpenAPI specifications rendered from `GET /-/openapi.json`.
### `GET /-/manage`
Shows a very basic UI to delete origins or leases.
### `GET /-/origins?leases=false`
List registered origins.
| Query Parameter | Default | Usage |
|-----------------|---------|--------------------------------------|
| `leases` | `false` | Include referenced leases per origin |
### `DELETE /-/origins`
Deletes all origins and their leases.
### `GET /-/leases?origin=false`
List current leases.
| Query Parameter | Default | Usage |
|-----------------|---------|-------------------------------------|
| `origin` | `false` | Include referenced origin per lease |
### `DELETE /-/lease/{lease_ref}`
Deletes an lease.
### `GET /-/client-token`
Generate client token, (see [installation](#installation)).
### Others
There are many other internal api endpoints for handling authentication and lease process.
# Troubleshoot # Troubleshoot
**Please make sure that fastapi-dls and your guests are on the same timezone!**
## Linux ## Linux
Logs are available with `journalctl -u nvidia-gridd -f`. Logs are available with `journalctl -u nvidia-gridd -f`.
@ -410,9 +358,6 @@ This message can be ignored.
- Ref. https://github.com/encode/uvicorn/issues/441 - Ref. https://github.com/encode/uvicorn/issues/441
<details>
<summary>Log example</summary>
``` ```
WARNING:uvicorn.error:Invalid HTTP request received. WARNING:uvicorn.error:Invalid HTTP request received.
Traceback (most recent call last): Traceback (most recent call last):
@ -431,8 +376,6 @@ Traceback (most recent call last):
h11._util.RemoteProtocolError: no request line received h11._util.RemoteProtocolError: no request line received
``` ```
</details>
## Windows ## Windows
### Required cipher on Windows Guests (e.g. managed by domain controller with GPO) ### Required cipher on Windows Guests (e.g. managed by domain controller with GPO)
@ -500,13 +443,14 @@ Dec 20 17:53:34 ubuntu-grid-server nvidia-gridd[10354]: License acquired success
</details> </details>
### Error on releasing leases on shutdown (can be ignored and/or fixed with reverse proxy) ### Error on releasing leases on shutdown (fixed in 1.3 by using reverse proxy)
The driver wants to release current leases on shutting down windows. This endpoint needs to be a http endpoint. **UPDATE for version `1.3`**: This issue can be fixed by using a reverse proxy (e.g. `nginx`). Please read section
The error message can safely be ignored (since we have no license limitation :P) and looks like this: below.
<details> The driver wants to release current leases on shutting down windows. This endpoint needs to be a http endpoint and
<summary>Log example</summary> is currently not implemented. The error message looks like and safely can be ignored (since we have no license
limitation :P):
``` ```
<1>:NLS initialized <1>:NLS initialized
@ -515,7 +459,7 @@ The error message can safely be ignored (since we have no license limitation :P)
<0>:End Logging <0>:End Logging
``` ```
#### log with nginx as reverse proxy (see [docker-compose.yml](docker-compose.yml)) #### log with 1.3 and nginx as reverse proxy
``` ```
<1>:NLS initialized <1>:NLS initialized
@ -530,8 +474,6 @@ The error message can safely be ignored (since we have no license limitation :P)
<0>:End Logging <0>:End Logging
``` ```
</details>
# Credits # Credits
Thanks to vGPU community and all who uses this project and report bugs. Thanks to vGPU community and all who uses this project and report bugs.

View File

@ -9,7 +9,7 @@ from dotenv import load_dotenv
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.requests import Request from fastapi.requests import Request
from json import loads as json_loads from json import loads as json_loads
from datetime import datetime, timedelta from datetime import datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from calendar import timegm from calendar import timegm
from jose import jws, jwk, jwt, JWTError from jose import jws, jwk, jwt, JWTError
@ -50,7 +50,6 @@ INSTANCE_KEY_PUB = load_key(str(env('INSTANCE_KEY_PUB', join(dirname(__file__),
TOKEN_EXPIRE_DELTA = relativedelta(days=int(env('TOKEN_EXPIRE_DAYS', 1)), hours=int(env('TOKEN_EXPIRE_HOURS', 0))) TOKEN_EXPIRE_DELTA = relativedelta(days=int(env('TOKEN_EXPIRE_DAYS', 1)), hours=int(env('TOKEN_EXPIRE_HOURS', 0)))
LEASE_EXPIRE_DELTA = relativedelta(days=int(env('LEASE_EXPIRE_DAYS', 90)), hours=int(env('LEASE_EXPIRE_HOURS', 0))) LEASE_EXPIRE_DELTA = relativedelta(days=int(env('LEASE_EXPIRE_DAYS', 90)), hours=int(env('LEASE_EXPIRE_HOURS', 0)))
LEASE_RENEWAL_PERIOD = float(env('LEASE_RENEWAL_PERIOD', 0.15)) LEASE_RENEWAL_PERIOD = float(env('LEASE_RENEWAL_PERIOD', 0.15))
LEASE_RENEWAL_DELTA = timedelta(days=int(env('LEASE_EXPIRE_DAYS', 90)), hours=int(env('LEASE_EXPIRE_HOURS', 0)))
CORS_ORIGINS = str(env('CORS_ORIGINS', '')).split(',') if (env('CORS_ORIGINS')) else [f'https://{DLS_URL}'] CORS_ORIGINS = str(env('CORS_ORIGINS', '')).split(',') if (env('CORS_ORIGINS')) else [f'https://{DLS_URL}']
jwt_encode_key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256) jwt_encode_key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256)
@ -144,8 +143,7 @@ async def _origins(request: Request, leases: bool = False):
for origin in session.query(Origin).all(): for origin in session.query(Origin).all():
x = origin.serialize() x = origin.serialize()
if leases: if leases:
serialize = dict(renewal_period=LEASE_RENEWAL_PERIOD, renewal_delta=LEASE_RENEWAL_DELTA) x['leases'] = list(map(lambda _: _.serialize(), Lease.find_by_origin_ref(db, origin.origin_ref)))
x['leases'] = list(map(lambda _: _.serialize(**serialize), Lease.find_by_origin_ref(db, origin.origin_ref)))
response.append(x) response.append(x)
session.close() session.close()
return JSONr(response) return JSONr(response)
@ -161,7 +159,7 @@ async def _origins_delete(request: Request):
async def _origins_delete_origin_ref(request: Request, origin_ref: str): async def _origins_delete_origin_ref(request: Request, origin_ref: str):
if Origin.delete(db, origin_ref) == 1: if Origin.delete(db, origin_ref) == 1:
return Response(status_code=201) return Response(status_code=201)
return JSONr(status_code=404, content={'status': 404, 'detail': 'lease not found'}) raise JSONr(status_code=404, content={'status': 404, 'detail': 'lease not found'})
@app.get('/-/leases', summary='* Leases') @app.get('/-/leases', summary='* Leases')
@ -169,12 +167,10 @@ async def _leases(request: Request, origin: bool = False):
session = sessionmaker(bind=db)() session = sessionmaker(bind=db)()
response = [] response = []
for lease in session.query(Lease).all(): for lease in session.query(Lease).all():
serialize = dict(renewal_period=LEASE_RENEWAL_PERIOD, renewal_delta=LEASE_RENEWAL_DELTA) x = lease.serialize()
x = lease.serialize(**serialize)
if origin: if origin:
lease_origin = session.query(Origin).filter(Origin.origin_ref == lease.origin_ref).first() # assume that each lease has a valid origin record
if lease_origin is not None: x['origin'] = session.query(Origin).filter(Origin.origin_ref == lease.origin_ref).first().serialize()
x['origin'] = lease_origin.serialize()
response.append(x) response.append(x)
session.close() session.close()
return JSONr(response) return JSONr(response)

View File

@ -1,5 +1,4 @@
from datetime import datetime, timedelta, timezone from datetime import datetime, timezone
from dateutil.relativedelta import relativedelta
from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, update, and_, inspect from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, update, and_, inspect
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
@ -57,12 +56,12 @@ class Origin(Base):
session.close() session.close()
@staticmethod @staticmethod
def delete(engine: Engine, origins: ["Origin"] = None) -> int: def delete(engine: Engine, origin_ref: str = None) -> int:
session = sessionmaker(bind=engine)() session = sessionmaker(bind=engine)()
if origins is None: if origin_ref is None:
deletions = session.query(Origin).delete() deletions = session.query(Origin).delete()
else: else:
deletions = session.query(Origin).filter(Origin.origin_ref in origins).delete() deletions = session.query(Origin).filter(Origin.origin_ref == origin_ref).delete()
session.commit() session.commit()
session.close() session.close()
return deletions return deletions
@ -82,10 +81,7 @@ class Lease(Base):
def __repr__(self): def __repr__(self):
return f'Lease(origin_ref={self.origin_ref}, lease_ref={self.lease_ref}, expires={self.lease_expires})' return f'Lease(origin_ref={self.origin_ref}, lease_ref={self.lease_ref}, expires={self.lease_expires})'
def serialize(self, renewal_period: float, renewal_delta: timedelta) -> dict: def serialize(self) -> dict:
lease_renewal = int(Lease.calculate_renewal(renewal_period, renewal_delta).total_seconds())
lease_renewal = self.lease_updated + relativedelta(seconds=lease_renewal)
return { return {
'lease_ref': self.lease_ref, 'lease_ref': self.lease_ref,
'origin_ref': self.origin_ref, 'origin_ref': self.origin_ref,
@ -93,7 +89,6 @@ class Lease(Base):
'lease_created': self.lease_created.replace(tzinfo=timezone.utc).isoformat(), 'lease_created': self.lease_created.replace(tzinfo=timezone.utc).isoformat(),
'lease_expires': self.lease_expires.replace(tzinfo=timezone.utc).isoformat(), 'lease_expires': self.lease_expires.replace(tzinfo=timezone.utc).isoformat(),
'lease_updated': self.lease_updated.replace(tzinfo=timezone.utc).isoformat(), 'lease_updated': self.lease_updated.replace(tzinfo=timezone.utc).isoformat(),
'lease_renewal': lease_renewal.replace(tzinfo=timezone.utc).isoformat(),
} }
@staticmethod @staticmethod
@ -161,20 +156,6 @@ class Lease(Base):
session.close() session.close()
return deletions return deletions
@staticmethod
def calculate_renewal(renewal_period: float, delta: timedelta) -> timedelta:
"""
import datetime
LEASE_RENEWAL_PERIOD=0.2 # 20%
delta = datetime.timedelta(days=1)
renew = delta.total_seconds() * LEASE_RENEWAL_PERIOD
renew = datetime.timedelta(seconds=renew)
expires = delta - renew # 19.2
"""
renew = delta.total_seconds() * renewal_period
renew = timedelta(seconds=renew)
return renew
def init(engine: Engine): def init(engine: Engine):
tables = [Origin, Lease] tables = [Origin, Lease]

View File

@ -76,7 +76,6 @@ async function fetchLeases(element) {
<th scope="col">lease</th> <th scope="col">lease</th>
<th scope="col">created</th> <th scope="col">created</th>
<th scope="col">updated</th> <th scope="col">updated</th>
<th scope="col">next renew</th>
<th scope="col">expires</th> <th scope="col">expires</th>
<th scope="col">origin</th> <th scope="col">origin</th>
</tr>` </tr>`
@ -88,9 +87,8 @@ async function fetchLeases(element) {
<td><code>${o.lease_ref}</code></td> <td><code>${o.lease_ref}</code></td>
<td>${new Date(o.lease_created).toLocaleDateString('system', dtc)}</td> <td>${new Date(o.lease_created).toLocaleDateString('system', dtc)}</td>
<td>${new Date(o.lease_updated).toLocaleDateString('system', dtc)}</td> <td>${new Date(o.lease_updated).toLocaleDateString('system', dtc)}</td>
<td>${new Date(o.lease_renewal).toLocaleDateString('system', dtc)}</td>
<td>${new Date(o.lease_expires).toLocaleDateString('system', dtc)}</td> <td>${new Date(o.lease_expires).toLocaleDateString('system', dtc)}</td>
<td><code title="hostname: ${o.origin?.hostname}">${o.origin_ref}</code></td>` <td><code title="hostname: ${o.origin.hostname}">${o.origin_ref}</code></td>`
tbody.appendChild(row); tbody.appendChild(row);
}) })
table.appendChild(tbody) table.appendChild(tbody)

View File

@ -2,28 +2,13 @@ version: '3.9'
x-dls-variables: &dls-variables x-dls-variables: &dls-variables
DLS_URL: localhost # REQUIRED, change to your ip or hostname DLS_URL: localhost # REQUIRED, change to your ip or hostname
DLS_PORT: 443 # must match nginx listen & exposed port DLS_PORT: 443 # must match nginx listen port
LEASE_EXPIRE_DAYS: 90 LEASE_EXPIRE_DAYS: 90
DATABASE: sqlite:////app/database/db.sqlite DATABASE: sqlite:////app/database/db.sqlite
DEBUG: false DEBUG: false
services: services:
dls: web:
image: collinwebdesigns/fastapi-dls:latest
restart: always
environment:
<<: *dls-variables
volumes:
- /opt/docker/fastapi-dls/cert:/app/cert # instance.private.pem, instance.public.pem
- db:/app/database
entrypoint: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "/app", "--proxy-headers"]
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8000/-/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
proxy:
image: nginx image: nginx
ports: ports:
# thees are ports where nginx (!) is listen to # thees are ports where nginx (!) is listen to
@ -32,14 +17,14 @@ services:
volumes: volumes:
- /opt/docker/fastapi-dls/cert:/opt/cert - /opt/docker/fastapi-dls/cert:/opt/cert
healthcheck: healthcheck:
test: ["CMD", "curl", "--insecure", "--fail", "https://localhost/-/health"] test: [ "CMD", "curl", "--insecure", "--fail", "https://localhost/-/health" ]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 3 retries: 3
start_period: 30s start_period: 30s
command: | command: |
bash -c "bash -s <<\"EOF\" bash -c 'bash -s <<"EOF"
cat > /etc/nginx/nginx.conf <<\"EON\" cat > /etc/nginx/nginx.conf <<"EON"
daemon off; daemon off;
user root; user root;
worker_processes auto; worker_processes auto;
@ -54,7 +39,7 @@ services:
include /etc/nginx/mime.types; include /etc/nginx/mime.types;
upstream dls-backend { upstream dls-backend {
server dls:8000; # must match dls listen port server dls:443;
} }
server { server {
@ -75,17 +60,18 @@ services:
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
location / { location / {
proxy_ssl_verify off;
proxy_set_header Host $$http_host; proxy_set_header Host $$http_host;
proxy_set_header X-Real-IP $$remote_addr; proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $$scheme; proxy_set_header X-Forwarded-Proto $$scheme;
proxy_pass http://dls-backend$$request_uri; proxy_pass https://dls-backend$$request_uri;
} }
location = /-/health { location = /-/health {
access_log off; access_log off;
add_header 'Content-Type' 'application/json'; add_header 'Content-Type' 'application/json';
return 200 '{\"status\":\"up\",\"service\":\"nginx\"}'; return 200; # '{\"status\":\"up\",\"service\":\"nginx\"}';
} }
} }
@ -98,11 +84,12 @@ services:
server_name _; server_name _;
location /leasing/v1/lessor/shutdown { location /leasing/v1/lessor/shutdown {
proxy_ssl_verify off;
proxy_set_header Host $$http_host; proxy_set_header Host $$http_host;
proxy_set_header X-Real-IP $$remote_addr; proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $$scheme; proxy_set_header X-Forwarded-Proto $$scheme;
proxy_pass http://dls-backend/leasing/v1/lessor/shutdown; proxy_pass https://dls-backend/leasing/v1/lessor/shutdown;
} }
location / { location / {
@ -112,7 +99,16 @@ services:
} }
EON EON
nginx nginx
EOF" EOF'
dls:
image: collinwebdesigns/fastapi-dls:latest
restart: always
environment:
<<: *dls-variables
volumes:
- /opt/docker/fastapi-dls/cert:/app/cert
- db:/app/database
volumes: volumes:
db: db:

View File

@ -1,9 +1,9 @@
fastapi==0.89.1 fastapi==0.88.0
uvicorn[standard]==0.20.0 uvicorn[standard]==0.20.0
python-jose==3.3.0 python-jose==3.3.0
pycryptodome==3.16.0 pycryptodome==3.16.0
python-dateutil==2.8.2 python-dateutil==2.8.2
sqlalchemy==1.4.46 sqlalchemy==1.4.45
markdown==3.4.1 markdown==3.4.1
python-dotenv==0.21.0 python-dotenv==0.21.0
jinja2==3.1.2 jinja2==3.1.2

View File

@ -1 +1 @@
VERSION=1.3.3 VERSION=1.3