Merge branch 'dev' into ui
# Conflicts: # app/orm.py
This commit is contained in:
commit
e1259838db
@ -1,2 +1 @@
|
||||
/etc/fastapi-dls/env
|
||||
/etc/systemd/system/fastapi-dls.service
|
||||
|
@ -3,7 +3,7 @@
|
||||
WORKING_DIR=/usr/share/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 ..."
|
||||
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
|
||||
@ -12,8 +12,8 @@ else
|
||||
fi
|
||||
|
||||
while true; do
|
||||
[[ -f $CONFIG_DIR/webserver.key ]] && default_answer="N" || default_answer="Y"
|
||||
[[ $default_answer == "Y" ]] && V="Y/n" || V="y/N"
|
||||
[ -f $CONFIG_DIR/webserver.key ] && default_answer="N" || default_answer="Y"
|
||||
[ $default_answer == "Y" ] && V="Y/n" || V="y/N"
|
||||
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.
|
||||
case $yn in
|
||||
@ -27,7 +27,7 @@ while true; do
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -f $CONFIG_DIR/webserver.key ]]; then
|
||||
if [ -f $CONFIG_DIR/webserver.key ]; then
|
||||
echo "> Starting service ..."
|
||||
systemctl start fastapi-dls.service
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ -f /etc/systemd/system/fastapi-dls.service ]]; then
|
||||
echo "> Removing service file."
|
||||
rm /etc/systemd/system/fastapi-dls.service
|
||||
fi
|
||||
# is removed automatically
|
||||
#if [ "$1" = purge ] && [ -d /usr/share/fastapi-dls ]; then
|
||||
# echo "> Removing app."
|
||||
# rm -r /usr/share/fastapi-dls
|
||||
#fi
|
||||
|
||||
# todo
|
||||
echo -e "> Done."
|
||||
|
@ -1,5 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo -e "> Starting uninstallation of 'fastapi-dls'!"
|
||||
|
||||
# todo
|
||||
|
@ -98,7 +98,7 @@ build:pacman:
|
||||
- "*.pkg.tar.zst"
|
||||
|
||||
test:
|
||||
image: python:3.10-slim-bullseye
|
||||
image: python:3.11-slim-bullseye
|
||||
stage: test
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
@ -114,6 +114,9 @@ test:
|
||||
- cd test
|
||||
script:
|
||||
- pytest main.py
|
||||
artifacts:
|
||||
reports:
|
||||
dotenv: version.env
|
||||
|
||||
.test:linux:
|
||||
stage: test
|
||||
@ -272,24 +275,11 @@ deploy:pacman:
|
||||
- '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}"'
|
||||
|
||||
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:
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
stage: .post
|
||||
needs:
|
||||
- job: release:prepare
|
||||
- job: test
|
||||
artifacts: true
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
@ -298,7 +288,7 @@ release:
|
||||
script:
|
||||
- echo "Running release-job for $VERSION"
|
||||
release:
|
||||
name: $CI_PROJECT_TITLE $version
|
||||
name: $CI_PROJECT_TITLE $VERSION
|
||||
description: Release of $CI_PROJECT_TITLE version $VERSION
|
||||
tag_name: $VERSION
|
||||
ref: $CI_COMMIT_SHA
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM python:3.10-alpine
|
||||
FROM python:3.11-alpine
|
||||
|
||||
COPY requirements.txt /tmp/requirements.txt
|
||||
|
||||
|
17
FAQ.md
Normal file
17
FAQ.md
Normal 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.
|
||||
|
80
README.md
80
README.md
@ -70,7 +70,7 @@ volumes:
|
||||
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.
|
||||
|
||||
@ -175,6 +175,11 @@ Successful tested with:
|
||||
- Debian 12 (Bookworm) (works but not recommended because it is currently in *testing* state)
|
||||
- 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**
|
||||
|
||||
First go to [GitLab-Registry](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/packages) and select your
|
||||
@ -201,13 +206,17 @@ Packages are available here:
|
||||
```shell
|
||||
pacman -Sy
|
||||
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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
@ -261,26 +270,67 @@ Successfully tested with this package versions:
|
||||
|
||||
## 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
|
||||
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
|
||||
```
|
||||
|
||||
Check licensing status:
|
||||
|
||||
```shell
|
||||
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`.
|
||||
Now restart `NvContainerLocalSystem` service.
|
||||
|
||||
**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"
|
||||
```text
|
||||
vGPU Software Licensed Product
|
||||
License Status : Licensed (Expiry: YYYY-M-DD hh:mm:ss GMT)
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
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 /`
|
||||
|
||||
|
14
app/main.py
14
app/main.py
@ -9,7 +9,7 @@ from dotenv import load_dotenv
|
||||
from fastapi import FastAPI
|
||||
from fastapi.requests import Request
|
||||
from json import loads as json_loads
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from calendar import timegm
|
||||
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)))
|
||||
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_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}']
|
||||
|
||||
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():
|
||||
x = origin.serialize()
|
||||
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)
|
||||
session.close()
|
||||
return JSONr(response)
|
||||
@ -167,10 +169,12 @@ async def _leases(request: Request, origin: bool = False):
|
||||
session = sessionmaker(bind=db)()
|
||||
response = []
|
||||
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:
|
||||
# assume that each lease has a valid origin record
|
||||
x['origin'] = session.query(Origin).filter(Origin.origin_ref == lease.origin_ref).first().serialize()
|
||||
lease_origin = session.query(Origin).filter(Origin.origin_ref == lease.origin_ref).first()
|
||||
if lease_origin is not None:
|
||||
x['origin'] = lease_origin.serialize()
|
||||
response.append(x)
|
||||
session.close()
|
||||
return JSONr(response)
|
||||
|
29
app/orm.py
29
app/orm.py
@ -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.ext.declarative import declarative_base
|
||||
@ -56,12 +57,12 @@ class Origin(Base):
|
||||
session.close()
|
||||
|
||||
@staticmethod
|
||||
def delete(engine: Engine, origin_ref: str = None) -> int:
|
||||
def delete(engine: Engine, origins: ["Origin"] = None) -> int:
|
||||
session = sessionmaker(bind=engine)()
|
||||
if origin_ref is None:
|
||||
if origins is None:
|
||||
deletions = session.query(Origin).delete()
|
||||
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.close()
|
||||
return deletions
|
||||
@ -81,7 +82,10 @@ class Lease(Base):
|
||||
def __repr__(self):
|
||||
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 {
|
||||
'lease_ref': self.lease_ref,
|
||||
'origin_ref': self.origin_ref,
|
||||
@ -89,6 +93,7 @@ class Lease(Base):
|
||||
'lease_created': self.lease_created.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_renewal': lease_renewal.replace(tzinfo=timezone.utc).isoformat(),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@ -156,6 +161,20 @@ class Lease(Base):
|
||||
session.close()
|
||||
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):
|
||||
tables = [Origin, Lease]
|
||||
|
@ -1,4 +1,4 @@
|
||||
fastapi==0.88.0
|
||||
fastapi==0.89.1
|
||||
uvicorn[standard]==0.20.0
|
||||
python-jose==3.3.0
|
||||
pycryptodome==3.16.0
|
||||
|
@ -1 +1 @@
|
||||
VERSION=1.3
|
||||
VERSION=1.3.3
|
||||
|
Loading…
Reference in New Issue
Block a user