Compare commits

...

57 Commits

Author SHA1 Message Date
6dbd39b76c helper.js - added "lease_renewal" to table 2023-01-18 08:42:57 +01:00
21f49a2fb5 fixed return type 2023-01-18 08:42:38 +01:00
757bae849c fixed origin null pointer exception on leases table 2023-01-18 08:42:28 +01:00
e1259838db Merge branch 'dev' into ui
# Conflicts:
#	app/orm.py
2023-01-18 08:39:06 +01:00
5bb8f17679 improvements 2023-01-18 08:07:55 +01:00
de17b0f1b5 fixes 2023-01-18 08:03:02 +01:00
0ab5969d3a fixes 2023-01-18 06:56:16 +01:00
059a51fe74 refactored commands 2023-01-17 17:25:48 +01:00
bf858b38f4 fixes 2023-01-17 17:09:13 +01:00
f60f08d543 run powershell as administrator 2023-01-17 16:57:15 +01:00
b2e6fab294 fixes 2023-01-17 16:49:15 +01:00
b09bb091a5 bump version to 1.3.3 2023-01-17 16:29:32 +01:00
651af4cc82 fixed client-token url and added wget als alternative to curl 2023-01-17 16:29:21 +01:00
70f7d3f483 mark Let's Encrypt section as optional 2023-01-17 15:36:38 +01:00
1e4070a1ba added remove "/usr/share/fastapi-dls" to "postrm" 2023-01-17 14:57:54 +01:00
d69d833923 migrated "[[ ]]" if statements to "[ ]" 2023-01-17 14:57:39 +01:00
7ef071f92b removed fastapi-dls.service from conffiles 2023-01-17 14:57:09 +01:00
3c19fc9d5b implemented "lease_renewal" attribute as calculated value within what period of time the license must be renewed 2023-01-17 11:49:56 +01:00
742fa07ed4 bump version to 1.3.2 2023-01-17 11:18:25 +01:00
a758d93970 main.py - fixed empty lease origin response 2023-01-17 11:18:07 +01:00
a65687a082 bump version to 1.3.1 2023-01-16 10:34:20 +01:00
3e445c80aa fixes 2023-01-16 10:33:52 +01:00
20cc984799 FAQ.md 2023-01-16 10:30:55 +01:00
3495cc3af5 typos 2023-01-16 10:30:40 +01:00
ed13577e82 Dockerfile - updated to python 3.11 2023-01-16 10:30:21 +01:00
ca8a9df54c requirements.txt updated 2023-01-16 10:24:08 +01:00
5425eec545 .gitlab-ci.yml simplified 2023-01-16 10:23:58 +01:00
2f3c7d5433 Merge branch 'main' into dev 2023-01-16 07:00:20 +01:00
b551b0e7f9 README.md - added sunsupoorted ubuntu version 2023-01-15 19:47:50 +01:00
50dea9ac4e fixes 2023-01-05 14:08:08 +01:00
4699c1770d Merge branch 'dev' into ui
# Conflicts:
#	app/main.py
2023-01-05 07:31:15 +01:00
549a48a10b Merge branch 'dev' into 'main'
1.3

See merge request oscar.krause/fastapi-dls!18
2023-01-05 07:27:10 +01:00
1f3bc8b4af .gitlab-ci.yml 2023-01-05 07:22:25 +01:00
5fc8d4091b Merge branch 'dev' into 'main'
1.3

See merge request oscar.krause/fastapi-dls!17
2023-01-05 07:21:28 +01:00
851ec1a5c6 requirements.txt updated 2023-01-05 06:56:56 +01:00
9180222169 README.md 2023-01-04 21:46:02 +01:00
e71d4c4f4e fixed missing servie file for DEBIAN 2023-01-04 18:27:57 +01:00
aecad82914 main.py - added confirmation to deleteOrigins() 2023-01-04 18:12:59 +01:00
02fccb3605 README.md 2023-01-04 18:05:07 +01:00
24dba89dbe removed todos, currently all done or there is a branch for it 2023-01-04 17:58:23 +01:00
f5557a5ccd README.md 2023-01-04 17:46:19 +01:00
e8736c94ec docker-compose.yml - disabled internal ssl support 2023-01-04 17:46:02 +01:00
4325560ec4 README.md - added some collapses for logs 2023-01-04 17:18:13 +01:00
05979490ce README.md - moved "Endpoints" below "Setup" 2023-01-04 17:17:58 +01:00
c894537ff9 Merge branch 'dev' into 'main'
1.2

See merge request oscar.krause/fastapi-dls!16
2022-12-30 07:51:26 +01:00
dc6b6bff69 Merge branch 'dev' into 'main'
fixes

See merge request oscar.krause/fastapi-dls!15
2022-12-29 13:03:22 +01:00
0b7bedde66 Merge branch 'dev' into 'main'
1.1

