Compare commits

..

No commits in common. "a09fc5f2ad912ad2cf5b97a3fb62500d158ee714" and "d57b494779ead1070ccf0093361f6ff14607bc8b" have entirely different histories.

8 changed files with 28 additions and 145 deletions

View File

@ -75,7 +75,7 @@ if [[ -f $CONFIG_DIR/webserver.key ]]; then
if [ -x "$(command -v curl)" ]; then if [ -x "$(command -v curl)" ]; then
echo "> Testing API ..." echo "> Testing API ..."
source $CONFIG_DIR/env source $CONFIG_DIR/env
curl --insecure -X GET https://$DLS_URL:$DLS_PORT/-/health curl --insecure -X GET https://$DLS_URL:$DLS_PORT/status
else else
echo "> Testing API failed, curl not available. Please test manually!" echo "> Testing API failed, curl not available. Please test manually!"
fi fi

View File

@ -32,7 +32,6 @@ package() {
install -d "$pkgdir/var/lib/$pkgname/cert" install -d "$pkgdir/var/lib/$pkgname/cert"
cp -r "$srcdir/$pkgname/doc"/* "$pkgdir/usr/share/doc/$pkgname/" cp -r "$srcdir/$pkgname/doc"/* "$pkgdir/usr/share/doc/$pkgname/"
install -Dm644 "$srcdir/$pkgname/README.md" "$pkgdir/usr/share/doc/$pkgname/README.md" install -Dm644 "$srcdir/$pkgname/README.md" "$pkgdir/usr/share/doc/$pkgname/README.md"
install -Dm644 "$srcdir/$pkgname/version.env" "$pkgdir/usr/share/doc/$pkgname/version.env"
sed -i "s/README.md/\/usr\/share\/doc\/$pkgname\/README.md/g" "$srcdir/$pkgname/app/main.py" sed -i "s/README.md/\/usr\/share\/doc\/$pkgname\/README.md/g" "$srcdir/$pkgname/app/main.py"
sed -i "s/join(dirname(__file__), 'cert\//join('\/var\/lib\/$pkgname', 'cert\//g" "$srcdir/$pkgname/app/main.py" sed -i "s/join(dirname(__file__), 'cert\//join('\/var\/lib\/$pkgname', 'cert\//g" "$srcdir/$pkgname/app/main.py"

View File

@ -122,7 +122,7 @@ test:
- FASTAPI_DLS_PID=$! - FASTAPI_DLS_PID=$!
- echo "Started service with pid $FASTAPI_DLS_PID" - echo "Started service with pid $FASTAPI_DLS_PID"
# testing service # testing service
- if [ "`curl --insecure -s https://127.0.0.1/-/health | jq .status`" != "up" ]; then echo "Success"; else "Error"; fi - if [ "`curl --insecure -s https://127.0.0.1/status | jq .status`" != "up" ]; then echo "Success"; else "Error"; fi
# cleanup # cleanup
- kill $FASTAPI_DLS_PID - kill $FASTAPI_DLS_PID
- apt-get purge -qq -y fastapi-dls - apt-get purge -qq -y fastapi-dls

View File

@ -1,2 +1,3 @@
* @oscar.krause !.PKGBUILD/ @oscar.krause
.PKGBUILD/ @samicrusader .PKGBUILD/ @samicrusader

View File

@ -14,5 +14,5 @@ COPY app /app
COPY version.env /version.env COPY version.env /version.env
COPY README.md /README.md COPY README.md /README.md
HEALTHCHECK --start-period=30s --interval=10s --timeout=5s --retries=3 CMD curl --insecure --fail https://localhost/-/health || exit 1 HEALTHCHECK --start-period=30s --interval=10s --timeout=5s --retries=3 CMD curl --insecure --fail https://localhost/status || exit 1
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "443", "--app-dir", "/app", "--proxy-headers", "--ssl-keyfile", "/app/cert/webserver.key", "--ssl-certfile", "/app/cert/webserver.crt"] CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "443", "--app-dir", "/app", "--proxy-headers", "--ssl-keyfile", "/app/cert/webserver.key", "--ssl-certfile", "/app/cert/webserver.crt"]

View File

@ -13,29 +13,17 @@ Only the clients need a connection to this service on configured port.
## Endpoints ## Endpoints
### [`GET /`](/) ### `GET /`
Redirect to `/-/readme`.
### [`GET /status`](/status) (deprecated: use `/-/health`)
Status endpoint, used for *healthcheck*. Shows also current version and commit hash.
### [`GET /-/health`](/-/health)
Status endpoint, used for *healthcheck*. Shows also current version and commit hash.
### [`GET /-/readme`](/-/readme)
HTML rendered README.md. HTML rendered README.md.
### [`GET /-/docs`](/-/docs), [`GET /-/redocs`](/-/redocs) ### `GET /status`
OpenAPI specifications rendered from `GET /-/openapi.json`. Status endpoint, used for *healthcheck*. Shows also current version and commit hash.
### [`GET /-/manage`](/-/manage) ### `GET /docs`
Shows a very basic UI to delete origins or leases. OpenAPI specifications rendered from `GET /openapi.json`.
### `GET /-/origins?leases=false` ### `GET /-/origins?leases=false`
@ -45,10 +33,6 @@ List registered origins.
|-----------------|---------|--------------------------------------| |-----------------|---------|--------------------------------------|
| `leases` | `false` | Include referenced leases per origin | | `leases` | `false` | Include referenced leases per origin |
### `DELETE /-/origins`
Deletes all origins and their leases.
### `GET /-/leases?origin=false` ### `GET /-/leases?origin=false`
List current leases. List current leases.
@ -57,10 +41,6 @@ List current leases.
|-----------------|---------|-------------------------------------| |-----------------|---------|-------------------------------------|
| `origin` | `false` | Include referenced origin per lease | | `origin` | `false` | Include referenced origin per lease |
### `DELETE /-/lease/{lease_ref}`
Deletes an lease.
### `GET /client-token` ### `GET /client-token`
Generate client token, (see [installation](#installation)). Generate client token, (see [installation](#installation)).
@ -420,10 +400,3 @@ Dec 20 17:53:34 ubuntu-grid-server nvidia-gridd[10354]: License acquired success
``` ```
</details> </details>
# Credits
Thanks to vGPU community and all who uses this project and report bugs.
Special thanks to @samicrusader who created build file for ArchLinux.

View File

@ -8,6 +8,7 @@ from os import getenv as env
from dotenv import load_dotenv from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from fastapi.requests import Request from fastapi.requests import Request
from fastapi.encoders import jsonable_encoder
import json import json
from datetime import datetime from datetime import datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
@ -15,12 +16,12 @@ from calendar import timegm
from jose import jws, jwk, jwt from jose import jws, jwk, jwt
from jose.constants import ALGORITHMS from jose.constants import ALGORITHMS
from starlette.middleware.cors import CORSMiddleware from starlette.middleware.cors import CORSMiddleware
from starlette.responses import StreamingResponse, JSONResponse, HTMLResponse, Response from starlette.responses import StreamingResponse, JSONResponse, HTMLResponse
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from util import load_key, load_file from util import load_key, load_file
from orm import Origin, Lease, init as db_init, migrate from orm import Origin, Lease, init as db_init
logger = logging.getLogger() logger = logging.getLogger()
load_dotenv('../version.env') load_dotenv('../version.env')
@ -29,7 +30,7 @@ VERSION, COMMIT, DEBUG = env('VERSION', 'unknown'), env('COMMIT', 'unknown'), bo
app = FastAPI(title='FastAPI-DLS', description='Minimal Delegated License Service (DLS).', version=VERSION) app = FastAPI(title='FastAPI-DLS', description='Minimal Delegated License Service (DLS).', version=VERSION)
db = create_engine(str(env('DATABASE', 'sqlite:///db.sqlite'))) db = create_engine(str(env('DATABASE', 'sqlite:///db.sqlite')))
db_init(db), migrate(db) db_init(db)
DLS_URL = str(env('DLS_URL', 'localhost')) DLS_URL = str(env('DLS_URL', 'localhost'))
DLS_PORT = int(env('DLS_PORT', '443')) DLS_PORT = int(env('DLS_PORT', '443'))
@ -63,60 +64,16 @@ def get_token(request: Request) -> dict:
return jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False}) return jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False})
@app.get('/', summary='* Index') @app.get('/')
async def index(): async def index():
return RedirectResponse('/-/readme')
@app.get('/status', summary='* Status', description='Returns current service status, version (incl. git-commit) and some variables.', deprecated=True)
async def status(request: Request):
return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG})
@app.get('/-/health', summary='* Health')
async def _health(request: Request):
return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG})
@app.get('/-/readme', summary='* Readme')
async def _readme():
from markdown import markdown from markdown import markdown
content = load_file('../README.md').decode('utf-8') content = load_file('../README.md').decode('utf-8')
return HTMLResponse(markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc'])) return HTMLResponse(markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc']))
@app.get('/-/manage', summary='* Management UI') @app.get('/status')
async def _manage(request: Request): async def status(request: Request):
response = ''' return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG})
<!DOCTYPE html>
<html>
<head>
<title>FastAPI-DLS Management</title>
</head>
<body>
<button onclick="deleteOrigins()">delete origins and their leases</button>
<button onclick="deleteLease()">delete specific lease</button>
<script>
function deleteOrigins() {
var xhr = new XMLHttpRequest();
xhr.open("DELETE", '/-/origins', true);
xhr.send();
}
function deleteLease(lease_ref) {
if(lease_ref === undefined)
lease_ref = window.prompt("Please enter 'lease_ref' which should be deleted");
if(lease_ref === null || lease_ref === "")
return
var xhr = new XMLHttpRequest();
xhr.open("DELETE", `/-/lease/${lease_ref}`, true);
xhr.send();
}
</script>
</body>
</html>
'''
return HTMLResponse(response)
@app.get('/-/origins') @app.get('/-/origins')
@ -132,12 +89,6 @@ async def _origins(request: Request, leases: bool = False):
return JSONResponse(response) return JSONResponse(response)
@app.delete('/-/origins')
async def _origins_delete(request: Request):
Origin.delete(db)
return Response(status_code=201)
@app.get('/-/leases') @app.get('/-/leases')
async def _leases(request: Request, origin: bool = False): async def _leases(request: Request, origin: bool = False):
session = sessionmaker(bind=db)() session = sessionmaker(bind=db)()
@ -152,13 +103,6 @@ async def _leases(request: Request, origin: bool = False):
return JSONResponse(response) return JSONResponse(response)
@app.delete('/-/lease/{lease_ref}')
async def _lease_delete(request: Request, lease_ref: str):
if Lease.delete(db, lease_ref) == 1:
return Response(status_code=201)
raise HTTPException(status_code=404, detail='lease not found')
# venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py # venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py
@app.get('/client-token') @app.get('/client-token')
async def client_token(): async def client_token():

View File

@ -1,6 +1,6 @@
import datetime import datetime
from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, update, and_, inspect from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, UniqueConstraint, update, and_, delete, inspect
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.engine import Engine from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
@ -43,35 +43,24 @@ class Origin(Base):
if entity is None: if entity is None:
session.add(origin) session.add(origin)
else: else:
x = dict( values = dict(
hostname=origin.hostname, hostname=origin.hostname,
guest_driver_version=origin.guest_driver_version, guest_driver_version=origin.guest_driver_version,
os_platform=origin.os_platform, os_platform=origin.os_platform,
os_version=origin.os_version os_version=origin.os_version,
) )
session.execute(update(Origin).where(Origin.origin_ref == origin.origin_ref).values(**x)) session.execute(update(Origin).where(Origin.origin_ref == origin.origin_ref).values(**values))
session.commit() session.commit()
session.flush() session.flush()
session.close() session.close()
@staticmethod
def delete(engine: Engine, origins: ["Origin"] = None) -> int:
session = sessionmaker(bind=engine)()
if origins is None:
deletions = session.query(Origin).delete()
else:
deletions = session.query(Origin).filter(Origin.origin_ref in origins).delete()
session.commit()
session.close()
return deletions
class Lease(Base): class Lease(Base):
__tablename__ = "lease" __tablename__ = "lease"
origin_ref = Column(CHAR(length=36), ForeignKey(Origin.origin_ref), primary_key=True, nullable=False, index=True) # uuid4
lease_ref = Column(CHAR(length=36), primary_key=True, nullable=False, index=True) # uuid4 lease_ref = Column(CHAR(length=36), primary_key=True, nullable=False, index=True) # uuid4
origin_ref = Column(CHAR(length=36), ForeignKey(Origin.origin_ref, ondelete='CASCADE'), nullable=False, index=True) # uuid4
lease_created = Column(DATETIME(), nullable=False) lease_created = Column(DATETIME(), nullable=False)
lease_expires = Column(DATETIME(), nullable=False) lease_expires = Column(DATETIME(), nullable=False)
lease_updated = Column(DATETIME(), nullable=False) lease_updated = Column(DATETIME(), nullable=False)
@ -96,14 +85,14 @@ class Lease(Base):
@staticmethod @staticmethod
def create_or_update(engine: Engine, lease: "Lease"): def create_or_update(engine: Engine, lease: "Lease"):
session = sessionmaker(bind=engine)() session = sessionmaker(bind=engine)()
entity = session.query(Lease).filter(Lease.lease_ref == lease.lease_ref).first() entity = session.query(Lease).filter(and_(Lease.origin_ref == lease.origin_ref, Lease.lease_ref == lease.lease_ref)).first()
if entity is None: if entity is None:
if lease.lease_updated is None: if lease.lease_updated is None:
lease.lease_updated = lease.lease_created lease.lease_updated = lease.lease_created
session.add(lease) session.add(lease)
else: else:
x = dict(origin_ref=lease.origin_ref, lease_expires=lease.lease_expires, lease_updated=lease.lease_updated) values = dict(lease_expires=lease.lease_expires, lease_updated=lease.lease_updated)
session.execute(update(Lease).where(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(**values))
session.commit() session.commit()
session.flush() session.flush()
session.close() session.close()
@ -125,8 +114,8 @@ class Lease(Base):
@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.datetime, lease_updated: datetime.datetime):
session = sessionmaker(bind=engine)() session = sessionmaker(bind=engine)()
x = dict(lease_expires=lease.lease_expires, lease_updated=lease.lease_updated) values = dict(lease_expires=lease.lease_expires, lease_updated=lease.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(**values))
session.commit() session.commit()
session.close() session.close()
@ -138,14 +127,6 @@ class Lease(Base):
session.close() session.close()
return deletions return deletions
@staticmethod
def delete(engine: Engine, lease_ref: str) -> int:
session = sessionmaker(bind=engine)()
deletions = session.query(Lease).filter(Lease.lease_ref == lease_ref).delete()
session.commit()
session.close()
return deletions
def init(engine: Engine): def init(engine: Engine):
tables = [Origin, Lease] tables = [Origin, Lease]
@ -156,18 +137,3 @@ def init(engine: Engine):
session.execute(str(table.create_statement(engine))) session.execute(str(table.create_statement(engine)))
session.commit() session.commit()
session.close() session.close()
def migrate(engine: Engine):
db = inspect(engine)
def upgrade_1_0_to_1_1():
x = db.dialect.get_columns(engine.connect(), Lease.__tablename__)
x = next(_ for _ in x if _['name'] == 'origin_ref')
if x['primary_key'] > 0:
print('Found old database schema with "origin_ref" as primary-key in "lease" table. Dropping table!')
print(' Your leases are recreated on next renewal!')
print(' If an error message appears on the client, you can ignore it.')
Lease.__table__.drop(bind=engine)
upgrade_1_0_to_1_1()