forked from oscar.krause/fastapi-dls
Merge branch 'dev' into 'main'
1.2 See merge request oscar.krause/fastapi-dls!16
This commit is contained in:
commit
c894537ff9
@ -6,10 +6,12 @@ CONFIG_DIR=/etc/fastapi-dls
|
|||||||
echo "> Create config directory ..."
|
echo "> Create config directory ..."
|
||||||
mkdir -p $CONFIG_DIR
|
mkdir -p $CONFIG_DIR
|
||||||
|
|
||||||
|
# normally we would define services in `conffiles` and as separate file, but we like to keep thinks simple.
|
||||||
echo "> Install service ..."
|
echo "> Install service ..."
|
||||||
cat <<EOF >/etc/systemd/system/fastapi-dls.service
|
cat <<EOF >/etc/systemd/system/fastapi-dls.service
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Service for fastapi-dls
|
Description=Service for fastapi-dls
|
||||||
|
Documentation=https://git.collinwebdesigns.de/oscar.krause/fastapi-dls
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
@ -37,6 +39,7 @@ EOF
|
|||||||
|
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
# normally we would define configfiles in `conffiles` and as separate file, but we like to keep thinks simple.
|
||||||
if [[ ! -f $CONFIG_DIR/env ]]; then
|
if [[ ! -f $CONFIG_DIR/env ]]; then
|
||||||
echo "> Writing initial config ..."
|
echo "> Writing initial config ..."
|
||||||
touch $CONFIG_DIR/env
|
touch $CONFIG_DIR/env
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# Maintainer: samicrusader <hi@samicrusader.me>
|
|
||||||
# Maintainer: Oscar Krause <oscar.krause@collinwebdesigns.de>
|
# Maintainer: Oscar Krause <oscar.krause@collinwebdesigns.de>
|
||||||
|
# Contributor: samicrusader <hi@samicrusader.me>
|
||||||
|
|
||||||
pkgname=fastapi-dls
|
pkgname=fastapi-dls
|
||||||
pkgver=0.0
|
pkgver=1.1
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc='NVIDIA DLS server implementation with FastAPI'
|
pkgdesc='NVIDIA DLS server implementation with FastAPI'
|
||||||
arch=('any')
|
arch=('any')
|
||||||
@ -13,10 +13,12 @@ provider=("$pkgname")
|
|||||||
install="$pkgname.install"
|
install="$pkgname.install"
|
||||||
source=('git+file:///builds/oscar.krause/fastapi-dls' # https://gitea.publichub.eu/oscar.krause/fastapi-dls.git
|
source=('git+file:///builds/oscar.krause/fastapi-dls' # https://gitea.publichub.eu/oscar.krause/fastapi-dls.git
|
||||||
"$pkgname.default"
|
"$pkgname.default"
|
||||||
"$pkgname.service")
|
"$pkgname.service"
|
||||||
|
"$pkgname.tmpfiles")
|
||||||
sha256sums=('SKIP'
|
sha256sums=('SKIP'
|
||||||
'4c07e9b627853bd4f3a398371912fc72302dac33f43e4cb7e9b79746cc9c9136'
|
'fbd015449a30c0ae82733289a56eb98151dcfab66c91b37fe8e202e39f7a5edb'
|
||||||
'10cb98d64f8bf37b11a60510793c187cc664e63c895d1205781c21fa2e703f32')
|
'2719338541104c537453a65261c012dda58e1dbee99154cf4f33b526ee6ca22e'
|
||||||
|
'3dc60140c08122a8ec0e7fa7f0937eb8c1288058890ba09478420fc30ce9e30c')
|
||||||
|
|
||||||
pkgver() {
|
pkgver() {
|
||||||
source $srcdir/$pkgname/version.env
|
source $srcdir/$pkgname/version.env
|
||||||
@ -46,4 +48,5 @@ package() {
|
|||||||
install -Dm755 "$srcdir/$pkgname/app/util.py" "$pkgdir/opt/$pkgname/util.py"
|
install -Dm755 "$srcdir/$pkgname/app/util.py" "$pkgdir/opt/$pkgname/util.py"
|
||||||
install -Dm644 "$srcdir/$pkgname.default" "$pkgdir/etc/default/$pkgname"
|
install -Dm644 "$srcdir/$pkgname.default" "$pkgdir/etc/default/$pkgname"
|
||||||
install -Dm644 "$srcdir/$pkgname.service" "$pkgdir/usr/lib/systemd/system/$pkgname.service"
|
install -Dm644 "$srcdir/$pkgname.service" "$pkgdir/usr/lib/systemd/system/$pkgname.service"
|
||||||
|
install -Dm644 "$srcdir/$pkgname.tmpfiles" "$pkgdir/usr/lib/tmpfiles.d/$pkgname.conf"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ DEBUG=false
|
|||||||
|
|
||||||
# Where the client can find the DLS server
|
# Where the client can find the DLS server
|
||||||
## DLS_URL should be a hostname
|
## DLS_URL should be a hostname
|
||||||
|
LISTEN_IP="0.0.0.0"
|
||||||
DLS_URL="localhost.localdomain"
|
DLS_URL="localhost.localdomain"
|
||||||
DLS_PORT=8443
|
DLS_PORT=8443
|
||||||
CORS_ORIGINS="https://$DLS_URL:$DLS_PORT"
|
CORS_ORIGINS="https://$DLS_URL:$DLS_PORT"
|
||||||
@ -21,3 +22,7 @@ INSTANCE_REF="<<instanceref>>"
|
|||||||
# Site-wide signing keys
|
# Site-wide signing keys
|
||||||
INSTANCE_KEY_RSA="/var/lib/fastapi-dls/instance.private.pem"
|
INSTANCE_KEY_RSA="/var/lib/fastapi-dls/instance.private.pem"
|
||||||
INSTANCE_KEY_PUB="/var/lib/fastapi-dls/instance.public.pem"
|
INSTANCE_KEY_PUB="/var/lib/fastapi-dls/instance.public.pem"
|
||||||
|
|
||||||
|
# TLS certificate
|
||||||
|
INSTANCE_SSL_CERT="/var/lib/fastapi-dls/cert/webserver.crt"
|
||||||
|
INSTANCE_SSL_KEY="/var/lib/fastapi-dls/cert/webserver.key"
|
||||||
|
@ -4,12 +4,13 @@ Documentation=https://git.collinwebdesigns.de/oscar.krause/fastapi-dls
|
|||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=forking
|
Type=simple
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
EnvironmentFile=/etc/default/fastapi-dls
|
EnvironmentFile=/etc/default/fastapi-dls
|
||||||
ExecStart=/usr/bin/python /opt/fastapi-dls/main.py
|
ExecStart=/usr/bin/uvicorn main:app --proxy-headers --env-file=/etc/default/fastapi-dls --host=${LISTEN_IP} --port=${DLS_PORT} --app-dir=/opt/fastapi-dls --ssl-keyfile=${INSTANCE_SSL_KEY} --ssl-certfile=${INSTANCE_SSL_CERT}
|
||||||
WorkingDir=/opt/fastapi-dls
|
|
||||||
Restart=on-abort
|
Restart=on-abort
|
||||||
User=root
|
User=http
|
||||||
|
Group=http
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
2
.PKGBUILD/fastapi-dls.tmpfiles
Normal file
2
.PKGBUILD/fastapi-dls.tmpfiles
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
d /var/lib/fastapi-dls 0755 http http
|
||||||
|
d /var/lib/fastapi-dls/cert 0755 http http
|
@ -275,9 +275,12 @@ release:
|
|||||||
when: never
|
when: never
|
||||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
before_script:
|
before_script:
|
||||||
|
- set -a # make variables from "source" command available to release-cli
|
||||||
- source version.env
|
- source version.env
|
||||||
script:
|
script:
|
||||||
- echo "Running release-job for $VERSION"
|
- echo "Running release-job for $VERSION"
|
||||||
|
after_script:
|
||||||
|
- set +a
|
||||||
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
|
||||||
|
12
README.md
12
README.md
@ -287,12 +287,14 @@ After first success you have to replace `--issue` with `--renew`.
|
|||||||
| `DLS_PORT` | `443` | Used in client-token to tell guest driver where dls instance is reachable |
|
| `DLS_PORT` | `443` | Used in client-token to tell guest driver where dls instance is reachable |
|
||||||
| `LEASE_EXPIRE_DAYS` | `90` | Lease time in days |
|
| `LEASE_EXPIRE_DAYS` | `90` | Lease time in days |
|
||||||
| `DATABASE` | `sqlite:///db.sqlite` | See [official SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/core/engines.html) |
|
| `DATABASE` | `sqlite:///db.sqlite` | See [official SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/core/engines.html) |
|
||||||
| `CORS_ORIGINS` | `https://{DLS_URL}` | Sets `Access-Control-Allow-Origin` header (comma separated string) |
|
| `CORS_ORIGINS` | `https://{DLS_URL}` | Sets `Access-Control-Allow-Origin` header (comma separated string) \* |
|
||||||
| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid |
|
| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid |
|
||||||
| `INSTANCE_REF` | `00000000-0000-0000-0000-000000000000` | Instance identification uuid |
|
| `INSTANCE_REF` | `00000000-0000-0000-0000-000000000000` | Instance 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 |
|
||||||
| `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 |
|
||||||
|
|
||||||
|
\* Always use `https`, since guest-drivers only support secure connections!
|
||||||
|
|
||||||
# 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.**
|
||||||
@ -316,6 +318,14 @@ nvidia-smi -q | grep "License"
|
|||||||
Download file and place it into `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`.
|
Download file and place it into `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`.
|
||||||
Now restart `NvContainerLocalSystem` service.
|
Now restart `NvContainerLocalSystem` service.
|
||||||
|
|
||||||
|
**Power-Shell**
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
curl.exe --insecure -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"
|
||||||
|
```
|
||||||
|
|
||||||
# Troubleshoot
|
# Troubleshoot
|
||||||
|
|
||||||
## Linux
|
## Linux
|
||||||
|
90
app/main.py
90
app/main.py
@ -40,8 +40,7 @@ INSTANCE_KEY_RSA = load_key(str(env('INSTANCE_KEY_RSA', join(dirname(__file__),
|
|||||||
INSTANCE_KEY_PUB = load_key(str(env('INSTANCE_KEY_PUB', join(dirname(__file__), 'cert/instance.public.pem'))))
|
INSTANCE_KEY_PUB = load_key(str(env('INSTANCE_KEY_PUB', join(dirname(__file__), 'cert/instance.public.pem'))))
|
||||||
TOKEN_EXPIRE_DELTA = relativedelta(hours=1) # days=1
|
TOKEN_EXPIRE_DELTA = relativedelta(hours=1) # days=1
|
||||||
LEASE_EXPIRE_DELTA = relativedelta(days=int(env('LEASE_EXPIRE_DAYS', 90)))
|
LEASE_EXPIRE_DELTA = relativedelta(days=int(env('LEASE_EXPIRE_DAYS', 90)))
|
||||||
|
CORS_ORIGINS = str(env('CORS_ORIGINS', '')).split(',') if (env('CORS_ORIGINS')) else [f'https://{DLS_URL}']
|
||||||
CORS_ORIGINS = env('CORS_ORIGINS').split(',') if (env('CORS_ORIGINS')) else f'https://{DLS_URL}' # todo: prevent static https
|
|
||||||
|
|
||||||
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)
|
||||||
jwt_decode_key = jwk.construct(INSTANCE_KEY_PUB.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256)
|
jwt_decode_key = jwk.construct(INSTANCE_KEY_PUB.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256)
|
||||||
@ -51,29 +50,34 @@ app.add_middleware(
|
|||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=CORS_ORIGINS,
|
allow_origins=CORS_ORIGINS,
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=['*'],
|
||||||
allow_headers=["*"],
|
allow_headers=['*'],
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
|
logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
def get_token(request: Request) -> dict:
|
def __get_token(request: Request) -> dict:
|
||||||
authorization_header = request.headers['authorization']
|
authorization_header = request.headers.get('authorization')
|
||||||
token = authorization_header.split(' ')[1]
|
token = authorization_header.split(' ')[1]
|
||||||
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('/', summary='Index')
|
||||||
async def index():
|
async def index():
|
||||||
return RedirectResponse('/-/readme')
|
return RedirectResponse('/-/readme')
|
||||||
|
|
||||||
|
|
||||||
@app.get('/status', summary='* Status', description='Returns current service status, version (incl. git-commit) and some variables.', deprecated=True)
|
@app.get('/status', summary='* Status', description='returns current service status, version (incl. git-commit) and some variables.', deprecated=True)
|
||||||
async def status(request: Request):
|
async def status(request: Request):
|
||||||
return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG})
|
return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/-/', summary='* Index')
|
||||||
|
async def _index():
|
||||||
|
return RedirectResponse('/-/readme')
|
||||||
|
|
||||||
|
|
||||||
@app.get('/-/health', summary='* Health')
|
@app.get('/-/health', summary='* Health')
|
||||||
async def _health(request: Request):
|
async def _health(request: Request):
|
||||||
return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG})
|
return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG})
|
||||||
@ -161,8 +165,8 @@ async def _lease_delete(request: Request, lease_ref: str):
|
|||||||
|
|
||||||
|
|
||||||
# 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', summary='* Client-Token')
|
@app.get('/-/client-token', summary='* Client-Token', description='creates a new messenger token for this service instance')
|
||||||
async def client_token():
|
async def _client_token():
|
||||||
cur_time = datetime.utcnow()
|
cur_time = datetime.utcnow()
|
||||||
exp_time = cur_time + relativedelta(years=12)
|
exp_time = cur_time + relativedelta(years=12)
|
||||||
|
|
||||||
@ -200,15 +204,20 @@ async def client_token():
|
|||||||
content = jws.sign(payload, key=jwt_encode_key, headers=None, algorithm=ALGORITHMS.RS256)
|
content = jws.sign(payload, key=jwt_encode_key, headers=None, algorithm=ALGORITHMS.RS256)
|
||||||
|
|
||||||
response = StreamingResponse(iter([content]), media_type="text/plain")
|
response = StreamingResponse(iter([content]), media_type="text/plain")
|
||||||
filename = f'client_configuration_token_{datetime.now().strftime("%d-%m-%y-%H-%M-%S")}'
|
filename = f'client_configuration_token_{datetime.now().strftime("%d-%m-%y-%H-%M-%S")}.tok'
|
||||||
response.headers["Content-Disposition"] = f'attachment; filename={filename}'
|
response.headers["Content-Disposition"] = f'attachment; filename={filename}'
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/client-token', summary='* Client-Token', description='creates a new messenger token for this service instance', deprecated=True)
|
||||||
|
async def client_token():
|
||||||
|
return RedirectResponse('/-/client-token')
|
||||||
|
|
||||||
|
|
||||||
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py
|
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py
|
||||||
# {"candidate_origin_ref":"00112233-4455-6677-8899-aabbccddeeff","environment":{"fingerprint":{"mac_address_list":["ff:ff:ff:ff:ff:ff"]},"hostname":"my-hostname","ip_address_list":["192.168.178.123","fe80::","fe80::1%enp6s18"],"guest_driver_version":"510.85.02","os_platform":"Debian GNU/Linux 11 (bullseye) 11","os_version":"11 (bullseye)"},"registration_pending":false,"update_pending":false}
|
# {"candidate_origin_ref":"00112233-4455-6677-8899-aabbccddeeff","environment":{"fingerprint":{"mac_address_list":["ff:ff:ff:ff:ff:ff"]},"hostname":"my-hostname","ip_address_list":["192.168.178.123","fe80::","fe80::1%enp6s18"],"guest_driver_version":"510.85.02","os_platform":"Debian GNU/Linux 11 (bullseye) 11","os_version":"11 (bullseye)"},"registration_pending":false,"update_pending":false}
|
||||||
@app.post('/auth/v1/origin')
|
@app.post('/auth/v1/origin', description='find or create an origin')
|
||||||
async def auth_v1_origin(request: Request):
|
async def auth_v1_origin(request: Request):
|
||||||
j, cur_time = json.loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
j, cur_time = json.loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
||||||
|
|
||||||
@ -239,7 +248,7 @@ async def auth_v1_origin(request: Request):
|
|||||||
|
|
||||||
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py
|
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py
|
||||||
# { "environment" : { "guest_driver_version" : "guest_driver_version", "hostname" : "myhost", "ip_address_list" : [ "192.168.1.129" ], "os_version" : "os_version", "os_platform" : "os_platform", "fingerprint" : { "mac_address_list" : [ "e4:b9:7a:e5:7b:ff" ] }, "host_driver_version" : "host_driver_version" }, "origin_ref" : "00112233-4455-6677-8899-aabbccddeeff" }
|
# { "environment" : { "guest_driver_version" : "guest_driver_version", "hostname" : "myhost", "ip_address_list" : [ "192.168.1.129" ], "os_version" : "os_version", "os_platform" : "os_platform", "fingerprint" : { "mac_address_list" : [ "e4:b9:7a:e5:7b:ff" ] }, "host_driver_version" : "host_driver_version" }, "origin_ref" : "00112233-4455-6677-8899-aabbccddeeff" }
|
||||||
@app.post('/auth/v1/origin/update')
|
@app.post('/auth/v1/origin/update', description='update an origin evidence')
|
||||||
async def auth_v1_origin_update(request: Request):
|
async def auth_v1_origin_update(request: Request):
|
||||||
j, cur_time = json.loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
j, cur_time = json.loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
||||||
|
|
||||||
@ -267,7 +276,7 @@ async def auth_v1_origin_update(request: Request):
|
|||||||
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py
|
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py
|
||||||
# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - CodeResponse
|
# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - CodeResponse
|
||||||
# {"code_challenge":"...","origin_ref":"00112233-4455-6677-8899-aabbccddeeff"}
|
# {"code_challenge":"...","origin_ref":"00112233-4455-6677-8899-aabbccddeeff"}
|
||||||
@app.post('/auth/v1/code')
|
@app.post('/auth/v1/code', description='get an authorization code')
|
||||||
async def auth_v1_code(request: Request):
|
async def auth_v1_code(request: Request):
|
||||||
j, cur_time = json.loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
j, cur_time = json.loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
||||||
|
|
||||||
@ -300,7 +309,7 @@ async def auth_v1_code(request: Request):
|
|||||||
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py
|
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py
|
||||||
# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - TokenResponse
|
# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - TokenResponse
|
||||||
# {"auth_code":"...","code_verifier":"..."}
|
# {"auth_code":"...","code_verifier":"..."}
|
||||||
@app.post('/auth/v1/token')
|
@app.post('/auth/v1/token', description='exchange auth code and verifier for token')
|
||||||
async def auth_v1_token(request: Request):
|
async def auth_v1_token(request: Request):
|
||||||
j, cur_time = json.loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
j, cur_time = json.loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
||||||
payload = jwt.decode(token=j['auth_code'], key=jwt_decode_key)
|
payload = jwt.decode(token=j['auth_code'], key=jwt_decode_key)
|
||||||
@ -337,11 +346,11 @@ async def auth_v1_token(request: Request):
|
|||||||
|
|
||||||
|
|
||||||
# {'fulfillment_context': {'fulfillment_class_ref_list': []}, 'lease_proposal_list': [{'license_type_qualifiers': {'count': 1}, 'product': {'name': 'NVIDIA RTX Virtual Workstation'}}], 'proposal_evaluation_mode': 'ALL_OF', 'scope_ref_list': ['00112233-4455-6677-8899-aabbccddeeff']}
|
# {'fulfillment_context': {'fulfillment_class_ref_list': []}, 'lease_proposal_list': [{'license_type_qualifiers': {'count': 1}, 'product': {'name': 'NVIDIA RTX Virtual Workstation'}}], 'proposal_evaluation_mode': 'ALL_OF', 'scope_ref_list': ['00112233-4455-6677-8899-aabbccddeeff']}
|
||||||
@app.post('/leasing/v1/lessor')
|
@app.post('/leasing/v1/lessor', description='request multiple leases (borrow) for current origin')
|
||||||
async def leasing_v1_lessor(request: Request):
|
async def leasing_v1_lessor(request: Request):
|
||||||
j, token, cur_time = json.loads((await request.body()).decode('utf-8')), get_token(request), datetime.utcnow()
|
j, token, cur_time = json.loads((await request.body()).decode('utf-8')), __get_token(request), datetime.utcnow()
|
||||||
|
|
||||||
origin_ref = token['origin_ref']
|
origin_ref = token.get('origin_ref')
|
||||||
scope_ref_list = j['scope_ref_list']
|
scope_ref_list = j['scope_ref_list']
|
||||||
logging.info(f'> [ create ]: {origin_ref}: create leases for scope_ref_list {scope_ref_list}')
|
logging.info(f'> [ create ]: {origin_ref}: create leases for scope_ref_list {scope_ref_list}')
|
||||||
|
|
||||||
@ -377,11 +386,11 @@ async def leasing_v1_lessor(request: Request):
|
|||||||
|
|
||||||
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
|
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
|
||||||
# venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql
|
# venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql
|
||||||
@app.get('/leasing/v1/lessor/leases')
|
@app.get('/leasing/v1/lessor/leases', description='get active leases for current origin')
|
||||||
async def leasing_v1_lessor_lease(request: Request):
|
async def leasing_v1_lessor_lease(request: Request):
|
||||||
token, cur_time = get_token(request), datetime.utcnow()
|
token, cur_time = __get_token(request), datetime.utcnow()
|
||||||
|
|
||||||
origin_ref = token['origin_ref']
|
origin_ref = token.get('origin_ref')
|
||||||
|
|
||||||
active_lease_list = list(map(lambda x: x.lease_ref, Lease.find_by_origin_ref(db, origin_ref)))
|
active_lease_list = list(map(lambda x: x.lease_ref, Lease.find_by_origin_ref(db, origin_ref)))
|
||||||
logging.info(f'> [ leases ]: {origin_ref}: found {len(active_lease_list)} active leases')
|
logging.info(f'> [ leases ]: {origin_ref}: found {len(active_lease_list)} active leases')
|
||||||
@ -396,11 +405,11 @@ async def leasing_v1_lessor_lease(request: Request):
|
|||||||
|
|
||||||
|
|
||||||
# venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py
|
# venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py
|
||||||
@app.put('/leasing/v1/lease/{lease_ref}')
|
@app.put('/leasing/v1/lease/{lease_ref}', description='renew a lease')
|
||||||
async def leasing_v1_lease_renew(request: Request, lease_ref: str):
|
async def leasing_v1_lease_renew(request: Request, lease_ref: str):
|
||||||
token, cur_time = get_token(request), datetime.utcnow()
|
token, cur_time = __get_token(request), datetime.utcnow()
|
||||||
|
|
||||||
origin_ref = token['origin_ref']
|
origin_ref = token.get('origin_ref')
|
||||||
logging.info(f'> [ renew ]: {origin_ref}: renew {lease_ref}')
|
logging.info(f'> [ renew ]: {origin_ref}: renew {lease_ref}')
|
||||||
|
|
||||||
entity = Lease.find_by_origin_ref_and_lease_ref(db, origin_ref, lease_ref)
|
entity = Lease.find_by_origin_ref_and_lease_ref(db, origin_ref, lease_ref)
|
||||||
@ -422,11 +431,36 @@ async def leasing_v1_lease_renew(request: Request, lease_ref: str):
|
|||||||
return JSONResponse(response)
|
return JSONResponse(response)
|
||||||
|
|
||||||
|
|
||||||
@app.delete('/leasing/v1/lessor/leases')
|
@app.delete('/leasing/v1/lease/{lease_ref}', description='release (return) a lease')
|
||||||
async def leasing_v1_lessor_lease_remove(request: Request):
|
async def leasing_v1_lease_delete(request: Request, lease_ref: str):
|
||||||
token, cur_time = get_token(request), datetime.utcnow()
|
token, cur_time = __get_token(request), datetime.utcnow()
|
||||||
|
|
||||||
origin_ref = token['origin_ref']
|
origin_ref = token.get('origin_ref')
|
||||||
|
logging.info(f'> [ return ]: {origin_ref}: return {lease_ref}')
|
||||||
|
|
||||||
|
entity = Lease.find_by_lease_ref(db, lease_ref)
|
||||||
|
if entity.origin_ref != origin_ref:
|
||||||
|
raise HTTPException(status_code=403, detail='access or operation forbidden')
|
||||||
|
if entity is None:
|
||||||
|
raise HTTPException(status_code=404, detail='requested lease not available')
|
||||||
|
|
||||||
|
if Lease.delete(db, lease_ref) == 0:
|
||||||
|
raise HTTPException(status_code=404, detail='lease not found')
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"lease_ref": lease_ref,
|
||||||
|
"prompts": None,
|
||||||
|
"sync_timestamp": cur_time.isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSONResponse(response)
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete('/leasing/v1/lessor/leases', description='release all leases')
|
||||||
|
async def leasing_v1_lessor_lease_remove(request: Request):
|
||||||
|
token, cur_time = __get_token(request), datetime.utcnow()
|
||||||
|
|
||||||
|
origin_ref = token.get('origin_ref')
|
||||||
|
|
||||||
released_lease_list = list(map(lambda x: x.lease_ref, Lease.find_by_origin_ref(db, origin_ref)))
|
released_lease_list = list(map(lambda x: x.lease_ref, Lease.find_by_origin_ref(db, origin_ref)))
|
||||||
deletions = Lease.cleanup(db, origin_ref)
|
deletions = Lease.cleanup(db, origin_ref)
|
||||||
|
@ -115,6 +115,13 @@ class Lease(Base):
|
|||||||
session.close()
|
session.close()
|
||||||
return entities
|
return entities
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def find_by_lease_ref(engine: Engine, lease_ref: str) -> "Lease":
|
||||||
|
session = sessionmaker(bind=engine)()
|
||||||
|
entity = session.query(Lease).filter(Lease.lease_ref == lease_ref).first()
|
||||||
|
session.close()
|
||||||
|
return entity
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_by_origin_ref_and_lease_ref(engine: Engine, origin_ref: str, lease_ref: str) -> "Lease":
|
def find_by_origin_ref_and_lease_ref(engine: Engine, origin_ref: str, lease_ref: str) -> "Lease":
|
||||||
session = sessionmaker(bind=engine)()
|
session = sessionmaker(bind=engine)()
|
||||||
@ -125,7 +132,7 @@ 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)
|
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))
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
|
29
app/util.py
29
app/util.py
@ -1,21 +1,28 @@
|
|||||||
try:
|
|
||||||
# Crypto | Cryptodome on Debian
|
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
from Crypto.PublicKey.RSA import RsaKey
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
from Cryptodome.PublicKey import RSA
|
|
||||||
from Cryptodome.PublicKey.RSA import RsaKey
|
|
||||||
|
|
||||||
|
|
||||||
def load_file(filename) -> bytes:
|
def load_file(filename) -> bytes:
|
||||||
with open(filename, 'rb') as file:
|
with open(filename, 'rb') as file:
|
||||||
content = file.read()
|
content = file.read()
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def load_key(filename) -> RsaKey:
|
def load_key(filename) -> "RsaKey":
|
||||||
|
try:
|
||||||
|
# Crypto | Cryptodome on Debian
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.PublicKey.RSA import RsaKey
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
from Cryptodome.PublicKey import RSA
|
||||||
|
from Cryptodome.PublicKey.RSA import RsaKey
|
||||||
|
|
||||||
return RSA.import_key(extern_key=load_file(filename), passphrase=None)
|
return RSA.import_key(extern_key=load_file(filename), passphrase=None)
|
||||||
|
|
||||||
|
|
||||||
def generate_key() -> RsaKey:
|
def generate_key() -> "RsaKey":
|
||||||
|
try:
|
||||||
|
# Crypto | Cryptodome on Debian
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.PublicKey.RSA import RsaKey
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
from Cryptodome.PublicKey import RSA
|
||||||
|
from Cryptodome.PublicKey.RSA import RsaKey
|
||||||
|
|
||||||
return RSA.generate(bits=2048)
|
return RSA.generate(bits=2048)
|
||||||
|
38
test/main.py
38
test/main.py
@ -16,7 +16,7 @@ sys.path.append('../')
|
|||||||
sys.path.append('../app')
|
sys.path.append('../app')
|
||||||
|
|
||||||
from app import main
|
from app import main
|
||||||
from app.util import generate_key, load_key
|
from app.util import load_key
|
||||||
|
|
||||||
client = TestClient(main.app)
|
client = TestClient(main.app)
|
||||||
|
|
||||||
@ -33,6 +33,12 @@ jwt_encode_key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), al
|
|||||||
jwt_decode_key = jwk.construct(INSTANCE_KEY_PUB.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256)
|
jwt_decode_key = jwk.construct(INSTANCE_KEY_PUB.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256)
|
||||||
|
|
||||||
|
|
||||||
|
def __bearer_token(origin_ref: str) -> str:
|
||||||
|
token = jwt.encode({"origin_ref": origin_ref}, key=jwt_encode_key, algorithm=ALGORITHMS.RS256)
|
||||||
|
token = f'Bearer {token}'
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
def test_index():
|
def test_index():
|
||||||
response = client.get('/')
|
response = client.get('/')
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@ -61,6 +67,11 @@ def test_manage():
|
|||||||
|
|
||||||
|
|
||||||
def test_client_token():
|
def test_client_token():
|
||||||
|
response = client.get('/-/client-token')
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_token_deprecated():
|
||||||
response = client.get('/client-token')
|
response = client.get('/client-token')
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
@ -175,9 +186,7 @@ def test_leasing_v1_lessor():
|
|||||||
'scope_ref_list': [LEASE_REF]
|
'scope_ref_list': [LEASE_REF]
|
||||||
}
|
}
|
||||||
|
|
||||||
bearer_token = jwt.encode({"origin_ref": ORIGIN_REF}, key=jwt_encode_key, algorithm=ALGORITHMS.RS256)
|
response = client.post('/leasing/v1/lessor', json=payload, headers={'authorization': __bearer_token(ORIGIN_REF)})
|
||||||
bearer_token = f'Bearer {bearer_token}'
|
|
||||||
response = client.post('/leasing/v1/lessor', json=payload, headers={'authorization': bearer_token})
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
lease_result_list = response.json()['lease_result_list']
|
lease_result_list = response.json()['lease_result_list']
|
||||||
@ -186,9 +195,7 @@ def test_leasing_v1_lessor():
|
|||||||
|
|
||||||
|
|
||||||
def test_leasing_v1_lessor_lease():
|
def test_leasing_v1_lessor_lease():
|
||||||
bearer_token = jwt.encode({"origin_ref": ORIGIN_REF}, key=jwt_encode_key, algorithm=ALGORITHMS.RS256)
|
response = client.get('/leasing/v1/lessor/leases', headers={'authorization': __bearer_token(ORIGIN_REF)})
|
||||||
bearer_token = f'Bearer {bearer_token}'
|
|
||||||
response = client.get('/leasing/v1/lessor/leases', headers={'authorization': bearer_token})
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
active_lease_list = response.json()['active_lease_list']
|
active_lease_list = response.json()['active_lease_list']
|
||||||
@ -197,18 +204,23 @@ def test_leasing_v1_lessor_lease():
|
|||||||
|
|
||||||
|
|
||||||
def test_leasing_v1_lease_renew():
|
def test_leasing_v1_lease_renew():
|
||||||
bearer_token = jwt.encode({"origin_ref": ORIGIN_REF}, key=jwt_encode_key, algorithm=ALGORITHMS.RS256)
|
response = client.put(f'/leasing/v1/lease/{LEASE_REF}', headers={'authorization': __bearer_token(ORIGIN_REF)})
|
||||||
bearer_token = f'Bearer {bearer_token}'
|
assert response.status_code == 200
|
||||||
response = client.put(f'/leasing/v1/lease/{LEASE_REF}', headers={'authorization': bearer_token})
|
|
||||||
|
assert response.json()['lease_ref'] == LEASE_REF
|
||||||
|
|
||||||
|
|
||||||
|
def test_leasing_v1_lease_delete():
|
||||||
|
response = client.delete(f'/leasing/v1/lease/{LEASE_REF}', headers={'authorization': __bearer_token(ORIGIN_REF)})
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert response.json()['lease_ref'] == LEASE_REF
|
assert response.json()['lease_ref'] == LEASE_REF
|
||||||
|
|
||||||
|
|
||||||
def test_leasing_v1_lessor_lease_remove():
|
def test_leasing_v1_lessor_lease_remove():
|
||||||
bearer_token = jwt.encode({"origin_ref": ORIGIN_REF}, key=jwt_encode_key, algorithm=ALGORITHMS.RS256)
|
test_leasing_v1_lessor()
|
||||||
bearer_token = f'Bearer {bearer_token}'
|
|
||||||
response = client.delete('/leasing/v1/lessor/leases', headers={'authorization': bearer_token})
|
response = client.delete('/leasing/v1/lessor/leases', headers={'authorization': __bearer_token(ORIGIN_REF)})
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
released_lease_list = response.json()['released_lease_list']
|
released_lease_list = response.json()['released_lease_list']
|
||||||
|
@ -1 +1 @@
|
|||||||
VERSION=1.1
|
VERSION=1.2
|
||||||
|
Loading…
Reference in New Issue
Block a user