See merge request oscar.krause/fastapi-dls!14
2022-12-29 12:54:37 +01:00
3d5203dae0 Merge branch 'dev' into 'main'
1.0.0

See merge request oscar.krause/fastapi-dls!12
2022-12-28 11:44:52 +01:00
d187167129 Merge branch 'dev' into 'main'
v0.6

See merge request oscar.krause/fastapi-dls!9
2022-12-23 07:17:17 +01:00
79c8d19b00 Merge branch 'dev' into 'main'
.gitlab-ci.yml fixes

See merge request oscar.krause/fastapi-dls!8
2022-12-21 11:12:18 +01:00
8a7f5d9cbe Merge branch 'dev' into 'main'
v0.5

See merge request oscar.krause/fastapi-dls!7
2022-12-21 11:10:18 +01:00
5e6c014b2b Merge branch 'dev' into 'main'
v0.4

See merge request oscar.krause/fastapi-dls!6
2022-12-20 19:45:35 +01:00
1173964643 Merge branch 'dev' into 'main'
v0.3

See merge request oscar.krause/fastapi-dls!5
2022-12-20 18:35:41 +01:00
a4c2ec6895 Merge branch 'dev' into 'main'
requirements.txt

See merge request oscar.krause/fastapi-dls!4
2022-12-20 15:13:11 +01:00
68aeeb785d Merge branch 'dev' into 'main'
v0.2

See merge request oscar.krause/fastapi-dls!3
2022-12-20 15:08:56 +01:00
a91ff4cd9b Merge branch 'dev' into 'main'
.gitlab-ci.yml - fixed deploy stage

See merge request oscar.krause/fastapi-dls!2
2022-12-20 09:09:08 +01:00
a6cf5e0ac1 Merge branch 'dev' into 'main'
v0.1

See merge request oscar.krause/fastapi-dls!1
2022-12-20 09:06:33 +01:00
13 changed files with 244 additions and 151 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,8 +1,9 @@
#!/bin/bash #!/bin/bash
if [[ -f /etc/systemd/system/fastapi-dls.service ]]; then # is removed automatically
echo "> Removing service file." #if [ "$1" = purge ] && [ -d /usr/share/fastapi-dls ]; then
rm /etc/systemd/system/fastapi-dls.service # echo "> Removing app."
fi # rm -r /usr/share/fastapi-dls
#fi
# todo echo -e "> Done."

View File

@ -1,5 +1,3 @@
#!/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 - cp .DEBIAN/fastapi-dls.service build/etc/systemd/system/fastapi-dls.service
# 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.10-slim-bullseye image: python:3.11-slim-bullseye
stage: test stage: test
rules: rules:
- if: $CI_COMMIT_BRANCH - if: $CI_COMMIT_BRANCH
@ -114,6 +114,9 @@ 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
@ -272,24 +275,11 @@ 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: release:prepare - job: test
artifacts: true artifacts: true
rules: rules:
- if: $CI_COMMIT_TAG - if: $CI_COMMIT_TAG
@ -298,7 +288,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.10-alpine FROM python:3.11-alpine
COPY requirements.txt /tmp/requirements.txt COPY requirements.txt /tmp/requirements.txt

17
FAQ.md Normal file
View File

@ -0,0 +1,17 @@
# 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.

230
README.md
View File

