ciocore 8.0.1__py2.py3-none-any.whl → 8.1.0b3__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ciocore might be problematic. Click here for more details.
- ciocore/VERSION +1 -1
- ciocore/api_client.py +248 -1
- ciocore/data.py +12 -4
- ciocore/docsite/apidoc/api_client/index.html +341 -0
- ciocore/docsite/apidoc/data/index.html +20 -6
- ciocore/docsite/objects.inv +0 -0
- ciocore/docsite/search/search_index.json +1 -1
- ciocore/docsite/sitemap.xml.gz +0 -0
- ciocore/package_tree.py +5 -8
- {ciocore-8.0.1.dist-info → ciocore-8.1.0b3.dist-info}/METADATA +8 -2
- {ciocore-8.0.1.dist-info → ciocore-8.1.0b3.dist-info}/RECORD +15 -15
- tests/test_api_client.py +217 -2
- {ciocore-8.0.1.dist-info → ciocore-8.1.0b3.dist-info}/WHEEL +0 -0
- {ciocore-8.0.1.dist-info → ciocore-8.1.0b3.dist-info}/entry_points.txt +0 -0
- {ciocore-8.0.1.dist-info → ciocore-8.1.0b3.dist-info}/top_level.txt +0 -0
ciocore/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
8.0.
|
|
1
|
+
8.1.0-beta.3
|
ciocore/api_client.py
CHANGED
|
@@ -3,18 +3,21 @@ The api_client module is used to make requests to the Conductor API.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import base64
|
|
6
|
+
import collections
|
|
7
|
+
import datetime
|
|
8
|
+
import hashlib
|
|
6
9
|
import importlib
|
|
7
10
|
import json
|
|
8
11
|
import jwt
|
|
9
12
|
import logging
|
|
10
13
|
import os
|
|
11
14
|
import platform
|
|
15
|
+
import datetime
|
|
12
16
|
import requests
|
|
13
17
|
import socket
|
|
14
18
|
import time
|
|
15
19
|
import sys
|
|
16
20
|
import platform
|
|
17
|
-
import hashlib
|
|
18
21
|
|
|
19
22
|
from urllib import parse
|
|
20
23
|
|
|
@@ -486,6 +489,30 @@ def account_name_from_jwt(token):
|
|
|
486
489
|
return None
|
|
487
490
|
|
|
488
491
|
|
|
492
|
+
def get_account_data():
|
|
493
|
+
"""
|
|
494
|
+
Get the authenticated account's data.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
dict: The account data.
|
|
498
|
+
"""
|
|
499
|
+
token = read_conductor_credentials(True)
|
|
500
|
+
if not token:
|
|
501
|
+
return None
|
|
502
|
+
|
|
503
|
+
payload = jwt.decode(token, verify=False)
|
|
504
|
+
return {
|
|
505
|
+
"email": payload.get("email"),
|
|
506
|
+
"domain": ".".join(payload.get("aud").split(".")[1:]),
|
|
507
|
+
"account_id" : payload.get("account"),
|
|
508
|
+
"user_id" : payload.get("id"),
|
|
509
|
+
"role": payload.get("role"),
|
|
510
|
+
"issued": datetime.datetime.fromtimestamp(payload.get("iat")),
|
|
511
|
+
"expiry": datetime.datetime.fromtimestamp(payload.get("exp")),
|
|
512
|
+
"token": token,
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
|
|
489
516
|
def request_instance_types(as_dict=False):
|
|
490
517
|
"""
|
|
491
518
|
Get the list of available instances types.
|
|
@@ -786,3 +813,223 @@ def kill_tasks(job_id, *task_ids):
|
|
|
786
813
|
raise Exception(msg)
|
|
787
814
|
|
|
788
815
|
return json.loads(response)
|
|
816
|
+
|
|
817
|
+
def _get_compute_usage(start_time, end_time):
|
|
818
|
+
"""
|
|
819
|
+
Query the account usage to get the raw compute data. Private method.
|
|
820
|
+
|
|
821
|
+
Compute includes licenses, instances and Conductor cost. Everything involved
|
|
822
|
+
with running a job.
|
|
823
|
+
|
|
824
|
+
Please use the public method api_client.get_compute_usage() instead.
|
|
825
|
+
|
|
826
|
+
Args:
|
|
827
|
+
start_time (datetime.datetime): The first day to include in the report. Only the date is considered and it's assumed to be in UTC.
|
|
828
|
+
end_time (datetime.datetime): The last day to include in the report. Only the date is considered and it's assumed to be in UTC.
|
|
829
|
+
|
|
830
|
+
Returns:
|
|
831
|
+
list: A list of billing entries
|
|
832
|
+
|
|
833
|
+
Examples:
|
|
834
|
+
>>> from ciocore import api_client
|
|
835
|
+
>>> api_client._get_compute_usage(start_time, end_time)
|
|
836
|
+
[
|
|
837
|
+
{
|
|
838
|
+
"cores": 0.5,
|
|
839
|
+
"instance_cost": 0.019999999552965164,
|
|
840
|
+
"license_cost": 0.019999999552965164,
|
|
841
|
+
"minutes": 6.9700000286102295,
|
|
842
|
+
"self_link": 0,
|
|
843
|
+
"start_time": "Tue, 09 Jan 2024 18:00:00 GMT"
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
"cores": 0.4,
|
|
847
|
+
"instance_cost": 0.019999999552965164,
|
|
848
|
+
"license_cost": 0.019999999552965164,
|
|
849
|
+
"minutes": 6.960000038146973,
|
|
850
|
+
"self_link": 1,
|
|
851
|
+
"start_time": "Tue, 09 Jan 2024 19:00:00 GMT"
|
|
852
|
+
}]
|
|
853
|
+
"""
|
|
854
|
+
|
|
855
|
+
api = ApiClient()
|
|
856
|
+
|
|
857
|
+
payload = {
|
|
858
|
+
"filter": json.dumps(
|
|
859
|
+
[{"field": "start_time", "op": ">=", "value": start_time.date().isoformat()},
|
|
860
|
+
{"field": "start_time", "op": "<", "value": end_time.date().isoformat()}]),
|
|
861
|
+
"group_by": json.dumps(["start_time"]),
|
|
862
|
+
"order_by": "start_time"
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
response, response_code = api.make_request(
|
|
866
|
+
uri_path="billing/get_usage",
|
|
867
|
+
verb="GET",
|
|
868
|
+
raise_on_error=False,
|
|
869
|
+
use_api_key=True,
|
|
870
|
+
params=payload
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
if response_code not in [200]:
|
|
874
|
+
msg = f"Failed to query compute usage"
|
|
875
|
+
msg += "\nError %s ...\n%s" % (response_code, response)
|
|
876
|
+
raise Exception(msg)
|
|
877
|
+
|
|
878
|
+
return json.loads(response)['data']
|
|
879
|
+
|
|
880
|
+
def _get_storage_usage(start_time, end_time):
|
|
881
|
+
"""
|
|
882
|
+
Query the account usage to get the raw storage data. Private method.
|
|
883
|
+
|
|
884
|
+
Please use the public method api_client.get_storage_usage() instead.
|
|
885
|
+
|
|
886
|
+
Args:
|
|
887
|
+
start_time (datetime.datetime): The first day to include in the report. Only the date is considered and it's assumed to be in UTC.
|
|
888
|
+
end_time (datetime.datetime): The last day to include in the report. Only the date is considered and it's assumed to be in UTC.
|
|
889
|
+
|
|
890
|
+
Returns:
|
|
891
|
+
dict: A dict of billing details related to storage
|
|
892
|
+
|
|
893
|
+
Examples:
|
|
894
|
+
>>> from ciocore import api_client
|
|
895
|
+
>>> api_client._get_storage_usage(start_time, end_time)
|
|
896
|
+
{
|
|
897
|
+
"cost": "28.96",
|
|
898
|
+
"cost_per_day": [
|
|
899
|
+
4.022,
|
|
900
|
+
4.502,
|
|
901
|
+
4.502,
|
|
902
|
+
5.102,
|
|
903
|
+
5.102,
|
|
904
|
+
5.732
|
|
905
|
+
],
|
|
906
|
+
"currency": "USD",
|
|
907
|
+
"daily_price": "0.006",
|
|
908
|
+
"end_date": "2024-01-07",
|
|
909
|
+
"gibs_per_day": [
|
|
910
|
+
679.714,
|
|
911
|
+
750.34,
|
|
912
|
+
750.34,
|
|
913
|
+
850.36,
|
|
914
|
+
850.35,
|
|
915
|
+
955.32
|
|
916
|
+
],
|
|
917
|
+
"gibs_used": "806.07",
|
|
918
|
+
"monthly_price": "0.18",
|
|
919
|
+
"start_date": "2024-01-01",
|
|
920
|
+
"storage_unit": "GiB"
|
|
921
|
+
}
|
|
922
|
+
]
|
|
923
|
+
}
|
|
924
|
+
"""
|
|
925
|
+
|
|
926
|
+
api = ApiClient()
|
|
927
|
+
|
|
928
|
+
# Add one day to the end time as the query is exclusive of the last day but
|
|
929
|
+
# we want consistency with _get_compute_usage()
|
|
930
|
+
payload = {
|
|
931
|
+
"start": start_time.date().isoformat(),
|
|
932
|
+
"end": (end_time.date() + datetime.timedelta(days=1)).isoformat()
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
response, response_code = api.make_request(
|
|
936
|
+
uri_path="billing/get_storage_usage",
|
|
937
|
+
verb="GET",
|
|
938
|
+
raise_on_error=False,
|
|
939
|
+
use_api_key=True,
|
|
940
|
+
params=payload
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
if response_code not in [200]:
|
|
944
|
+
msg = f"Failed to query storage usage"
|
|
945
|
+
msg += "\nError %s ...\n%s" % (response_code, response)
|
|
946
|
+
raise Exception(msg)
|
|
947
|
+
|
|
948
|
+
return json.loads(response)['data'][0]
|
|
949
|
+
|
|
950
|
+
def get_compute_usage(start_time, end_time):
|
|
951
|
+
'''
|
|
952
|
+
Query the compute usage for an account.
|
|
953
|
+
|
|
954
|
+
Compute includes licenses, instances and Conductor cost. Everything involved
|
|
955
|
+
with running a job.
|
|
956
|
+
|
|
957
|
+
Args:
|
|
958
|
+
start_time (datetime.datetime): The first day to include in the report. Only the date is considered and it's assumed to be in UTC.
|
|
959
|
+
end_time (datetime.datetime): The last day to include in the report. Only the date is considered and it's assumed to be in UTC.
|
|
960
|
+
|
|
961
|
+
Returns:
|
|
962
|
+
dict: Each key is a date (UTC). The value is a dict with values for:
|
|
963
|
+
- cost: The total accumulated compute cost for the day
|
|
964
|
+
- corehours: The total accumulated core hours for the day
|
|
965
|
+
- walltime: The number of minutes that instances (regardless of type) were running
|
|
966
|
+
|
|
967
|
+
Examples:
|
|
968
|
+
>>> from ciocore import api_client
|
|
969
|
+
>>> api_client.get_compute_usage(start_time, end_time)
|
|
970
|
+
{ '2024-01-09': { 'cost': 0.08,
|
|
971
|
+
'corehours': 0.9,
|
|
972
|
+
'walltime': 13.93},
|
|
973
|
+
'2024-01-16': { 'cost': 0.12,
|
|
974
|
+
'corehours': 0.9613,
|
|
975
|
+
'walltime': 7.21}}
|
|
976
|
+
'''
|
|
977
|
+
date_format = "%a, %d %b %Y %H:%M:%S %Z"
|
|
978
|
+
data = _get_compute_usage(start_time, end_time)
|
|
979
|
+
|
|
980
|
+
# Create a nested default dictionary with initial float values of 0.0
|
|
981
|
+
results = collections.defaultdict(lambda: collections.defaultdict(float))
|
|
982
|
+
|
|
983
|
+
for entry in data:
|
|
984
|
+
entry_start_date = datetime.datetime.strptime(entry['start_time'], date_format).date().isoformat()
|
|
985
|
+
|
|
986
|
+
results[entry_start_date]['walltime'] += entry['minutes']
|
|
987
|
+
results[entry_start_date]['corehours'] += entry['cores']
|
|
988
|
+
results[entry_start_date]['cost'] += entry['license_cost'] + entry['instance_cost']
|
|
989
|
+
|
|
990
|
+
# Round the data to avoid FP errors
|
|
991
|
+
results[entry_start_date]['walltime'] = round(results[entry_start_date]['walltime'], 4)
|
|
992
|
+
results[entry_start_date]['corehours'] = round(results[entry_start_date]['corehours'], 4)
|
|
993
|
+
results[entry_start_date]['cost'] = round(results[entry_start_date]['cost'], 4)
|
|
994
|
+
|
|
995
|
+
return results
|
|
996
|
+
|
|
997
|
+
def get_storage_usage(start_time, end_time):
|
|
998
|
+
'''
|
|
999
|
+
Query the storage usage for an account.
|
|
1000
|
+
|
|
1001
|
+
Storage is calculated twice a day (UTC) and the average is used.
|
|
1002
|
+
|
|
1003
|
+
Args:
|
|
1004
|
+
start_time (datetime.datetime): The first day to include in the report. Only the date is considered and it's assumed to be in UTC.
|
|
1005
|
+
end_time (datetime.datetime): The last day to include in the report. Only the date is considered and it's assumed to be in UTC.
|
|
1006
|
+
|
|
1007
|
+
Returns:
|
|
1008
|
+
dict: Each key is a date (UTC). The value is a dict with values for:
|
|
1009
|
+
- cost: The cost of accumulated storage for that one day
|
|
1010
|
+
- GiB: The total amount of storage used on that day
|
|
1011
|
+
|
|
1012
|
+
Examples:
|
|
1013
|
+
>>> from ciocore import api_client
|
|
1014
|
+
>>> api_client.get_storage_usage(start_time, end_time)
|
|
1015
|
+
{ '2024-01-01': {'cost': 4.022, 'GiB': 679.714},
|
|
1016
|
+
'2024-01-02': {'cost': 4.502, 'GiB': 750.34},
|
|
1017
|
+
'2024-01-03': {'cost': 4.502, 'GiB': 750.34}}
|
|
1018
|
+
'''
|
|
1019
|
+
one_day = datetime.timedelta(days=1)
|
|
1020
|
+
|
|
1021
|
+
data = _get_storage_usage(start_time, end_time)
|
|
1022
|
+
|
|
1023
|
+
results = {}
|
|
1024
|
+
|
|
1025
|
+
entry_date = datetime.date.fromisoformat(data['start_date'])
|
|
1026
|
+
|
|
1027
|
+
for cnt, entry in enumerate(data["cost_per_day"]):
|
|
1028
|
+
|
|
1029
|
+
entry_start_date = entry_date.isoformat()
|
|
1030
|
+
results[entry_start_date] = {'cost': float(entry), 'GiB': float(data['gibs_per_day'][cnt])}
|
|
1031
|
+
entry_date += one_day
|
|
1032
|
+
|
|
1033
|
+
return results
|
|
1034
|
+
|
|
1035
|
+
|
ciocore/data.py
CHANGED
|
@@ -53,7 +53,7 @@ def init(*products, **kwargs):
|
|
|
53
53
|
|
|
54
54
|
def data(force=False):
|
|
55
55
|
"""
|
|
56
|
-
Provide projects, instance types,
|
|
56
|
+
Provide projects, instance types, software package, and account data.
|
|
57
57
|
|
|
58
58
|
Keyword Args:
|
|
59
59
|
force: (bool) If `True`, then force the system to fetch fresh data -- Defaults to `False`.
|
|
@@ -62,10 +62,10 @@ def data(force=False):
|
|
|
62
62
|
ValueError: Module was not initialized with [init()](/data/#ciocore.data.init).
|
|
63
63
|
|
|
64
64
|
Returns:
|
|
65
|
-
dict: Keys are `projects`, `instance_types`, `software`.
|
|
65
|
+
dict: Keys are `projects`, `instance_types`, `software`, `account`.
|
|
66
66
|
|
|
67
67
|
When you access the data, if it has already been fetched, it will be returned. Otherwise,
|
|
68
|
-
requests will be made to fetch
|
|
68
|
+
requests will be made to fetch fresh data. You may need to authenticate in order to access the
|
|
69
69
|
data.
|
|
70
70
|
|
|
71
71
|
The set of instance types and software can be pruned to match the available platforms
|
|
@@ -84,7 +84,8 @@ def data(force=False):
|
|
|
84
84
|
|
|
85
85
|
* **software** is a PackageTree object containing either all
|
|
86
86
|
the software available at Conductor, or a subset based on specified products.
|
|
87
|
-
|
|
87
|
+
|
|
88
|
+
* **account** is a dictionary containing account information gained from the JWT token.
|
|
88
89
|
|
|
89
90
|
Examples:
|
|
90
91
|
>>> from ciocore import data as coredata
|
|
@@ -98,6 +99,10 @@ def data(force=False):
|
|
|
98
99
|
|
|
99
100
|
>>> coredata.data()["instance_types"]
|
|
100
101
|
<ciocore.hardware_set.HardwareSet object at 0x0000028941CD9DC0>
|
|
102
|
+
|
|
103
|
+
>>> coredata.data()["account"]["account_id"]
|
|
104
|
+
123456789012
|
|
105
|
+
|
|
101
106
|
"""
|
|
102
107
|
|
|
103
108
|
global __data__
|
|
@@ -129,6 +134,9 @@ def data(force=False):
|
|
|
129
134
|
except Exception:
|
|
130
135
|
pass
|
|
131
136
|
__data__["extra_environment"] = extra_env_vars
|
|
137
|
+
|
|
138
|
+
__data__["account"] = api_client.get_account_data()
|
|
139
|
+
|
|
132
140
|
|
|
133
141
|
# PLATFORMS
|
|
134
142
|
it_platforms = set([it["operating_system"] for it in instance_types])
|