mirror of
				https://git.collinwebdesigns.de/oscar.krause/fastapi-dls.git
				synced 2025-10-26 18:05:28 +03:00 
			
		
		
		
	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() | ||||||
|  | |||||||
							
								
								
									
										25
									
								
								app/util.py
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								app/util.py
									
									
									
									
									
								
							| @ -1,3 +1,10 @@ | |||||||
|  | def load_file(filename) -> bytes: | ||||||
|  |     with open(filename, 'rb') as file: | ||||||
|  |         content = file.read() | ||||||
|  |     return content | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def load_key(filename) -> "RsaKey": | ||||||
|     try: |     try: | ||||||
|         # Crypto | Cryptodome on Debian |         # Crypto | Cryptodome on Debian | ||||||
|         from Crypto.PublicKey import RSA |         from Crypto.PublicKey import RSA | ||||||
| @ -6,16 +13,16 @@ except ModuleNotFoundError: | |||||||
|         from Cryptodome.PublicKey import RSA |         from Cryptodome.PublicKey import RSA | ||||||
|         from Cryptodome.PublicKey.RSA import RsaKey |         from Cryptodome.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) |     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
	 Oscar Krause
						Oscar Krause