diff --git a/README.md b/README.md index 6597c19..e64effa 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ Only the clients need a connection to this service on configured port. Redirect to `/-/readme`. -### `GET /status` (deprecated: use `/-/health`) - -Status endpoint, used for *healthcheck*. Shows also current version and commit hash. - ### `GET /-/health` -Status endpoint, used for *healthcheck*. Shows also current version and commit hash. +Status endpoint, used for *healthcheck*. + +### `GET /-/config` + +Shows current runtime environment variables and their values. ### `GET /-/readme` @@ -63,7 +63,7 @@ List current leases. Deletes an lease. -### `GET /client-token` (deprecated: use `/-/client-token`) +### `GET /-/client-token` Generate client token, (see [installation](#installation)). @@ -282,20 +282,26 @@ After first success you have to replace `--issue` with `--renew`. # Configuration -| Variable | Default | Usage | -|---------------------|----------------------------------------|-------------------------------------------------------------------------------------| -| `DEBUG` | `false` | Toggles `fastapi` debug mode | -| `DLS_URL` | `localhost` | 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 | -| `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) \* | -| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid | -| `INSTANCE_REF` | `00000000-0000-0000-0000-000000000000` | Instance identification uuid | -| `INSTANCE_KEY_RSA` | `/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs | -| `INSTANCE_KEY_PUB` | `/cert/instance.public.pem` | Site-wide public key | +| Variable | Default | Usage | +|------------------------|----------------------------------------|------------------------------------------------------------------------------------------------------| +| `DEBUG` | `false` | Toggles `fastapi` debug mode | +| `DLS_URL` | `localhost` | 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_RENEWAL_PERIOD` | `0.15` | The percentage of the lease period that must elapse before a licensed client can renew a license \*1 | +| `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) \*2 | +| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid | +| `INSTANCE_REF` | `10000000-0000-0000-0000-000000000001` | Instance identification uuid | +| `ALLOTMENT_REF` | `20000000-0000-0000-0000-000000000001` | Allotment identification uuid | +| `INSTANCE_KEY_RSA` | `/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs | +| `INSTANCE_KEY_PUB` | `/cert/instance.public.pem` | Site-wide public key | -\* Always use `https`, since guest-drivers only support secure connections! +\*1 For example, if the lease period is one day and the renewal period is 20%, the client attempts to renew its license +every 4.8 hours. If network connectivity is lost, the loss of connectivity is detected during license renewal and the +client has 19.2 hours in which to re-establish connectivity before its license expires. + +\*2 Always use `https`, since guest-drivers only support secure connections! # Setup (Client) diff --git a/app/main.py b/app/main.py index 110ed2e..b413706 100644 --- a/app/main.py +++ b/app/main.py @@ -37,14 +37,17 @@ templates = Jinja2Templates(directory='templates') db = create_engine(str(env('DATABASE', 'sqlite:///db.sqlite'))) db_init(db), migrate(db) +# everything prefixed with "INSTANCE_*" is used as "SERVICE_INSTANCE_*" or "SI_*" in official dls service DLS_URL = str(env('DLS_URL', 'localhost')) DLS_PORT = int(env('DLS_PORT', '443')) SITE_KEY_XID = str(env('SITE_KEY_XID', '00000000-0000-0000-0000-000000000000')) -INSTANCE_REF = str(env('INSTANCE_REF', '00000000-0000-0000-0000-000000000000')) +INSTANCE_REF = str(env('INSTANCE_REF', '10000000-0000-0000-0000-000000000001')) +ALLOTMENT_REF = str(env('ALLOTMENT_REF', '20000000-0000-0000-0000-000000000001')) INSTANCE_KEY_RSA = load_key(str(env('INSTANCE_KEY_RSA', join(dirname(__file__), 'cert/instance.private.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 LEASE_EXPIRE_DELTA = relativedelta(days=int(env('LEASE_EXPIRE_DAYS', 90))) +LEASE_RENEWAL_PERIOD = float(env('LEASE_RENEWAL_PERIOD', 0.15)) CORS_ORIGINS = str(env('CORS_ORIGINS', '')).split(',') if (env('CORS_ORIGINS')) else [f'https://{DLS_URL}'] jwt_encode_key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256) @@ -73,11 +76,6 @@ async def index(): return RedirectResponse('/-/') -@app.get('/status', summary='* Status', description='returns current service status, version (incl. git-commit) and some variables.', deprecated=True) -async def status(request: Request): - return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG}) - - @app.get('/-/', summary='* Index') async def _index(request: Request): return templates.TemplateResponse(name='views/index.html', context={'request': request, 'VERSION': VERSION}) @@ -85,7 +83,25 @@ async def _index(request: Request): @app.get('/-/health', summary='* Health') async def _health(request: Request): - return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG}) + return JSONResponse({'status': 'up'}) + + +@app.get('/-/config', summary='* Config', description='returns environment variables.') +async def _config(): + return JSONResponse({ + 'VERSION': str(VERSION), + 'COMMIT': str(COMMIT), + 'DEBUG': str(DEBUG), + 'DLS_URL': str(DLS_URL), + 'DLS_PORT': str(DLS_PORT), + 'SITE_KEY_XID': str(SITE_KEY_XID), + 'INSTANCE_REF': str(INSTANCE_REF), + 'ALLOTMENT_REF': [ALLOTMENT_REF], + 'TOKEN_EXPIRE_DELTA': str(TOKEN_EXPIRE_DELTA), + 'LEASE_EXPIRE_DELTA': str(LEASE_EXPIRE_DELTA), + 'LEASE_RENEWAL_PERIOD': str(LEASE_RENEWAL_PERIOD), + 'CORS_ORIGINS': str(CORS_ORIGINS), + }) @app.get('/-/readme', summary='* Readme') @@ -171,7 +187,7 @@ async def _client_token(): "nbf": timegm(cur_time.timetuple()), "exp": timegm(exp_time.timetuple()), "update_mode": "ABSOLUTE", - "scope_ref_list": [str(uuid4())], # this is our LEASE_REF + "scope_ref_list": [ALLOTMENT_REF], "fulfillment_class_ref_list": [], "service_instance_configuration": { "nls_service_instance_ref": INSTANCE_REF, @@ -203,32 +219,26 @@ async def _client_token(): 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 -# {"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', description='find or create an origin') async def auth_v1_origin(request: Request): j, cur_time = json.loads((await request.body()).decode('utf-8')), datetime.utcnow() - origin_ref = j['candidate_origin_ref'] + origin_ref = j.get('candidate_origin_ref') logging.info(f'> [ origin ]: {origin_ref}: {j}') data = Origin( origin_ref=origin_ref, - hostname=j['environment']['hostname'], - guest_driver_version=j['environment']['guest_driver_version'], - os_platform=j['environment']['os_platform'], os_version=j['environment']['os_version'], + hostname=j.get('environment').get('hostname'), + guest_driver_version=j.get('environment').get('guest_driver_version'), + os_platform=j.get('environment').get('os_platform'), os_version=j.get('environment').get('os_version'), ) Origin.create_or_update(db, data) response = { "origin_ref": origin_ref, - "environment": j['environment'], + "environment": j.get('environment'), "svc_port_set_list": None, "node_url_list": None, "node_query_order": None, @@ -240,25 +250,24 @@ async def auth_v1_origin(request: Request): # 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" } @app.post('/auth/v1/origin/update', description='update an origin evidence') async def auth_v1_origin_update(request: Request): j, cur_time = json.loads((await request.body()).decode('utf-8')), datetime.utcnow() - origin_ref = j['origin_ref'] + origin_ref = j.get('origin_ref') logging.info(f'> [ update ]: {origin_ref}: {j}') data = Origin( origin_ref=origin_ref, - hostname=j['environment']['hostname'], - guest_driver_version=j['environment']['guest_driver_version'], - os_platform=j['environment']['os_platform'], os_version=j['environment']['os_version'], + hostname=j.get('environment').get('hostname'), + guest_driver_version=j.get('environment').get('guest_driver_version'), + os_platform=j.get('environment').get('os_platform'), os_version=j.get('environment').get('os_version'), ) Origin.create_or_update(db, data) response = { - "environment": j['environment'], + "environment": j.get('environment'), "prompts": None, "sync_timestamp": cur_time.isoformat() } @@ -268,12 +277,11 @@ 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_core_auth/auth.py - CodeResponse -# {"code_challenge":"...","origin_ref":"00112233-4455-6677-8899-aabbccddeeff"} @app.post('/auth/v1/code', description='get an authorization code') async def auth_v1_code(request: Request): j, cur_time = json.loads((await request.body()).decode('utf-8')), datetime.utcnow() - origin_ref = j['origin_ref'] + origin_ref = j.get('origin_ref') logging.info(f'> [ code ]: {origin_ref}: {j}') delta = relativedelta(minutes=15) @@ -282,8 +290,8 @@ async def auth_v1_code(request: Request): payload = { 'iat': timegm(cur_time.timetuple()), 'exp': timegm(expires.timetuple()), - 'challenge': j['code_challenge'], - 'origin_ref': j['origin_ref'], + 'challenge': j.get('code_challenge'), + 'origin_ref': j.get('origin_ref'), 'key_ref': SITE_KEY_XID, 'kid': SITE_KEY_XID } @@ -301,17 +309,16 @@ 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_core_auth/auth.py - TokenResponse -# {"auth_code":"...","code_verifier":"..."} @app.post('/auth/v1/token', description='exchange auth code and verifier for token') async def auth_v1_token(request: Request): 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.get('auth_code'), key=jwt_decode_key) - origin_ref = payload['origin_ref'] + origin_ref = payload.get('origin_ref') logging.info(f'> [ auth ]: {origin_ref}: {j}') # validate the code challenge - if payload['challenge'] != b64enc(sha256(j['code_verifier'].encode('utf-8')).digest()).rstrip(b'=').decode('utf-8'): + if payload.get('challenge') != b64enc(sha256(j.get('code_verifier').encode('utf-8')).digest()).rstrip(b'=').decode('utf-8'): raise HTTPException(status_code=401, detail='expected challenge did not match verifier') access_expires_on = cur_time + TOKEN_EXPIRE_DELTA @@ -338,33 +345,36 @@ async def auth_v1_token(request: Request): return JSONResponse(response) -# {'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']} +# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py @app.post('/leasing/v1/lessor', description='request multiple leases (borrow) for current origin') async def leasing_v1_lessor(request: Request): j, token, cur_time = json.loads((await request.body()).decode('utf-8')), __get_token(request), datetime.utcnow() origin_ref = token.get('origin_ref') - scope_ref_list = j['scope_ref_list'] + scope_ref_list = j.get('scope_ref_list') logging.info(f'> [ create ]: {origin_ref}: create leases for scope_ref_list {scope_ref_list}') lease_result_list = [] for scope_ref in scope_ref_list: + # if scope_ref not in [ALLOTMENT_REF]: + # raise HTTPException(status_code=500, detail=f'no service instances found for scopes: ["{scope_ref}"]') + + lease_ref = str(uuid4()) expires = cur_time + LEASE_EXPIRE_DELTA lease_result_list.append({ "ordinal": 0, # https://docs.nvidia.com/license-system/latest/nvidia-license-system-user-guide/index.html "lease": { - "ref": scope_ref, + "ref": lease_ref, "created": cur_time.isoformat(), "expires": expires.isoformat(), - # The percentage of the lease period that must elapse before a licensed client can renew a license - "recommended_lease_renewal": 0.15, + "recommended_lease_renewal": LEASE_RENEWAL_PERIOD, "offline_lease": "true", "license_type": "CONCURRENT_COUNTED_SINGLE" } }) - data = Lease(origin_ref=origin_ref, lease_ref=scope_ref, lease_created=cur_time, lease_expires=expires) + data = Lease(origin_ref=origin_ref, lease_ref=lease_ref, lease_created=cur_time, lease_expires=expires) Lease.create_or_update(db, data) response = { @@ -397,6 +407,7 @@ async def leasing_v1_lessor_lease(request: Request): return JSONResponse(response) +# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_single_controller.py # venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py @app.put('/leasing/v1/lease/{lease_ref}', description='renew a lease') async def leasing_v1_lease_renew(request: Request, lease_ref: str): @@ -413,7 +424,7 @@ async def leasing_v1_lease_renew(request: Request, lease_ref: str): response = { "lease_ref": lease_ref, "expires": expires.isoformat(), - "recommended_lease_renewal": 0.16, + "recommended_lease_renewal": LEASE_RENEWAL_PERIOD, "offline_lease": True, "prompts": None, "sync_timestamp": cur_time.isoformat(), @@ -424,6 +435,7 @@ async def leasing_v1_lease_renew(request: Request, lease_ref: str): return JSONResponse(response) +# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_single_controller.py @app.delete('/leasing/v1/lease/{lease_ref}', description='release (return) a lease') async def leasing_v1_lease_delete(request: Request, lease_ref: str): token, cur_time = __get_token(request), datetime.utcnow() @@ -449,6 +461,7 @@ async def leasing_v1_lease_delete(request: Request, lease_ref: str): return JSONResponse(response) +# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py @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() @@ -469,6 +482,28 @@ async def leasing_v1_lessor_lease_remove(request: Request): return JSONResponse(response) +@app.post('/leasing/v1/lessor/shutdown', description='shutdown all leases') +async def leasing_v1_lessor_shutdown(request: Request): + j, cur_time = json.loads((await request.body()).decode('utf-8')) + + token = j.get('token') + token = jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False}) + origin_ref = token.get('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) + logging.info(f'> [ shutdown ]: {origin_ref}: removed {deletions} leases') + + response = { + "released_lease_list": released_lease_list, + "release_failure_list": None, + "sync_timestamp": cur_time.isoformat(), + "prompts": None + } + + return JSONResponse(response) + + if __name__ == '__main__': import uvicorn diff --git a/app/orm.py b/app/orm.py index 0f5d386..efc5853 100644 --- a/app/orm.py +++ b/app/orm.py @@ -13,6 +13,7 @@ class Origin(Base): origin_ref = Column(CHAR(length=36), primary_key=True, unique=True, index=True) # uuid4 + # service_instance_xid = Column(CHAR(length=36), nullable=False, index=True) # uuid4 # not necessary, we only support one service_instance_xid ('INSTANCE_REF') hostname = Column(VARCHAR(length=256), nullable=True) guest_driver_version = Column(VARCHAR(length=10), nullable=True) os_platform = Column(VARCHAR(length=256), nullable=True) @@ -24,6 +25,7 @@ class Origin(Base): def serialize(self) -> dict: return { 'origin_ref': self.origin_ref, + # 'service_instance_xid': self.service_instance_xid, 'hostname': self.hostname, 'guest_driver_version': self.guest_driver_version, 'os_platform': self.os_platform, @@ -72,6 +74,7 @@ class Lease(Base): lease_ref = Column(CHAR(length=36), primary_key=True, nullable=False, index=True) # uuid4 origin_ref = Column(CHAR(length=36), ForeignKey(Origin.origin_ref, ondelete='CASCADE'), nullable=False, index=True) # uuid4 + # scope_ref = Column(CHAR(length=36), nullable=False, index=True) # uuid4 # not necessary, we only support one scope_ref ('ALLOTMENT_REF') lease_created = Column(DATETIME(), nullable=False) lease_expires = Column(DATETIME(), nullable=False) lease_updated = Column(DATETIME(), nullable=False) @@ -83,6 +86,7 @@ class Lease(Base): return { 'lease_ref': self.lease_ref, 'origin_ref': self.origin_ref, + # 'scope_ref': self.scope_ref, 'lease_created': self.lease_created.isoformat(), 'lease_expires': self.lease_expires.isoformat(), 'lease_updated': self.lease_updated.isoformat(), @@ -178,4 +182,14 @@ def migrate(engine: Engine): Lease.__table__.drop(bind=engine) init(engine) + # def upgrade_1_2_to_1_3(): + # x = db.dialect.get_columns(engine.connect(), Lease.__tablename__) + # x = next((_ for _ in x if _['name'] == 'scope_ref'), None) + # if x is None: + # Lease.scope_ref.compile() + # column_name = Lease.scope_ref.name + # column_type = Lease.scope_ref.type.compile(engine.dialect) + # engine.execute(f'ALTER TABLE "{Lease.__tablename__}" ADD COLUMN "{column_name}" {column_type}') + upgrade_1_0_to_1_1() + # upgrade_1_2_to_1_3() diff --git a/doc/Database.md b/doc/Database.md new file mode 100644 index 0000000..5a838a3 --- /dev/null +++ b/doc/Database.md @@ -0,0 +1,26 @@ +# Database structure + +## `request_routing.service_instance` + +| xid | org_name | +|----------------------------------------|--------------------------| +| `10000000-0000-0000-0000-000000000000` | `lic-000000000000000000` | + +- `xid` is used as `SERVICE_INSTANCE_XID` + +## `request_routing.license_allotment_service_instance` + +| xid | service_instance_xid | license_allotment_xid | +|----------------------------------------|----------------------------------------|----------------------------------------| +| `90000000-0000-0000-0000-000000000001` | `10000000-0000-0000-0000-000000000000` | `80000000-0000-0000-0000-000000000001` | + +- `xid` is only a primary-key and never used as foreign-key or reference +- `license_allotment_xid` must be used to fetch `xid`'s from `request_routing.license_allotment_reference` + +## `request_routing.license_allotment_reference` + +| xid | license_allotment_xid | +|----------------------------------------|----------------------------------------| +| `20000000-0000-0000-0000-000000000001` | `80000000-0000-0000-0000-000000000001` | + +- `xid` is used as `scope_ref_list` on token request diff --git a/doc/Reverse Engineering Notes.md b/doc/Reverse Engineering Notes.md index 6746cf7..920a2c3 100644 --- a/doc/Reverse Engineering Notes.md +++ b/doc/Reverse Engineering Notes.md @@ -33,6 +33,8 @@ nvidia-gridd[2986]: License acquired successfully. (Info: license.nvidia.space, Most variables and configs are stored in `/var/lib/docker/volumes/configurations/_data`. +Files can be modified with `docker cp :/venv/... /opt/localfile/...` and back. + ## Dive / Docker image inspector - `dive dls:appliance` diff --git a/test/main.py b/test/main.py index 9ef4457..f04de99 100644 --- a/test/main.py +++ b/test/main.py @@ -3,7 +3,7 @@ from hashlib import sha256 from calendar import timegm from datetime import datetime from os.path import dirname, join -from uuid import uuid4 +from uuid import uuid4, UUID from dateutil.relativedelta import relativedelta from jose import jwt, jwk @@ -20,8 +20,7 @@ from app.util import load_key client = TestClient(main.app) -ORIGIN_REF, LEASE_REF = str(uuid4()), str(uuid4()) -SECRET = "HelloWorld" +ORIGIN_REF, ALLOTMENT_REF, SECRET = str(uuid4()), '20000000-0000-0000-0000-000000000001', 'HelloWorld' # INSTANCE_KEY_RSA = generate_key() # INSTANCE_KEY_PUB = INSTANCE_KEY_RSA.public_key() @@ -44,16 +43,15 @@ def test_index(): assert response.status_code == 200 -def test_status(): - response = client.get('/status') - assert response.status_code == 200 - assert response.json()['status'] == 'up' - - def test_health(): response = client.get('/-/health') assert response.status_code == 200 - assert response.json()['status'] == 'up' + assert response.json().get('status') == 'up' + + +def test_config(): + response = client.get('/-/') + assert response.status_code == 200 def test_readme(): @@ -71,11 +69,6 @@ def test_client_token(): assert response.status_code == 200 -def test_client_token_deprecated(): - response = client.get('/client-token') - assert response.status_code == 200 - - def test_origins(): pass @@ -110,7 +103,7 @@ def test_auth_v1_origin(): response = client.post('/auth/v1/origin', json=payload) assert response.status_code == 200 - assert response.json()['origin_ref'] == ORIGIN_REF + assert response.json().get('origin_ref') == ORIGIN_REF def auth_v1_origin_update(): @@ -131,7 +124,7 @@ def auth_v1_origin_update(): response = client.post('/auth/v1/origin/update', json=payload) assert response.status_code == 200 - assert response.json()['origin_ref'] == ORIGIN_REF + assert response.json().get('origin_ref') == ORIGIN_REF def test_auth_v1_code(): @@ -143,8 +136,8 @@ def test_auth_v1_code(): response = client.post('/auth/v1/code', json=payload) assert response.status_code == 200 - payload = jwt.get_unverified_claims(token=response.json()['auth_code']) - assert payload['origin_ref'] == ORIGIN_REF + payload = jwt.get_unverified_claims(token=response.json().get('auth_code')) + assert payload.get('origin_ref') == ORIGIN_REF def test_auth_v1_token(): @@ -168,9 +161,9 @@ def test_auth_v1_token(): response = client.post('/auth/v1/token', json=payload) assert response.status_code == 200 - token = response.json()['auth_token'] + token = response.json().get('auth_token') payload = jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False}) - assert payload['origin_ref'] == ORIGIN_REF + assert payload.get('origin_ref') == ORIGIN_REF def test_leasing_v1_lessor(): @@ -183,46 +176,59 @@ def test_leasing_v1_lessor(): 'product': {'name': 'NVIDIA RTX Virtual Workstation'} }], 'proposal_evaluation_mode': 'ALL_OF', - 'scope_ref_list': [LEASE_REF] + 'scope_ref_list': [ALLOTMENT_REF] } response = client.post('/leasing/v1/lessor', json=payload, headers={'authorization': __bearer_token(ORIGIN_REF)}) assert response.status_code == 200 - lease_result_list = response.json()['lease_result_list'] + lease_result_list = response.json().get('lease_result_list') assert len(lease_result_list) == 1 - assert lease_result_list[0]['lease']['ref'] == LEASE_REF + assert str(UUID(lease_result_list[0]['lease']['ref'])) == lease_result_list[0]['lease']['ref'] + return lease_result_list[0]['lease']['ref'] def test_leasing_v1_lessor_lease(): response = client.get('/leasing/v1/lessor/leases', headers={'authorization': __bearer_token(ORIGIN_REF)}) assert response.status_code == 200 - active_lease_list = response.json()['active_lease_list'] + active_lease_list = response.json().get('active_lease_list') assert len(active_lease_list) == 1 - assert active_lease_list[0] == LEASE_REF + assert str(UUID(active_lease_list[0])) == active_lease_list[0] def test_leasing_v1_lease_renew(): - response = client.put(f'/leasing/v1/lease/{LEASE_REF}', headers={'authorization': __bearer_token(ORIGIN_REF)}) + response = client.get('/leasing/v1/lessor/leases', headers={'authorization': __bearer_token(ORIGIN_REF)}) + active_lease_list = response.json().get('active_lease_list') + lease_ref = active_lease_list[0] + + ### + + response = client.put(f'/leasing/v1/lease/{lease_ref}', headers={'authorization': __bearer_token(ORIGIN_REF)}) assert response.status_code == 200 - assert response.json()['lease_ref'] == LEASE_REF + assert response.json().get('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)}) + response = client.get('/leasing/v1/lessor/leases', headers={'authorization': __bearer_token(ORIGIN_REF)}) + active_lease_list = response.json().get('active_lease_list') + lease_ref = active_lease_list[0] + + ### + + response = client.delete(f'/leasing/v1/lease/{lease_ref}', headers={'authorization': __bearer_token(ORIGIN_REF)}) assert response.status_code == 200 - assert response.json()['lease_ref'] == LEASE_REF + assert response.json().get('lease_ref') == lease_ref def test_leasing_v1_lessor_lease_remove(): - test_leasing_v1_lessor() + lease_ref = test_leasing_v1_lessor() response = client.delete('/leasing/v1/lessor/leases', headers={'authorization': __bearer_token(ORIGIN_REF)}) assert response.status_code == 200 - released_lease_list = response.json()['released_lease_list'] + released_lease_list = response.json().get('released_lease_list') assert len(released_lease_list) == 1 - assert released_lease_list[0] == LEASE_REF + assert released_lease_list[0] == lease_ref diff --git a/version.env b/version.env index 955cc3f..5be527b 100644 --- a/version.env +++ b/version.env @@ -1 +1 @@ -VERSION=1.2 +VERSION=1.3