diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..09c810c --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,7 @@ +plugins: + bandit: + enabled: true + sonar-python: + enabled: true + pylint: + enabled: true diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b35140d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,28 @@ +root = true + +[*] +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{js,py}] +charset = utf-8 + +# 4 space indentation +[*.py] +indent_style = space +indent_size = 4 + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab + +# Indentation override for all JS under lib directory +[lib/**.js] +indent_style = space +indent_size = 2 + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e1f44b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +venv/ +.idea/ +app/cert/*.* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..2a07981 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,30 @@ +cache: + key: one-key-to-rule-them-all + +build: + image: docker:dind + interruptible: true + stage: build + rules: + - if: $CI_COMMIT_BRANCH + tags: [ docker ] + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build . --tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${CI_BUILD_REF} + - docker push ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${CI_BUILD_REF} + +test: + stage: test + script: + - echo "Nothing to do ..." + +deploy: + stage: deploy + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + script: + - docker login -u $PUBLIC_REGISTRY_USER -p $PUBLIC_REGISTRY_TOKEN + - docker build . --tag $PUBLIC_REGISTRY_USER/${CI_PROJECT_PATH}:${CI_BUILD_REF} + - docker build . --tag $PUBLIC_REGISTRY_USER/${CI_PROJECT_PATH}:latest + - docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_PATH}:${CI_BUILD_REF} + - docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_PATH}:latest diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..78bae1b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.10-alpine + +COPY requirements.txt /tmp/requirements.txt + +RUN apk update \ + && apk add --no-cache --virtual build-deps gcc g++ python3-dev musl-dev \ + && apk add --no-cache postgresql postgresql-dev mariadb-connector-c-dev \ + && pip install --no-cache-dir --upgrade uvicorn \ + && pip install --no-cache-dir psycopg2==2.9.3 mysqlclient==2.1.1 pysqlite3==0.4.7 \ + && pip install --no-cache-dir -r /tmp/requirements.txt \ + && apk del build-deps + +COPY app /app + +HEALTHCHECK --start-period=30s --interval=10s --timeout=5s --retries=3 CMD curl --fail http://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"] diff --git a/README.md b/README.md index 5861a35..ed2697b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,78 @@ # FastAPI-DLS -Minimal Licencing Servie. +Minimal Delegated License Service (DLS). + +# Setup (Docker) + +**Run this on the Docker-Host** + +```shell +WORKING_DIR=/opt/docker/fastapi-dls/cert +mkdir -p $WORKING_DIR +cd $WORKING_DIR +openssl genrsa -out $WORKING_DIR/instance.private.pem 2048 +openssl rsa -in $WORKING_DIR/instance.private.pem -outform PEM -pubout -out $WORKING_DIR/instance.public.pem +openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $WORKING_DIR/webserver.key -out $WORKING_DIR/webserver.crt +docker run -e DLS_URL=`hostname -i` -e DLS_PORT=443 -p 443:443 -v $WORKING_DIR:/app/cert collinwebdesigns/fastapi-dls:latest +``` + +# Installation + +**The token file has to be copied! It's not enough to C&P file contents, because there can be special characters.** + +## Linux + +```shell +curl --insecure -X GET https:///client-token -o /etc/nvidia/ClientConfigToken/client_configuration_token.tok +service nvidia-gridd restart +nvidia-smi -q | grep "License" +``` + +## Windows + +Download file and place it into `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`. +Now restart `NvContainerLocalSystem` service. + +# Troubleshoot + +## Linux + +Logs are available with `journalctl -u nvidia-gridd -f`. + +## Windows + +Logs are available in `C:\Users\Public\Documents\Nvidia\LoggingLog.NVDisplay.Container.exe.log`. + +# Known Issues + +## Linux + +Currently, there are no known issues. + +## Windows + +On Windows there is currently a problem returning the license. As you can see the license is installed successfully +after +a few minutes. About the time of the first *lease period* the driver gets a *Mismatch between client and server with +respect to licenses held*. + +
+ Log + +``` +Tue Dec 20 05:55:52 2022:<2>:NLS initialized +Tue Dec 20 05:55:57 2022:<2>:Mismatch between client and server with respect to licenses held. Returning the licenses +Tue Dec 20 05:55:58 2022:<2>:License returned successfully. (Info: 192.168.178.33) +Tue Dec 20 05:56:20 2022:<2>:Mismatch between client and server with respect to licenses held. Returning the licenses +Tue Dec 20 05:56:21 2022:<2>:License returned successfully. (Info: 192.168.178.33) +Tue Dec 20 05:56:46 2022:<2>:Mismatch between client and server with respect to licenses held. Returning the licenses +Tue Dec 20 05:56:47 2022:<2>:License returned successfully. (Info: 192.168.178.33) +Tue Dec 20 05:56:54 2022:<1>:License renewed successfully. (Info: 192.168.178.33, NVIDIA RTX Virtual Workstation; Expiry: 2022-12-20 5:11:54 GMT) +Tue Dec 20 05:57:17 2022:<2>:Mismatch between client and server with respect to licenses held. Returning the licenses +Tue Dec 20 05:57:18 2022:<2>:License returned successfully. (Info: 192.168.178.33) +Tue Dec 20 05:59:20 2022:<1>:License renewed successfully. (Info: 192.168.178.33, NVIDIA RTX Virtual Workstation; Expiry: 2022-12-20 5:14:20 GMT) +Tue Dec 20 06:01:45 2022:<1>:License renewed successfully. (Info: 192.168.178.33, NVIDIA RTX Virtual Workstation; Expiry: 2022-12-20 5:16:45 GMT) +Tue Dec 20 06:04:10 2022:<1>:License renewed successfully. (Info: 192.168.178.33, NVIDIA RTX Virtual Workstation; Expiry: 2022-12-20 5:19:10 GMT) +``` + +
diff --git a/ToDo.md b/ToDo.md new file mode 100644 index 0000000..c87b012 --- /dev/null +++ b/ToDo.md @@ -0,0 +1,9 @@ +# FastAPI-DLS Server + + +## ToDo + +- Create Client Token (`.tok`-file) +- Docker Image +- Create Certificates if not exist +- Keep track of clients and Leases diff --git a/app/helper.py b/app/helper.py new file mode 100644 index 0000000..b416ab3 --- /dev/null +++ b/app/helper.py @@ -0,0 +1,20 @@ +from Crypto.PublicKey import RSA +from Crypto.PublicKey.RSA import RsaKey + + +def load_file(filename) -> bytes: + with open(filename, 'rb') as file: + content = file.read() + return content + + +def load_key(filename) -> RsaKey: + return RSA.import_key(extern_key=load_file(filename), passphrase=None) + + +def private_bytes(rsa: RsaKey) -> bytes: + return rsa.export_key(format='PEM', passphrase=None, protection=None) + + +def public_key(rsa: RsaKey) -> bytes: + return rsa.public_key().export_key(format='PEM') diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..42ac2d2 --- /dev/null +++ b/app/main.py @@ -0,0 +1,295 @@ +from base64 import b64encode +from hashlib import sha256 +from uuid import uuid4 +from os.path import join, dirname +from os import getenv +from fastapi import FastAPI, HTTPException +from fastapi.requests import Request +import json +from datetime import datetime +from dateutil.relativedelta import relativedelta +from calendar import timegm +from jose import jws, jwk, jwt +from jose.constants import ALGORITHMS +from starlette.responses import StreamingResponse, JSONResponse + +from helper import load_key, private_bytes, public_key + +# todo: initialize certificate (or should be done by user, and passed through "volumes"?) + +app = FastAPI() + +LEASE_EXPIRE_DELTA = relativedelta(minutes=15) # days=90 + +DLS_URL = str(getenv('DLS_URL', 'localhost')) +DLS_PORT = int(getenv('DLS_PORT', '443')) +SITE_KEY_XID = getenv('SITE_KEY_XID', '00000000-0000-0000-0000-000000000000') +INSTANCE_KEY_RSA = load_key(join(dirname(__file__), 'cert/instance.private.pem')) +INSTANCE_KEY_PUB = load_key(join(dirname(__file__), 'cert/instance.public.pem')) + + +@app.get('/') +async def index(): + return JSONResponse({'hello': 'world'}) + + +@app.get('/status') +async def status(request: Request): + return JSONResponse({'status': 'up'}) + + +# venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py +@app.get('/client-token') +async def client_token(): + cur_time = datetime.utcnow() + exp_time = cur_time + relativedelta(years=12) + + service_instance_public_key_configuration = { + "service_instance_public_key_me": { + "mod": hex(INSTANCE_KEY_PUB.public_key().n)[2:], + "exp": INSTANCE_KEY_PUB.public_key().e, + }, + "service_instance_public_key_pem": INSTANCE_KEY_PUB.export_key().decode('utf-8'), + "key_retention_mode": "LATEST_ONLY" + } + + payload = { + "jti": str(uuid4()), + "iss": "NLS Service Instance", + "aud": "NLS Licensed Client", + "iat": timegm(cur_time.timetuple()), + "nbf": timegm(cur_time.timetuple()), + "exp": timegm(exp_time.timetuple()), + "update_mode": "ABSOLUTE", + "scope_ref_list": [str(uuid4())], + "fulfillment_class_ref_list": [], + "service_instance_configuration": { + "nls_service_instance_ref": "00000000-0000-0000-0000-000000000000", + "svc_port_set_list": [ + { + "idx": 0, + "d_name": "DLS", + "svc_port_map": [ + {"service": "auth", "port": DLS_PORT}, + {"service": "lease", "port": DLS_PORT} + ] + } + ], + "node_url_list": [{"idx": 0, "url": DLS_URL, "url_qr": DLS_URL, "svc_port_set_idx": 0}] + }, + "service_instance_public_key_configuration": service_instance_public_key_configuration, + } + + key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256) + data = jws.sign(payload, key=key, headers=None, algorithm='RS256') + + response = StreamingResponse(iter([data]), media_type="text/plain") + filename = f'client_configuration_token_{datetime.now().strftime("%d-%m-%y-%H-%M-%S")}' + response.headers["Content-Disposition"] = f'attachment; filename={filename}' + return response + + +# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py +@app.post('/auth/v1/origin') +async def auth_origin(request: Request): + body = await request.body() + body = body.decode('utf-8') + j = json.loads(body) + # {"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} + print(f'> [ origin ]: {j}') + + cur_time = datetime.utcnow() + response = { + "origin_ref": j['candidate_origin_ref'], + "environment": j['environment'], + "svc_port_set_list": None, + "node_url_list": None, + "node_query_order": None, + "prompts": None, + "sync_timestamp": cur_time.isoformat() + } + return JSONResponse(response) + + +# 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 +@app.post('/auth/v1/code') +async def auth_code(request: Request): + body = await request.body() + body = body.decode('utf-8') + j = json.loads(body) + # {"code_challenge":"...","origin_ref":"00112233-4455-6677-8899-aabbccddeeff"} + print(f'> [ code ]: {j}') + + cur_time = datetime.utcnow() + expires = cur_time + relativedelta(days=1) + + payload = { + 'iat': timegm(cur_time.timetuple()), + 'exp': timegm(expires.timetuple()), + 'challenge': j['code_challenge'], + 'origin_ref': j['code_challenge'], + 'key_ref': SITE_KEY_XID, + 'kid': SITE_KEY_XID + } + + headers = None + kid = payload.get('kid') + if kid: + headers = {'kid': kid} + key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS512) + auth_code = jws.sign(payload, key, headers=headers, algorithm='RS256') + + response = { + "auth_code": auth_code, + "sync_timestamp": cur_time.isoformat(), + "prompts": None + } + return JSONResponse(response) + + +# 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 +@app.post('/auth/v1/token') +async def auth_token(request: Request): + body = await request.body() + body = body.decode('utf-8') + j = json.loads(body) + # {"auth_code":"...","code_verifier":"..."} + + # payload = self._security.get_valid_payload(req.auth_code) # todo + key = jwk.construct(INSTANCE_KEY_PUB.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS512) + payload = jwt.decode(token=j['auth_code'], key=key) + + # validate the code challenge + if payload['challenge'] != b64encode(sha256(j['code_verifier'].encode('utf-8')).digest()).rstrip(b'=').decode('utf-8'): + raise HTTPException(status_code=403, detail='expected challenge did not match verifier') + + cur_time = datetime.utcnow() + access_expires_on = cur_time + relativedelta(days=1) + + new_payload = { + 'iat': timegm(cur_time.timetuple()), + 'nbf': timegm(cur_time.timetuple()), + 'iss': 'https://cls.nvidia.org', + 'aud': 'https://cls.nvidia.org', + 'exp': timegm(access_expires_on.timetuple()), + 'origin_ref': payload['origin_ref'], + 'key_ref': SITE_KEY_XID, + 'kid': SITE_KEY_XID, + } + + headers = None + kid = payload.get('kid') + if kid: + headers = {'kid': kid} + key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS512) + auth_token = jwt.encode(new_payload, key=key, headers=headers, algorithm='RS256') + + response = { + "expires": access_expires_on.isoformat(), + "auth_token": auth_token, + "sync_timestamp": cur_time.isoformat(), + } + + return JSONResponse(response) + + +@app.post('/leasing/v1/lessor') +async def leasing_lessor(request: Request): + body = await request.body() + body = body.decode('utf-8') + j = json.loads(body) + # {'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']} + print(f'> [ lessor ]: {j}') + + cur_time = datetime.utcnow() + # todo: keep track of leases, to return correct list on '/leasing/v1/lessor/leases' + lease_result_list = [] + for scope_ref in j['scope_ref_list']: + lease_result_list.append({ + "ordinal": 0, + "lease": { + "ref": scope_ref, + "created": cur_time.isoformat(), + "expires": (cur_time + LEASE_EXPIRE_DELTA).isoformat(), + "recommended_lease_renewal": 0.15, + "offline_lease": "true", + "license_type": "CONCURRENT_COUNTED_SINGLE" + } + }) + + response = { + "lease_result_list": lease_result_list, + "result_code": "SUCCESS", + "sync_timestamp": cur_time.isoformat(), + "prompts": None + } + + return JSONResponse(response) + + +# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py +@app.get('/leasing/v1/lessor/leases') +async def leasing_lessor_lease(request: Request): + cur_time = datetime.utcnow() + # venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql + response = { + # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE + "active_lease_list": [ + "BE276D7B-2CDB-11EC-9838-061A22468B59" + ], + "sync_timestamp": cur_time.isoformat(), + "prompts": None + } + + return JSONResponse(response) + + +# venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py +@app.put('/leasing/v1/lease/{lease_ref}') +async def leasing_lease_renew(request: Request, lease_ref: str): + print(f'> [ renew ]: lease: {lease_ref}') + + cur_time = datetime.utcnow() + response = { + "lease_ref": lease_ref, + "expires": (cur_time + LEASE_EXPIRE_DELTA).isoformat(), + "recommended_lease_renewal": 0.16, + "offline_lease": True, + "prompts": None, + "sync_timestamp": cur_time.isoformat(), + } + + return JSONResponse(response) + + +@app.delete('/leasing/v1/lessor/leases') +async def leasing_lessor_lease_remove(request: Request): + cur_time = datetime.utcnow() + response = { + "released_lease_list": None, + "release_failure_list": None, + "sync_timestamp": cur_time.isoformat(), + "prompts": None + } + return JSONResponse(response) + + +if __name__ == '__main__': + import uvicorn + + ### + # + # Running `python app/main.py` assumes that the user created a keypair, e.g. with openssl. + # + # openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout app/cert/webserver.key -out app/cert/webserver.crt + # + ### + + print(f'> Starting dev-server ...') + + ssl_keyfile = join(dirname(__file__), 'cert/webserver.key') + ssl_certfile = join(dirname(__file__), 'cert/webserver.crt') + + uvicorn.run('main:app', host='0.0.0.0', port=443, ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, reload=True) diff --git a/doc/Reverse Engineering Notes.md b/doc/Reverse Engineering Notes.md new file mode 100644 index 0000000..6746cf7 --- /dev/null +++ b/doc/Reverse Engineering Notes.md @@ -0,0 +1,174 @@ +# Reverse Engineering Notes + +# Usefully commands + +## Check licensing status + +- `nvidia-smi -q | grep "License"` + +**Output** + +``` +vGPU Software Licensed Product + License Status : Licensed (Expiry: 2023-1-14 12:59:52 GMT) +``` + +## Track licensing progress + +- NVIDIA Grid Log: `journalctl -u nvidia-gridd -f` + +``` +systemd[1]: Started NVIDIA Grid Daemon. +nvidia-gridd[2986]: Configuration parameter ( ServerAddress ) not set +nvidia-gridd[2986]: vGPU Software package (0) +nvidia-gridd[2986]: Ignore service provider and node-locked licensing +nvidia-gridd[2986]: NLS initialized +nvidia-gridd[2986]: Acquiring license. (Info: license.nvidia.space; NVIDIA RTX Virtual Workstation) +nvidia-gridd[2986]: License acquired successfully. (Info: license.nvidia.space, NVIDIA RTX Virtual Workstation; Expiry: 2023-1-29 22:3:0 GMT) +``` + +# DLS-Container File-System (Docker) + +## Configuration data + +Most variables and configs are stored in `/var/lib/docker/volumes/configurations/_data`. + +## Dive / Docker image inspector + +- `dive dls:appliance` + +The source code is stored in `/venv/lib/python3.9/site-packages/nls_*`. + +Image-Reference: + +``` +Tags: (unavailable) +Id: d1c7976a5d2b3681ff6c5a30f8187e4015187a83f3f285ba4a37a45458bd6b98 +Digest: sha256:311223c5af7a298ec1104f5dc8c3019bfb0e1f77256dc3d995244ffb295a97 +1f +Command: +#(nop) ADD file:c1900d3e3a29c29a743a8da86c437006ec5d2aa873fb24e48033b6bf492bb37b in / +``` + +## Private Key (Site-Key) + +- `/etc/dls/config/decryptor/decryptor` + +```shell + docker exec -it /etc/dls/config/decryptor/decryptor > /tmp/private-key.pem +``` + +``` +-----BEGIN RSA PRIVATE KEY----- +... +-----END RSA PRIVATE KEY----- +``` + +## Site Key Uri - `/etc/dls/config/site_key_uri.bin` + +``` +base64-content... +``` + +## DB Password - `/etc/dls/config/dls_db_password.bin` + +``` +base64-content... +``` + +**Decrypt database password** + +``` +cd /var/lib/docker/volumes/configurations/_data +cat dls_db_password.bin | base64 -d > dls_db_password.bin.raw +openssl rsautl -decrypt -inkey /tmp/private-key.pem -in dls_db_password.bin.raw +``` + +# Database + +- It's enough to manipulate database licenses. There must not be changed any line of code to bypass licensing + validations. + +# Logging / Stack Trace + +- https://docs.nvidia.com/license-system/latest/nvidia-license-system-user-guide/index.html#troubleshooting-dls-instance + +**Failed licensing log** + +``` +{ + "activity": 100, + "context": { + "SERVICE_INSTANCE_ID": "b43d6e46-d6d0-4943-8b8d-c66a5f6e0d38", + "SERVICE_INSTANCE_NAME": "DEFAULT_2022-12-14_12:48:30", + "description": "borrow failed: NotFoundError(no pool features found for: NVIDIA RTX Virtual Workstation)", + "event_type": null, + "function_name": "_evt", + "lineno": 54, + "module_name": "nls_dal_lease_dls.event", + "operation_id": "e72a8ca7-34cc-4e11-b80c-273592085a24", + "origin_ref": "3f7f5a50-a26b-425b-8d5e-157f63e72b1c", + "service_name": "nls_services_lease" + }, + "detail": { + "oc": { + "license_allotment_xid": "10c4317f-7c4c-11ed-a524-0e4252a7e5f1", + "origin_ref": "3f7f5a50-a26b-425b-8d5e-157f63e72b1c", + "service_instance_xid": "b43d6e46-d6d0-4943-8b8d-c66a5f6e0d38" + }, + "operation_id": "e72a8ca7-34cc-4e11-b80c-273592085a24" + }, + "id": "0cc9e092-3b92-4652-8d9e-7622ef85dc79", + "metadata": {}, + "ts": "2022-12-15T10:25:36.827661Z" +} + +{ + "activity": 400, + "context": { + "SERVICE_INSTANCE_ID": "b43d6e46-d6d0-4943-8b8d-c66a5f6e0d38", + "SERVICE_INSTANCE_NAME": "DEFAULT_2022-12-14_12:48:30", + "description": "lease_multi_create failed: no pool features found for: NVIDIA RTX Virtual Workstation", + "event_by": "system", + "function_name": "lease_multi_create", + "level": "warning", + "lineno": 157, + "module_name": "nls_services_lease.controllers.lease_multi_controller", + "operation_id": "e72a8ca7-34cc-4e11-b80c-273592085a24", + "service_name": "nls_services_lease" + }, + "detail": { + "_msg": "lease_multi_create failed: no pool features found for: NVIDIA RTX Virtual Workstation", + "exec_info": ["NotFoundError", "NotFoundError(no pool features found for: NVIDIA RTX Virtual Workstation)", " File \"/venv/lib/python3.9/site-packages/nls_services_lease/controllers/lease_multi_controller.py\", line 127, in lease_multi_create\n data = _leaseMulti.lease_multi_create(event_args)\n File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 208, in lease_multi_create\n raise e\n File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 184, in lease_multi_create\n self._try_proposals(oc, mlr, results, detail)\n File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 219, in _try_proposals\n lease = self._leases.create(creator)\n File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 230, in create\n features = self._get_features(creator)\n File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 148, in _get_features\n self._explain_not_available(cur, creator)\n File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 299, in _explain_not_available\n raise NotFoundError(f'no pool features found for: {lcc.product_name}')\n"], + "operation_id": "e72a8ca7-34cc-4e11-b80c-273592085a24" + }, + "id": "282801b9-d612-40a5-9145-b56d8e420dac", + "metadata": {}, + "ts": "2022-12-15T10:25:36.831673Z" +} + +``` + +**Stack Trace** + +``` +"NotFoundError", "NotFoundError(no pool features found for: NVIDIA RTX Virtual Workstation)", " File \"/venv/lib/python3.9/site-packages/nls_services_lease/controllers/lease_multi_controller.py\", line 127, in lease_multi_create + data = _leaseMulti.lease_multi_create(event_args) + File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 208, in lease_multi_create + raise e + File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 184, in lease_multi_create + self._try_proposals(oc, mlr, results, detail) + File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 219, in _try_proposals + lease = self._leases.create(creator) + File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 230, in create + features = self._get_features(creator) + File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 148, in _get_features + self._explain_not_available(cur, creator) + File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 299, in _explain_not_available + raise NotFoundError(f'no pool features found for: {lcc.product_name}') +" +``` + +# Nginx + +- NGINX uses `/opt/certs/cert.pem` and `/opt/certs/key.pem` diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d0361f2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.88.0 +uvicorn[standard]==0.20.0 +python-jose==3.3.0 +pycryptodome==3.16.0 +python-dateutil==2.8.2