@ -9,68 +9,6 @@ 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)
@ -96,6 +34,8 @@ 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
@ -103,7 +43,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. Goto [`docker-compose.yml`](docker-compose.yml) for more advanced example (with reverse proxy usage).
```yaml ```yaml
version: '3.9' version: '3.9'
@ -130,7 +70,7 @@ volumes:
dls-db: dls-db:
``` ```
## Debian/Ubuntu (manual method using `git clone`) ## Debian/Ubuntu (manual method using `git clone` and python virtual environment)
Tested on `Debian 11 (bullseye)`, Ubuntu may also work. Tested on `Debian 11 (bullseye)`, Ubuntu may also work.
@ -235,6 +175,11 @@ 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
@ -261,13 +206,17 @@ 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 ## Let's Encrypt Certificate (optional)
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.
@ -298,8 +247,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 | | `INSTANCE_KEY_RSA` | `<app-dir>/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs \*3 |
| `INSTANCE_KEY_PUB` | `<app-dir>/cert/instance.public.pem` | Site-wide public key | | `INSTANCE_KEY_PUB` | `<app-dir>/cert/instance.public.pem` | Site-wide public key \*3 |
\*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
@ -307,6 +256,8 @@ 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.**
@ -319,27 +270,128 @@ 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"
``` ```
## Windows Output should be something like:
Download file and place it into `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`. ```text
Now restart `NvContainerLocalSystem` service. vGPU Software Licensed Product
License Status : Licensed (Expiry: YYYY-M-DD hh:mm:ss GMT)
**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-Service NVDisplay.ContainerLocalSystem
'C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe' -q | Select-String "License"
``` ```
Done. For more information check [troubleshoot section](#troubleshoot).
## Windows
**Power-Shell** (run as administrator!)
Download *client-token* and place it into `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`:
```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
Restart-Service NVDisplay.ContainerLocalSystem
```
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`.
@ -358,6 +410,9 @@ 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):
@ -376,6 +431,8 @@ 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)
@ -443,14 +500,13 @@ Dec 20 17:53:34 ubuntu-grid-server nvidia-gridd[10354]: License acquired success
</details> </details>
### Error on releasing leases on shutdown (fixed in 1.3 by using reverse proxy) ### Error on releasing leases on shutdown (can be ignored and/or fixed with reverse proxy)
**UPDATE for version `1.3`**: This issue can be fixed by using a reverse proxy (e.g. `nginx`). Please read section The driver wants to release current leases on shutting down windows. This endpoint needs to be a http endpoint.
below. The error message can safely be ignored (since we have no license limitation :P) and looks like this:
The driver wants to release current leases on shutting down windows. This endpoint needs to be a http endpoint and <details>
is currently not implemented. The error message looks like and safely can be ignored (since we have no license <summary>Log example</summary>
limitation :P):
``` ```
<1>:NLS initialized <1>:NLS initialized
@ -459,7 +515,7 @@ limitation :P):
<0>:End Logging <0>:End Logging
``` ```
#### log with 1.3 and nginx as reverse proxy #### log with nginx as reverse proxy (see [docker-compose.yml](docker-compose.yml))
``` ```
<1>:NLS initialized <1>:NLS initialized
@ -474,6 +530,8 @@ 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 from datetime import datetime, timedelta
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,6 +50,7 @@ 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)
@ -143,7 +144,8 @@ 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:
x['leases'] = list(map(lambda _: _.serialize(), Lease.find_by_origin_ref(db, origin.origin_ref))) serialize = dict(renewal_period=LEASE_RENEWAL_PERIOD, renewal_delta=LEASE_RENEWAL_DELTA)
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)
@ -159,7 +161,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)
raise JSONr(status_code=404, content={'status': 404, 'detail': 'lease not found'}) return JSONr(status_code=404, content={'status': 404, 'detail': 'lease not found'})
@app.get('/-/leases', summary='* Leases') @app.get('/-/leases', summary='* Leases')
@ -167,10 +169,12 @@ 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():
x = lease.serialize() serialize = dict(renewal_period=LEASE_RENEWAL_PERIOD, renewal_delta=LEASE_RENEWAL_DELTA)
x = lease.serialize(**serialize)
if origin: if origin:
# assume that each lease has a valid origin record lease_origin = session.query(Origin).filter(Origin.origin_ref == lease.origin_ref).first()
x['origin'] = session.query(Origin).filter(Origin.origin_ref == lease.origin_ref).first().serialize() if lease_origin is not None:
x['origin'] = lease_origin.serialize()
response.append(x) response.append(x)
session.close() session.close()
return JSONr(response) return JSONr(response)

View File

@ -1,4 +1,5 @@
from datetime import datetime, timezone from datetime import datetime, timedelta, 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
@ -56,12 +57,12 @@ class Origin(Base):
session.close() session.close()
@staticmethod @staticmethod
def delete(engine: Engine, origin_ref: str = None) -> int: def delete(engine: Engine, origins: ["Origin"] = None) -> int:
session = sessionmaker(bind=engine)() session = sessionmaker(bind=engine)()
if origin_ref is None: if origins is None:
deletions = session.query(Origin).delete() deletions = session.query(Origin).delete()
else: else:
deletions = session.query(Origin).filter(Origin.origin_ref == origin_ref).delete() deletions = session.query(Origin).filter(Origin.origin_ref in origins).delete()
session.commit() session.commit()
session.close() session.close()
return deletions return deletions
@ -81,7 +82,10 @@ 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) -> dict: def serialize(self, renewal_period: float, renewal_delta: timedelta) -> 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,
@ -89,6 +93,7 @@ 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
@ -156,6 +161,20 @@ 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,6 +76,7 @@ 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>`
@ -87,8 +88,9 @@ 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,13 +2,28 @@ 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 port DLS_PORT: 443 # must match nginx listen & exposed 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:
web: dls:
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
@ -17,14 +32,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;
@ -39,7 +54,7 @@ services:
include /etc/nginx/mime.types; include /etc/nginx/mime.types;
upstream dls-backend { upstream dls-backend {
server dls:443; server dls:8000; # must match dls listen port
} }
server { server {
@ -60,18 +75,17 @@ 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 https://dls-backend$$request_uri; proxy_pass http://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\"}';
} }
} }
@ -84,12 +98,11 @@ 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 https://dls-backend/leasing/v1/lessor/shutdown; proxy_pass http://dls-backend/leasing/v1/lessor/shutdown;
} }
location / { location / {
@ -99,16 +112,7 @@ 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.88.0 fastapi==0.89.1
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.45 sqlalchemy==1.4.46
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 VERSION=1.3.3