mirror of
				https://gitea.publichub.eu/oscar.krause/fastapi-dls.git
				synced 2025-11-04 07:26:07 +00:00 
			
		
		
		
	Merge branch 'dev' into 'main'
1.3.3 See merge request oscar.krause/fastapi-dls!21
This commit is contained in:
		@@ -1,2 +1 @@
 | 
				
			|||||||
/etc/fastapi-dls/env
 | 
					/etc/fastapi-dls/env
 | 
				
			||||||
/etc/systemd/system/fastapi-dls.service
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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."
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,3 @@
 | 
				
			|||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo -e "> Starting uninstallation of 'fastapi-dls'!"
 | 
					echo -e "> Starting uninstallation of 'fastapi-dls'!"
 | 
				
			||||||
 | 
					 | 
				
			||||||
# todo
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										73
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								README.md
									
									
									
									
									
								
							@@ -206,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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -266,26 +270,67 @@ 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"
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 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 /`
 | 
					### `GET /`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
@@ -43,6 +43,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)
 | 
				
			||||||
@@ -151,7 +152,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)
 | 
				
			||||||
@@ -168,7 +170,8 @@ 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:
 | 
				
			||||||
            lease_origin = session.query(Origin).filter(Origin.origin_ref == lease.origin_ref).first()
 | 
					            lease_origin = session.query(Origin).filter(Origin.origin_ref == lease.origin_ref).first()
 | 
				
			||||||
            if lease_origin is not None:
 | 
					            if lease_origin is not None:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								app/orm.py
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								app/orm.py
									
									
									
									
									
								
							@@ -1,4 +1,5 @@
 | 
				
			|||||||
import datetime
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
@@ -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.isoformat(),
 | 
					            'lease_created': self.lease_created.isoformat(),
 | 
				
			||||||
            'lease_expires': self.lease_expires.isoformat(),
 | 
					            'lease_expires': self.lease_expires.isoformat(),
 | 
				
			||||||
            'lease_updated': self.lease_updated.isoformat(),
 | 
					            'lease_updated': self.lease_updated.isoformat(),
 | 
				
			||||||
 | 
					            'lease_renewal': lease_renewal.isoformat(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
@@ -133,7 +138,7 @@ class Lease(Base):
 | 
				
			|||||||
        return entity
 | 
					        return entity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def renew(engine: Engine, lease: "Lease", lease_expires: datetime.datetime, lease_updated: datetime.datetime):
 | 
					    def renew(engine: Engine, lease: "Lease", lease_expires: datetime, lease_updated: datetime):
 | 
				
			||||||
        session = sessionmaker(bind=engine)()
 | 
					        session = sessionmaker(bind=engine)()
 | 
				
			||||||
        x = dict(lease_expires=lease_expires, lease_updated=lease_updated)
 | 
					        x = dict(lease_expires=lease_expires, lease_updated=lease_updated)
 | 
				
			||||||
        session.execute(update(Lease).where(and_(Lease.origin_ref == lease.origin_ref, Lease.lease_ref == lease.lease_ref)).values(**x))
 | 
					        session.execute(update(Lease).where(and_(Lease.origin_ref == lease.origin_ref, Lease.lease_ref == lease.lease_ref)).values(**x))
 | 
				
			||||||
@@ -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]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1 @@
 | 
				
			|||||||
VERSION=1.3.2
 | 
					VERSION=1.3.3
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user