qarnot 2.17.0__tar.gz → 2.18.0__tar.gz
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.
- {qarnot-2.17.0/qarnot.egg-info → qarnot-2.18.0}/PKG-INFO +3 -2
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/computeindex.rst +1 -0
- qarnot-2.18.0/doc/source/api/compute/computing_quotas.rst +8 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/_version.py +3 -3
- qarnot-2.18.0/qarnot/computing_quotas.py +328 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/connection.py +23 -6
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/helper.py +4 -3
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/pool.py +22 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/task.py +24 -2
- {qarnot-2.17.0 → qarnot-2.18.0/qarnot.egg-info}/PKG-INFO +3 -2
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot.egg-info/SOURCES.txt +2 -0
- qarnot-2.18.0/requirements.txt +6 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_connection.py +118 -3
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_pool.py +29 -2
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_task.py +33 -3
- qarnot-2.17.0/requirements.txt +0 -6
- {qarnot-2.17.0 → qarnot-2.18.0}/LICENSE +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/MANIFEST.in +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/README.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/Makefile +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/make.bat +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/_static/qarnot.png +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/carbon_facts.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/forced_network_rule.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/hardware_constraint.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/job.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/paginate.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/pool.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/privileges.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/retry_settings.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/scheduling_type.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/secrets.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/status.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/compute/task.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/connection.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/exceptions.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/storage/advanced_bucket.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/storage/bucket.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/storage/storage.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/api/storage/storageindex.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/basic.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/conf.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/index.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/installation.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/doc/source/qarnot.rst +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/pyproject.toml +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/__init__.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/_filter.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/_retry.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/_util.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/advanced_bucket.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/bucket.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/carbon_facts.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/error.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/exceptions.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/forced_constant.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/forced_network_rule.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/hardware_constraint.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/job.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/paginate.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/privileges.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/retry_settings.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/scheduling_type.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/secrets.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/status.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot/storage.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot.egg-info/dependency_links.txt +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot.egg-info/requires.txt +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/qarnot.egg-info/top_level.txt +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/requirements-doc.txt +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/requirements-lint.txt +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/requirements-optional.txt +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/requirements-test.txt +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/setup.cfg +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/setup.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_advanced_bucket.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_bucket.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_carbon_facts.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_hardware_constraints.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_import.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_job.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_paginate.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_retry.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_secrets.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_status.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/test/test_util.py +0 -0
- {qarnot-2.17.0 → qarnot-2.18.0}/versioneer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: qarnot
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.18.0
|
|
4
4
|
Summary: Qarnot Computing SDK
|
|
5
5
|
Home-page: https://computing.qarnot.com
|
|
6
6
|
Author: Qarnot computing
|
|
@@ -27,6 +27,7 @@ Dynamic: classifier
|
|
|
27
27
|
Dynamic: description
|
|
28
28
|
Dynamic: home-page
|
|
29
29
|
Dynamic: license
|
|
30
|
+
Dynamic: license-file
|
|
30
31
|
Dynamic: requires-dist
|
|
31
32
|
Dynamic: requires-python
|
|
32
33
|
Dynamic: summary
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2025-
|
|
11
|
+
"date": "2025-05-22T14:41:22+0200",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "v2.
|
|
14
|
+
"full-revisionid": "9361585630d3dbcc8fa50ecbb9d20d592234a808",
|
|
15
|
+
"version": "v2.18.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# Copyright 2025 Qarnot computing
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from typing import Optional, List, Dict, Any
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class UserSchedulingQuota(object):
|
|
19
|
+
"""Describes a scheduling quota for the user.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, max_cores: int, running_cores_count: int, max_instances: int, running_instances_count: int):
|
|
23
|
+
"""Create a new UserSchedulingQuota object describing a scheduling quota for the user.
|
|
24
|
+
|
|
25
|
+
:param int max_cores: Maximum number of cores that can be simultaneously used with this scheduling plan.
|
|
26
|
+
:param int running_cores_count: Number of cores that are currently running with this scheduling plan.
|
|
27
|
+
:param int max_instances: Maximum number of instances that can be simultaneously used with this scheduling plan.
|
|
28
|
+
:param int running_instances_count: Number of instances that are currently running with this scheduling plan.
|
|
29
|
+
:returns: The created :class:`~qarnot.computing_quotas.UserSchedulingQuota`.
|
|
30
|
+
"""
|
|
31
|
+
self.max_cores = max_cores
|
|
32
|
+
""":type: :class:`int`
|
|
33
|
+
|
|
34
|
+
Maximum number of cores that can be simultaneously used with this scheduling plan.
|
|
35
|
+
"""
|
|
36
|
+
self.running_cores_count = running_cores_count
|
|
37
|
+
""":type: :class:`int`
|
|
38
|
+
|
|
39
|
+
Number of cores that are currently running with this scheduling plan.
|
|
40
|
+
"""
|
|
41
|
+
self.max_instances = max_instances
|
|
42
|
+
""":type: :class:`int`
|
|
43
|
+
|
|
44
|
+
Maximum number of instances that can be simultaneously used with this scheduling plan.
|
|
45
|
+
"""
|
|
46
|
+
self.running_instances_count = running_instances_count
|
|
47
|
+
""":type: :class:`int`
|
|
48
|
+
|
|
49
|
+
Number of instances that are currently running with this scheduling plan.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_json(cls, json: Dict[str, Any]):
|
|
54
|
+
"""Create a new UserSchedulingQuota object from json describing a scheduling quota for a user.
|
|
55
|
+
|
|
56
|
+
:param dict json: Dictionary representing the user scheduling plan
|
|
57
|
+
:returns: The created :class:`~qarnot.computing_quotas.UserSchedulingQuota`.
|
|
58
|
+
"""
|
|
59
|
+
if json is None:
|
|
60
|
+
return None
|
|
61
|
+
return cls(
|
|
62
|
+
json.get('maxCores'),
|
|
63
|
+
json.get('runningCoresCount'),
|
|
64
|
+
json.get('maxInstances'),
|
|
65
|
+
json.get('runningInstancesCount'),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class UserReservedSchedulingQuota(UserSchedulingQuota):
|
|
70
|
+
"""Describes a reserved scheduling quota for the user.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, machine_key: str, max_cores: int, running_cores_count: int, max_instances: int, running_instances_count: int):
|
|
74
|
+
"""Create a new UserReservedSchedulingQuota object describing a reserved scheduling quota for the user.
|
|
75
|
+
|
|
76
|
+
:param str machine_key: Machine key of the reservation.
|
|
77
|
+
:param int max_cores: Maximum number of cores that can be simultaneously used with this reserved machine specification.
|
|
78
|
+
:param int running_cores_count: Number of cores that are currently running with this reserved machine specification.
|
|
79
|
+
:param int max_instances: Maximum number of instances that can be simultaneously used with this reserved machine specification.
|
|
80
|
+
:param int running_instances_count: Number of instances that are currently running with this reserved machine specification.
|
|
81
|
+
:returns: The created :class:`~qarnot.computing_quotas.UserReservedSchedulingQuota`.
|
|
82
|
+
"""
|
|
83
|
+
super().__init__(max_cores, running_cores_count, max_instances, running_instances_count)
|
|
84
|
+
self.machine_key = machine_key
|
|
85
|
+
""":type: :class:`str`
|
|
86
|
+
|
|
87
|
+
Machine key of the reservation.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_json(cls, json: Dict[str, Any]):
|
|
92
|
+
"""Create a new UserReservedSchedulingQuota object from json describing a reserved scheduling quota for a user.
|
|
93
|
+
|
|
94
|
+
:param dict json: Dictionary representing the user reserved scheduling quota
|
|
95
|
+
:returns: The created :class:`~qarnot.computing_quotas.UserReservedSchedulingQuota`.
|
|
96
|
+
"""
|
|
97
|
+
if json is None:
|
|
98
|
+
return None
|
|
99
|
+
return cls(
|
|
100
|
+
json.get('machineKey'),
|
|
101
|
+
json.get('maxCores'),
|
|
102
|
+
json.get('runningCoresCount'),
|
|
103
|
+
json.get('maxInstances'),
|
|
104
|
+
json.get('runningInstancesCount'),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class UserComputingQuotas(object):
|
|
109
|
+
"""Describes the user's computing quotas.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(self, flex: UserSchedulingQuota, on_demand: UserSchedulingQuota, reserved: List[UserReservedSchedulingQuota]):
|
|
113
|
+
"""Create a new UserComputingQuotas object describing the user's computing quotas.
|
|
114
|
+
|
|
115
|
+
:param `~qarnot.computing_quotas.UserSchedulingQuota` flex: Quotas for Flex scheduling plan.
|
|
116
|
+
:param `~qarnot.computing_quotas.UserSchedulingQuota` on_demand: Quotas for OnDemand scheduling plan.
|
|
117
|
+
:param List of `~qarnot.computing_quotas.UserReservedSchedulingQuota` reserved: Quotas for Reserved scheduling plan.
|
|
118
|
+
:returns: The created :class:`~qarnot.computing_quotas.UserComputingQuotas`.
|
|
119
|
+
"""
|
|
120
|
+
self.flex = flex
|
|
121
|
+
""":type: :class:`~qarnot.computing_quotas.UserSchedulingQuota`
|
|
122
|
+
|
|
123
|
+
Quotas for Flex scheduling plan."""
|
|
124
|
+
self.on_demand = on_demand
|
|
125
|
+
""":type: :class:`~qarnot.computing_quotas.UserSchedulingQuota`
|
|
126
|
+
|
|
127
|
+
Quotas for OnDemand scheduling plan."""
|
|
128
|
+
self.reserved = reserved
|
|
129
|
+
""":type: list(:class:`~qarnot.computing_quotas.UserReservedSchedulingQuota`)
|
|
130
|
+
|
|
131
|
+
Quotas for Reserved scheduling plan."""
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def from_json(cls, json: Dict[str, Any]):
|
|
135
|
+
"""Create a new UserComputingQuotas object from json describing the user's computing quotas.
|
|
136
|
+
|
|
137
|
+
:param dict json: Dictionary representing the user computing quota
|
|
138
|
+
:returns: The created :class:`~qarnot.computing_quotas.UserComputingQuotas`.
|
|
139
|
+
"""
|
|
140
|
+
if json is None:
|
|
141
|
+
return None
|
|
142
|
+
return cls(
|
|
143
|
+
UserSchedulingQuota.from_json(json.get('flex')),
|
|
144
|
+
UserSchedulingQuota.from_json(json.get('onDemand')),
|
|
145
|
+
[UserReservedSchedulingQuota.from_json(v) for v in json.get('reserved', []) if v is not None]
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class OrganizationSchedulingQuota(object):
|
|
150
|
+
"""Describes a scheduling quota for the organization.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def __init__(self, max_cores: int, running_cores_count: int, max_instances: int, running_instances_count: int):
|
|
154
|
+
"""Create a new OrganizationSchedulingQuota object describing a scheduling quota for the organization.
|
|
155
|
+
|
|
156
|
+
:param int max_cores: Maximum number of cores that can be simultaneously used with this scheduling plan.
|
|
157
|
+
:param int running_cores_count: Number of cores that are currently running with this scheduling plan.
|
|
158
|
+
:param int max_instances: Maximum number of instances that can be simultaneously used with this scheduling plan.
|
|
159
|
+
:param int running_instances_count: Number of instances that are currently running with this scheduling plan.
|
|
160
|
+
:returns: The created :class:`~qarnot.computing_quotas.OrganizationSchedulingQuota`.
|
|
161
|
+
"""
|
|
162
|
+
self.max_cores = max_cores
|
|
163
|
+
""":type: :class:`int`
|
|
164
|
+
|
|
165
|
+
Maximum number of cores that can be simultaneously used with this scheduling plan.
|
|
166
|
+
"""
|
|
167
|
+
self.running_cores_count = running_cores_count
|
|
168
|
+
""":type: :class:`int`
|
|
169
|
+
|
|
170
|
+
Number of cores that are currently running with this scheduling plan.
|
|
171
|
+
"""
|
|
172
|
+
self.max_instances = max_instances
|
|
173
|
+
""":type: :class:`int`
|
|
174
|
+
|
|
175
|
+
Maximum number of instances that can be simultaneously used with this scheduling plan.
|
|
176
|
+
"""
|
|
177
|
+
self.running_instances_count = running_instances_count
|
|
178
|
+
""":type: :class:`int`
|
|
179
|
+
|
|
180
|
+
Number of instances that are currently running with this scheduling plan.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def from_json(cls, json: Dict[str, Any]):
|
|
185
|
+
"""Create a new OrganizationSchedulingQuota object from json describing a scheduling quota for the organization.
|
|
186
|
+
|
|
187
|
+
:param dict json: Dictionary representing the organization scheduling plan
|
|
188
|
+
:returns: The created :class:`~qarnot.computing_quotas.OrganizationSchedulingQuota`.
|
|
189
|
+
"""
|
|
190
|
+
if json is None:
|
|
191
|
+
return None
|
|
192
|
+
return cls(
|
|
193
|
+
json.get('maxCores'),
|
|
194
|
+
json.get('runningCoresCount'),
|
|
195
|
+
json.get('maxInstances'),
|
|
196
|
+
json.get('runningInstancesCount'),
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class OrganizationReservedSchedulingQuota(OrganizationSchedulingQuota):
|
|
201
|
+
"""Describes a reserved scheduling quota for the organization.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
def __init__(self, machine_key: str, max_cores: int, running_cores_count: int, max_instances: int, running_instances_count: int):
|
|
205
|
+
"""Create a new OrganizationReservedSchedulingQuota object describing a reserved scheduling quota for the organization.
|
|
206
|
+
|
|
207
|
+
:param str machine_key: Machine key of the reservation.
|
|
208
|
+
:param int max_cores: Maximum number of cores that can be simultaneously used with this reserved machine specification.
|
|
209
|
+
:param int running_cores_count: Number of cores that are currently running with this reserved machine specification.
|
|
210
|
+
:param int max_instances: Maximum number of instances that can be simultaneously used with this reserved machine specification.
|
|
211
|
+
:param int running_instances_count: Number of instances that are currently running with this reserved machine specification.
|
|
212
|
+
:returns: The created :class:`~qarnot.computing_quotas.OrganizationReservedSchedulingQuota`.
|
|
213
|
+
"""
|
|
214
|
+
super().__init__(max_cores, running_cores_count, max_instances, running_instances_count)
|
|
215
|
+
self.machine_key = machine_key
|
|
216
|
+
""":type: :class:`str`
|
|
217
|
+
|
|
218
|
+
Machine key of the reservation.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
@classmethod
|
|
222
|
+
def from_json(cls, json: Dict[str, Any]):
|
|
223
|
+
"""Create a new OrganizationReservedSchedulingQuota object from json describing a reserved scheduling quota for a organization.
|
|
224
|
+
|
|
225
|
+
:param dict json: Dictionary representing the organization reserved scheduling quota
|
|
226
|
+
:returns: The created :class:`~qarnot.computing_quotas.OrganizationReservedSchedulingQuota`.
|
|
227
|
+
"""
|
|
228
|
+
if json is None:
|
|
229
|
+
return None
|
|
230
|
+
return cls(
|
|
231
|
+
json.get('machineKey'),
|
|
232
|
+
json.get('maxCores'),
|
|
233
|
+
json.get('runningCoresCount'),
|
|
234
|
+
json.get('maxInstances'),
|
|
235
|
+
json.get('runningInstancesCount'),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class OrganizationComputingQuotas(object):
|
|
240
|
+
"""Describes the organization's computing quotas.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
def __init__(self, name: str, flex: OrganizationSchedulingQuota, on_demand: OrganizationSchedulingQuota, reserved: List[OrganizationReservedSchedulingQuota]):
|
|
244
|
+
"""Create a new OrganizationComputingQuotas object describing the organization's computing quotas.
|
|
245
|
+
|
|
246
|
+
:param `str` name: Name of the organization.
|
|
247
|
+
:param `~qarnot.computing_quotas.OrganizationSchedulingQuota` flex: Quotas for Flex scheduling plan.
|
|
248
|
+
:param `~qarnot.computing_quotas.OrganizationSchedulingQuota` on_demand: Quotas for OnDemand scheduling plan.
|
|
249
|
+
:param List of `~qarnot.computing_quotas.OrganizationReservedSchedulingQuota` reserved: Quotas for Reserved scheduling plan.
|
|
250
|
+
:returns: The created :class:`~qarnot.computing_quotas.OrganizationComputingQuotas`.
|
|
251
|
+
"""
|
|
252
|
+
self.name = name
|
|
253
|
+
""":type: :class:`str`
|
|
254
|
+
|
|
255
|
+
Name of the organization."""
|
|
256
|
+
self.flex = flex
|
|
257
|
+
""":type: :class:`~qarnot.computing_quotas.OrganizationSchedulingQuota`
|
|
258
|
+
|
|
259
|
+
Quotas for Flex scheduling plan."""
|
|
260
|
+
self.on_demand = on_demand
|
|
261
|
+
""":type: :class:`~qarnot.computing_quotas.OrganizationSchedulingQuota`
|
|
262
|
+
|
|
263
|
+
Quotas for OnDemand scheduling plan."""
|
|
264
|
+
self.reserved = reserved
|
|
265
|
+
""":type: list(:class:`~qarnot.computing_quotas.OrganizationReservedSchedulingQuota`)
|
|
266
|
+
|
|
267
|
+
Quotas for Reserved scheduling plan."""
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def from_json(cls, json: Dict[str, Any]):
|
|
271
|
+
"""Create a new OrganizationComputingQuotas object from json describing the organization's computing quotas.
|
|
272
|
+
|
|
273
|
+
:param dict json: Dictionary representing the organization computing quota
|
|
274
|
+
:returns: The created :class:`~qarnot.computing_quotas.OrganizationComputingQuotas`.
|
|
275
|
+
"""
|
|
276
|
+
if json is None:
|
|
277
|
+
return None
|
|
278
|
+
return cls(
|
|
279
|
+
json.get('name'),
|
|
280
|
+
OrganizationSchedulingQuota.from_json(json.get('flex')),
|
|
281
|
+
OrganizationSchedulingQuota.from_json(json.get('onDemand')),
|
|
282
|
+
[OrganizationReservedSchedulingQuota.from_json(v) for v in json.get('reserved', []) if v is not None]
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class ComputingQuotas(object):
|
|
287
|
+
"""Describes user and organization computing quotas.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
def __init__(self, user_computing_quotas: Optional[UserComputingQuotas], organization_computing_quotas: Optional[OrganizationComputingQuotas] = None):
|
|
291
|
+
"""Create a new ComputingQuotas object describing user and organization computing quotas.
|
|
292
|
+
|
|
293
|
+
:param user_computing_quotas: the user related computing quotas
|
|
294
|
+
:type user_computing_quotas: `~qarnot.computing_quotas.UserComputingQuotas`, optional
|
|
295
|
+
:param organization_computing_quotas: the organization related computing quotas
|
|
296
|
+
:type organization_computing_quotas: `~qarnot.computing_quotas.OrganizationComputingQuotas`, optional
|
|
297
|
+
:returns: The created :class:`~qarnot.computing_quotas.ComputingQuotas`.
|
|
298
|
+
"""
|
|
299
|
+
self.user = user_computing_quotas
|
|
300
|
+
""":type: :class:`~qarnot.computing_quotas.UserComputingQuotas`
|
|
301
|
+
|
|
302
|
+
The user related computing quotas."""
|
|
303
|
+
self.organization = organization_computing_quotas
|
|
304
|
+
""":type: :class:`~qarnot.computing_quotas.OrganizationComputingQuotas`
|
|
305
|
+
|
|
306
|
+
The organization related computing quotas."""
|
|
307
|
+
|
|
308
|
+
@classmethod
|
|
309
|
+
def from_json(cls, json: Dict[str, Any]):
|
|
310
|
+
"""Create a new ComputingQuotas object from json describing user and organization computing quotas.
|
|
311
|
+
|
|
312
|
+
:param dict json: Dictionary representing the computing quotas
|
|
313
|
+
:returns: The created :class:`~qarnot.computing_quotas.ComputingQuotas`
|
|
314
|
+
"""
|
|
315
|
+
if json is None:
|
|
316
|
+
return None
|
|
317
|
+
user_computing_quotas = UserComputingQuotas.from_json(json.get('user'))
|
|
318
|
+
organization_computing_quotas = OrganizationComputingQuotas.from_json(json.get('organization'))
|
|
319
|
+
return cls(user_computing_quotas, organization_computing_quotas)
|
|
320
|
+
|
|
321
|
+
@classmethod
|
|
322
|
+
def from_json_legacy(cls, json: Dict[str, Any]):
|
|
323
|
+
if json is None:
|
|
324
|
+
return None
|
|
325
|
+
flex = UserSchedulingQuota(json.get('maxFlexCores'), json.get('runningFlexCoreCount'), json.get('maxFlexInstances'), json.get('runningFlexInstanceCount'))
|
|
326
|
+
onDemand = UserSchedulingQuota(json.get('maxOnDemandCores'), json.get('runningOnDemandCoreCount'), json.get('maxOnDemandInstances'), json.get('runningOnDemandInstanceCount'))
|
|
327
|
+
user = UserComputingQuotas(flex, onDemand, [])
|
|
328
|
+
return cls(user, None)
|
|
@@ -28,6 +28,7 @@ from .task import Task, BulkTaskResponse
|
|
|
28
28
|
from .pool import Pool
|
|
29
29
|
from .paginate import PaginateResponse, OffsetResponse
|
|
30
30
|
from .bucket import Bucket
|
|
31
|
+
from .computing_quotas import ComputingQuotas
|
|
31
32
|
from .job import Job
|
|
32
33
|
from ._filter import create_pool_filter, create_task_filter, create_job_filter
|
|
33
34
|
from ._retry import with_retry
|
|
@@ -95,8 +96,8 @@ class Connection(object):
|
|
|
95
96
|
unsafe=False
|
|
96
97
|
|
|
97
98
|
"""
|
|
98
|
-
self.logger = logger if logger is not None else Log.get_logger_for_stream(sys.stdout)
|
|
99
|
-
self.logger_stderr = logger if logger is not None else Log.get_logger_for_stream(sys.stderr) # to avoid breaking change of task stderr logs
|
|
99
|
+
self.logger = logger if logger is not None else Log.get_logger_for_stream(sys.stdout, "stdout")
|
|
100
|
+
self.logger_stderr = logger if logger is not None else Log.get_logger_for_stream(sys.stderr, "stderr") # to avoid breaking change of task stderr logs
|
|
100
101
|
self._version = "qarnot-sdk-python/" + __version__
|
|
101
102
|
self._http = requests.session()
|
|
102
103
|
self._retry_count = retry_count
|
|
@@ -997,21 +998,37 @@ class UserInfo(object):
|
|
|
997
998
|
""":type: :class:`int`
|
|
998
999
|
|
|
999
1000
|
Number of cores currently submitted or running."""
|
|
1000
|
-
self.
|
|
1001
|
+
self.computing_quotas = ComputingQuotas.from_json(info.get('computingQuotas')) or ComputingQuotas.from_json_legacy(info)
|
|
1002
|
+
""":type: :class:`~qarnot.computing_quotas.ComputingQuotas`
|
|
1003
|
+
|
|
1004
|
+
Computing quotas information of the user and his organization."""
|
|
1005
|
+
self.max_flex_instances = self.computing_quotas.user.flex.max_instances if self.computing_quotas is not None else info.get('maxFlexInstances')
|
|
1001
1006
|
""":type: :class:`int`
|
|
1002
1007
|
|
|
1008
|
+
.. deprecated:: v2.18.0
|
|
1009
|
+
Use `self.computing_quotas` instead.
|
|
1010
|
+
|
|
1003
1011
|
Maximum number of instances simultaneously used with Flex scheduling plan."""
|
|
1004
|
-
self.max_flex_cores = info.get('maxFlexCores')
|
|
1012
|
+
self.max_flex_cores = self.computing_quotas.user.flex.max_cores if self.computing_quotas is not None else info.get('maxFlexCores')
|
|
1005
1013
|
""":type: :class:`int`
|
|
1006
1014
|
|
|
1015
|
+
.. deprecated:: v2.18.0
|
|
1016
|
+
Use `self.computing_quotas` instead.
|
|
1017
|
+
|
|
1007
1018
|
Maximum number of cores simultaneously used with Flex scheduling plan."""
|
|
1008
|
-
self.max_on_demand_instances = info.get('maxOnDemandInstances')
|
|
1019
|
+
self.max_on_demand_instances = self.computing_quotas.user.on_demand.max_instances if self.computing_quotas is not None else info.get('maxOnDemandInstances')
|
|
1009
1020
|
""":type: :class:`int`
|
|
1010
1021
|
|
|
1022
|
+
.. deprecated:: v2.18.0
|
|
1023
|
+
Use `self.computing_quotas` instead.
|
|
1024
|
+
|
|
1011
1025
|
Maximum number of instances simultaneously used with OnDemand scheduling plan."""
|
|
1012
|
-
self.max_on_demand_cores = info.get('maxOnDemandCores')
|
|
1026
|
+
self.max_on_demand_cores = self.computing_quotas.user.on_demand.max_cores if self.computing_quotas is not None else info.get('maxOnDemandCores')
|
|
1013
1027
|
""":type: :class:`int`
|
|
1014
1028
|
|
|
1029
|
+
.. deprecated:: v2.18.0
|
|
1030
|
+
Use `self.computing_quotas` instead.
|
|
1031
|
+
|
|
1015
1032
|
Maximum number of cores simultaneously used with OnDemand scheduling plan."""
|
|
1016
1033
|
|
|
1017
1034
|
|
|
@@ -14,7 +14,7 @@ class Log():
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
@staticmethod
|
|
17
|
-
def get_logger_for_stream(stream: TextIO = None, log_format: str = DEFAULT_LOG_FORMAT):
|
|
17
|
+
def get_logger_for_stream(stream: TextIO = None, name: str = None, log_format: str = DEFAULT_LOG_FORMAT):
|
|
18
18
|
"""Create a logger whose output is a stream.
|
|
19
19
|
|
|
20
20
|
:param TextIO stream:
|
|
@@ -31,12 +31,13 @@ class Log():
|
|
|
31
31
|
:rtype: logging.Logger
|
|
32
32
|
:returns: The created logger.
|
|
33
33
|
"""
|
|
34
|
-
|
|
34
|
+
if name is None:
|
|
35
|
+
name = __name__
|
|
35
36
|
formatter = logging.Formatter(log_format)
|
|
36
37
|
handler = logging.StreamHandler(stream if stream is not None else sys.stdout)
|
|
37
38
|
handler.setFormatter(formatter)
|
|
38
39
|
|
|
39
|
-
logger = logging.getLogger(
|
|
40
|
+
logger = logging.getLogger(name)
|
|
40
41
|
logger.addHandler(handler)
|
|
41
42
|
logger.setLevel(logging.INFO)
|
|
42
43
|
return logger
|
|
@@ -114,6 +114,7 @@ class Pool(object):
|
|
|
114
114
|
self._queued_or_running_task_instances_count = 0.0
|
|
115
115
|
|
|
116
116
|
self._completion_time_to_live = "00:00:00"
|
|
117
|
+
self._max_time_queue_seconds: int = None
|
|
117
118
|
self._auto_delete = False
|
|
118
119
|
self._tasks_wait_for_synchronization = False
|
|
119
120
|
|
|
@@ -215,6 +216,8 @@ class Pool(object):
|
|
|
215
216
|
if 'completionTimeToLive' in json_pool:
|
|
216
217
|
self._completion_time_to_live = json_pool.get("completionTimeToLive")
|
|
217
218
|
|
|
219
|
+
self._max_time_queue_seconds = json_pool.get("maxTimeQueueSeconds", None)
|
|
220
|
+
|
|
218
221
|
if 'elasticProperty' in json_pool:
|
|
219
222
|
elasticProperty = json_pool.get("elasticProperty")
|
|
220
223
|
self._is_elastic = elasticProperty.get("isElastic")
|
|
@@ -297,6 +300,9 @@ class Pool(object):
|
|
|
297
300
|
json_pool['privileges'] = self._privileges.to_json()
|
|
298
301
|
json_pool['defaultRetrySettings'] = self._default_retry_settings.to_json()
|
|
299
302
|
|
|
303
|
+
if self._max_time_queue_seconds is not None:
|
|
304
|
+
json_pool['maxTimeQueueSeconds'] = self._max_time_queue_seconds
|
|
305
|
+
|
|
300
306
|
if self._scheduling_type is not None:
|
|
301
307
|
json_pool['schedulingType'] = self._scheduling_type.schedulingType
|
|
302
308
|
|
|
@@ -1431,6 +1437,22 @@ class Pool(object):
|
|
|
1431
1437
|
|
|
1432
1438
|
self._privileges._exportApiAndStorageCredentialsInEnvironment = True
|
|
1433
1439
|
|
|
1440
|
+
@property
|
|
1441
|
+
def max_time_queue_seconds(self):
|
|
1442
|
+
"""
|
|
1443
|
+
:type: :class:`uint`
|
|
1444
|
+
:getter: Max time to wait before time out when there is not any place to execute the pool.
|
|
1445
|
+
|
|
1446
|
+
pool's max time queue seconds
|
|
1447
|
+
"""
|
|
1448
|
+
self._update_if_summary()
|
|
1449
|
+
return self._max_time_queue_seconds
|
|
1450
|
+
|
|
1451
|
+
@max_time_queue_seconds.setter
|
|
1452
|
+
def max_time_queue_seconds(self, value: int):
|
|
1453
|
+
"""Setter for max_time_queue_seconds."""
|
|
1454
|
+
self._max_time_queue_seconds = value
|
|
1455
|
+
|
|
1434
1456
|
@property
|
|
1435
1457
|
def default_retry_settings(self) -> RetrySettings:
|
|
1436
1458
|
""":type: :class:`~qarnot.retry_settings.RetrySettings`
|
|
@@ -19,7 +19,7 @@ from os import makedirs, path
|
|
|
19
19
|
import time
|
|
20
20
|
import warnings
|
|
21
21
|
import sys
|
|
22
|
-
from typing import Dict, Optional, Union, List, Any, Callable
|
|
22
|
+
from typing import Dict, Optional, Union, List, Any, Callable, Sequence
|
|
23
23
|
|
|
24
24
|
from qarnot.carbon_facts import CarbonClient, CarbonFacts
|
|
25
25
|
from qarnot.retry_settings import RetrySettings
|
|
@@ -40,6 +40,7 @@ from .exceptions import MissingTaskException, MaxTaskException, NotEnoughCredits
|
|
|
40
40
|
|
|
41
41
|
try:
|
|
42
42
|
from progressbar import AnimatedMarker, Bar, Percentage, AdaptiveETA, ProgressBar
|
|
43
|
+
from progressbar.widgets import WidgetBase
|
|
43
44
|
except ImportError:
|
|
44
45
|
pass
|
|
45
46
|
|
|
@@ -172,6 +173,7 @@ class Task(object):
|
|
|
172
173
|
self._progress = None
|
|
173
174
|
self._execution_time = None
|
|
174
175
|
self._wall_time = None
|
|
176
|
+
self._max_time_queue_seconds: int = None
|
|
175
177
|
self._end_date = None
|
|
176
178
|
self._upload_results_on_cancellation: Optional[bool] = None
|
|
177
179
|
self._hardware_constraints: List[HardwareConstraint] = []
|
|
@@ -513,6 +515,7 @@ class Task(object):
|
|
|
513
515
|
self._progress = json_task.get("progress", None)
|
|
514
516
|
self._execution_time = json_task.get("executionTime", None)
|
|
515
517
|
self._wall_time = json_task.get("wallTime", None)
|
|
518
|
+
self._max_time_queue_seconds = json_task.get("maxTimeQueueSeconds", None)
|
|
516
519
|
self._end_date = json_task.get("endDate", None)
|
|
517
520
|
self._labels = json_task.get("labels", {})
|
|
518
521
|
self._hardware_constraints = [HardwareConstraint.from_json(hw_constraint_dict) for hw_constraint_dict in json_task.get("hardwareConstraints", [])]
|
|
@@ -589,7 +592,7 @@ class Task(object):
|
|
|
589
592
|
|
|
590
593
|
if live_progress:
|
|
591
594
|
try:
|
|
592
|
-
widgets = [
|
|
595
|
+
widgets: Sequence[WidgetBase | str] = [
|
|
593
596
|
Percentage(),
|
|
594
597
|
' ', AnimatedMarker(),
|
|
595
598
|
' ', Bar(),
|
|
@@ -1778,6 +1781,22 @@ class Task(object):
|
|
|
1778
1781
|
self._update_if_summary()
|
|
1779
1782
|
return self._wall_time
|
|
1780
1783
|
|
|
1784
|
+
@property
|
|
1785
|
+
def max_time_queue_seconds(self):
|
|
1786
|
+
"""
|
|
1787
|
+
:type: :class:`uint`
|
|
1788
|
+
:getter: Max time to wait before time out when there is not any place to execute the task.
|
|
1789
|
+
|
|
1790
|
+
task's max time queue seconds
|
|
1791
|
+
"""
|
|
1792
|
+
self._update_if_summary()
|
|
1793
|
+
return self._max_time_queue_seconds
|
|
1794
|
+
|
|
1795
|
+
@max_time_queue_seconds.setter
|
|
1796
|
+
def max_time_queue_seconds(self, value: int):
|
|
1797
|
+
"""Setter for max_time_queue_seconds."""
|
|
1798
|
+
self._max_time_queue_seconds = value
|
|
1799
|
+
|
|
1781
1800
|
@property
|
|
1782
1801
|
def snapshot_interval(self):
|
|
1783
1802
|
"""
|
|
@@ -1836,6 +1855,9 @@ class Task(object):
|
|
|
1836
1855
|
self._resource_object_advanced = [x.to_json() for x in self._resource_objects]
|
|
1837
1856
|
json_task['advancedResourceBuckets'] = self._resource_object_advanced
|
|
1838
1857
|
|
|
1858
|
+
if self._max_time_queue_seconds is not None:
|
|
1859
|
+
json_task['maxTimeQueueSeconds'] = self._max_time_queue_seconds
|
|
1860
|
+
|
|
1839
1861
|
if self._result_object is not None:
|
|
1840
1862
|
json_task['resultBucket'] = self._result_object.uuid
|
|
1841
1863
|
if self._result_object._cache_ttl_sec is not None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: qarnot
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.18.0
|
|
4
4
|
Summary: Qarnot Computing SDK
|
|
5
5
|
Home-page: https://computing.qarnot.com
|
|
6
6
|
Author: Qarnot computing
|
|
@@ -27,6 +27,7 @@ Dynamic: classifier
|
|
|
27
27
|
Dynamic: description
|
|
28
28
|
Dynamic: home-page
|
|
29
29
|
Dynamic: license
|
|
30
|
+
Dynamic: license-file
|
|
30
31
|
Dynamic: requires-dist
|
|
31
32
|
Dynamic: requires-python
|
|
32
33
|
Dynamic: summary
|
|
@@ -22,6 +22,7 @@ doc/source/api/connection.rst
|
|
|
22
22
|
doc/source/api/exceptions.rst
|
|
23
23
|
doc/source/api/compute/carbon_facts.rst
|
|
24
24
|
doc/source/api/compute/computeindex.rst
|
|
25
|
+
doc/source/api/compute/computing_quotas.rst
|
|
25
26
|
doc/source/api/compute/forced_network_rule.rst
|
|
26
27
|
doc/source/api/compute/hardware_constraint.rst
|
|
27
28
|
doc/source/api/compute/job.rst
|
|
@@ -45,6 +46,7 @@ qarnot/_version.py
|
|
|
45
46
|
qarnot/advanced_bucket.py
|
|
46
47
|
qarnot/bucket.py
|
|
47
48
|
qarnot/carbon_facts.py
|
|
49
|
+
qarnot/computing_quotas.py
|
|
48
50
|
qarnot/connection.py
|
|
49
51
|
qarnot/error.py
|
|
50
52
|
qarnot/exceptions.py
|
|
@@ -442,7 +442,7 @@ class TestConnectionPaginateMethods():
|
|
|
442
442
|
next(iterator)
|
|
443
443
|
assert mock_page_call.call_count == 2
|
|
444
444
|
|
|
445
|
-
def
|
|
445
|
+
def test_user_information_legacy_computing_quotas(self):
|
|
446
446
|
connec = self.get_connection()
|
|
447
447
|
with patch("qarnot.connection.Connection._get") as get_user:
|
|
448
448
|
user_json = {
|
|
@@ -464,8 +464,12 @@ class TestConnectionPaginateMethods():
|
|
|
464
464
|
"runningCoreCount":19,
|
|
465
465
|
"maxFlexInstances":20,
|
|
466
466
|
"maxFlexCores":21,
|
|
467
|
-
"
|
|
468
|
-
"
|
|
467
|
+
"runningFlexCoreCount":22,
|
|
468
|
+
"runningFlexInstanceCount":23,
|
|
469
|
+
"maxOnDemandInstances":24,
|
|
470
|
+
"maxOnDemandCores":25,
|
|
471
|
+
"runningOnDemandCoreCount":26,
|
|
472
|
+
"runningOnDemandInstanceCount":27,
|
|
469
473
|
}
|
|
470
474
|
get_user.return_value.status_code = 200
|
|
471
475
|
get_user.return_value.json.return_value = user_json
|
|
@@ -492,6 +496,117 @@ class TestConnectionPaginateMethods():
|
|
|
492
496
|
assert user.max_on_demand_instances == user_json['maxOnDemandInstances']
|
|
493
497
|
assert user.max_on_demand_cores == user_json['maxOnDemandCores']
|
|
494
498
|
|
|
499
|
+
assert user.computing_quotas.user.flex.max_instances == user_json['maxFlexInstances']
|
|
500
|
+
assert user.computing_quotas.user.flex.max_cores == user_json['maxFlexCores']
|
|
501
|
+
assert user.computing_quotas.user.flex.running_cores_count == user_json['runningFlexCoreCount']
|
|
502
|
+
assert user.computing_quotas.user.flex.running_instances_count == user_json['runningFlexInstanceCount']
|
|
503
|
+
assert user.computing_quotas.user.on_demand.max_instances == user_json['maxOnDemandInstances']
|
|
504
|
+
assert user.computing_quotas.user.on_demand.max_cores == user_json['maxOnDemandCores']
|
|
505
|
+
assert user.computing_quotas.user.on_demand.running_cores_count == user_json['runningOnDemandCoreCount']
|
|
506
|
+
assert user.computing_quotas.user.on_demand.running_instances_count == user_json['runningOnDemandInstanceCount']
|
|
507
|
+
assert user.computing_quotas.user.reserved == []
|
|
508
|
+
|
|
509
|
+
def test_user_information_new_format_computing_quotas(self):
|
|
510
|
+
connec = self.get_connection()
|
|
511
|
+
with patch("qarnot.connection.Connection._get") as get_user:
|
|
512
|
+
user_json = {
|
|
513
|
+
"email":"",
|
|
514
|
+
"maxBucket":5,
|
|
515
|
+
"bucketCount":6,
|
|
516
|
+
"quotaBytesBucket":7,
|
|
517
|
+
"usedQuotaBytesBucket":8,
|
|
518
|
+
"taskCount":9,
|
|
519
|
+
"maxTask":10,
|
|
520
|
+
"runningTaskCount":11,
|
|
521
|
+
"maxRunningTask":12,
|
|
522
|
+
"maxInstances":13,
|
|
523
|
+
"maxPool":14,
|
|
524
|
+
"poolCount":15,
|
|
525
|
+
"maxRunningPool":16,
|
|
526
|
+
"runningPoolCount":17,
|
|
527
|
+
"runningInstanceCount":18,
|
|
528
|
+
"runningCoreCount":19,
|
|
529
|
+
"computingQuotas": {
|
|
530
|
+
"user": {
|
|
531
|
+
"flex": {
|
|
532
|
+
"maxInstances": 64,
|
|
533
|
+
"maxCores": 512,
|
|
534
|
+
"runningInstancesCount": 1,
|
|
535
|
+
"runningCoresCount": 2
|
|
536
|
+
},
|
|
537
|
+
"onDemand": {
|
|
538
|
+
"maxInstances": 642,
|
|
539
|
+
"maxCores": 5122,
|
|
540
|
+
"runningInstancesCount": 3,
|
|
541
|
+
"runningCoresCount": 4
|
|
542
|
+
},
|
|
543
|
+
"reserved": [
|
|
544
|
+
{
|
|
545
|
+
"machineKey": "string",
|
|
546
|
+
"maxInstances": 643,
|
|
547
|
+
"maxCores": 5123,
|
|
548
|
+
"runningInstancesCount": 5,
|
|
549
|
+
"runningCoresCount": 6
|
|
550
|
+
}
|
|
551
|
+
]
|
|
552
|
+
},
|
|
553
|
+
"organization": {
|
|
554
|
+
"name": "string",
|
|
555
|
+
"flex": {
|
|
556
|
+
"maxInstances": 644,
|
|
557
|
+
"maxCores": 5124,
|
|
558
|
+
"runningInstancesCount": 7,
|
|
559
|
+
"runningCoresCount": 8
|
|
560
|
+
},
|
|
561
|
+
"onDemand": {
|
|
562
|
+
"maxInstances": 645,
|
|
563
|
+
"maxCores": 5125,
|
|
564
|
+
"runningInstancesCount": 9,
|
|
565
|
+
"runningCoresCount": 10
|
|
566
|
+
},
|
|
567
|
+
"reserved": [
|
|
568
|
+
{
|
|
569
|
+
"machineKey": "string",
|
|
570
|
+
"maxInstances": 646,
|
|
571
|
+
"maxCores": 5126,
|
|
572
|
+
"runningInstancesCount": 11,
|
|
573
|
+
"runningCoresCount": 12
|
|
574
|
+
}
|
|
575
|
+
]
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
}
|
|
579
|
+
get_user.return_value.status_code = 200
|
|
580
|
+
get_user.return_value.json.return_value = user_json
|
|
581
|
+
user = connec.user_info
|
|
582
|
+
assert user.email == user_json.get('email', '')
|
|
583
|
+
assert user.max_bucket == user_json['maxBucket']
|
|
584
|
+
assert user.bucket_count == user_json.get('bucketCount', -1)
|
|
585
|
+
assert user.quota_bytes_bucket == user_json['quotaBytesBucket']
|
|
586
|
+
assert user.used_quota_bytes_bucket == user_json['usedQuotaBytesBucket']
|
|
587
|
+
assert user.task_count == user_json['taskCount']
|
|
588
|
+
assert user.max_task == user_json['maxTask']
|
|
589
|
+
assert user.running_task_count == user_json['runningTaskCount']
|
|
590
|
+
assert user.max_running_task == user_json['maxRunningTask']
|
|
591
|
+
assert user.max_instances == user_json['maxInstances']
|
|
592
|
+
assert user.max_pool == user_json['maxPool']
|
|
593
|
+
assert user.pool_count == user_json['poolCount']
|
|
594
|
+
assert user.max_running_pool == user_json['maxRunningPool']
|
|
595
|
+
assert user.running_pool_count == user_json['runningPoolCount']
|
|
596
|
+
assert user.running_instance_count == user_json['runningInstanceCount']
|
|
597
|
+
assert user.running_core_count == user_json['runningCoreCount']
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
assert user.computing_quotas.user.flex.max_instances == user_json['computingQuotas']['user']['flex']['maxInstances']
|
|
601
|
+
assert user.computing_quotas.user.flex.max_cores == user_json['computingQuotas']['user']['flex']['maxCores']
|
|
602
|
+
assert user.computing_quotas.user.flex.running_cores_count == user_json['computingQuotas']['user']['flex']['runningCoresCount']
|
|
603
|
+
assert user.computing_quotas.user.flex.running_instances_count == user_json['computingQuotas']['user']['flex']['runningInstancesCount']
|
|
604
|
+
|
|
605
|
+
assert user.computing_quotas.organization.on_demand.max_instances == user_json['computingQuotas']['organization']['onDemand']['maxInstances']
|
|
606
|
+
assert user.computing_quotas.organization.on_demand.max_cores == user_json['computingQuotas']['organization']['onDemand']['maxCores']
|
|
607
|
+
assert user.computing_quotas.organization.on_demand.running_cores_count == user_json['computingQuotas']['organization']['onDemand']['runningCoresCount']
|
|
608
|
+
assert user.computing_quotas.organization.on_demand.running_instances_count == user_json['computingQuotas']['organization']['onDemand']['runningInstancesCount']
|
|
609
|
+
|
|
495
610
|
def test_user_with_missing_information(self): # To ensure it doesn't throw when modifying a field in rest-computing side
|
|
496
611
|
connec = self.get_connection()
|
|
497
612
|
with patch("qarnot.connection.Connection._get") as get_user:
|
|
@@ -44,14 +44,31 @@ class TestPoolProperties:
|
|
|
44
44
|
pool.completion_ttl = "4.11:08:06"
|
|
45
45
|
assert "4.11:08:06" == pool.completion_ttl
|
|
46
46
|
|
|
47
|
-
def
|
|
47
|
+
def test_fields_are_in_json_from_task(self):
|
|
48
48
|
pool = Pool(self.conn, "pool-name", "profile")
|
|
49
49
|
pool.completion_ttl = "4.11:08:06"
|
|
50
50
|
pool.auto_delete = True
|
|
51
|
+
pool.max_time_queue_seconds = 10
|
|
51
52
|
json_pool = pool._to_json()
|
|
52
53
|
|
|
53
54
|
assert json_pool['completionTimeToLive'] == '4.11:08:06'
|
|
54
55
|
assert json_pool['autoDeleteOnCompletion'] == True
|
|
56
|
+
assert json_pool['maxTimeQueueSeconds'] == 10
|
|
57
|
+
|
|
58
|
+
def test_fields_are_in_task_from_json(self):
|
|
59
|
+
json_pool = {}
|
|
60
|
+
json_pool['maxTimeQueueSeconds'] = 10
|
|
61
|
+
|
|
62
|
+
# fields that need to be non null for the deserialization to not fail
|
|
63
|
+
json_pool['creationDate'] = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
64
|
+
json_pool['uuid'] = str(uuid.uuid4())
|
|
65
|
+
json_pool['state'] = 'Submitted'
|
|
66
|
+
json_pool['runningCoreCount'] = 0
|
|
67
|
+
json_pool['runningInstanceCount'] = 0
|
|
68
|
+
|
|
69
|
+
pool_from_json = Pool(self.conn, "pool-name", "profile")
|
|
70
|
+
pool_from_json._update(json_pool)
|
|
71
|
+
assert pool_from_json.max_time_queue_seconds == 10
|
|
55
72
|
|
|
56
73
|
def test_update_resources_send_the_good_url(self):
|
|
57
74
|
update_connection = MockConnection()
|
|
@@ -70,6 +87,7 @@ class TestPoolProperties:
|
|
|
70
87
|
("execution_time", None),
|
|
71
88
|
("end_date", None),
|
|
72
89
|
("tasks_default_wait_for_pool_resources_synchronization", False),
|
|
90
|
+
("max_time_queue_seconds", None),
|
|
73
91
|
("privileges", Privileges()),
|
|
74
92
|
])
|
|
75
93
|
def test_pool_property_default_value(self, property_name, expected_value):
|
|
@@ -96,12 +114,21 @@ class TestPoolProperties:
|
|
|
96
114
|
("privileges", default_json_pool["privileges"]),
|
|
97
115
|
("defaultRetrySettings", default_json_pool["defaultRetrySettings"]),
|
|
98
116
|
])
|
|
99
|
-
def
|
|
117
|
+
def test_pool_property_with_default_send_to_json_representation(self, property_name, expected_value):
|
|
100
118
|
pool = Pool(self.conn, "pool-name", "profile")
|
|
101
119
|
pool._update(default_json_pool)
|
|
102
120
|
pool_json = pool._to_json()
|
|
103
121
|
assert pool_json[property_name] == expected_value
|
|
104
122
|
|
|
123
|
+
@pytest.mark.parametrize("property_name", [
|
|
124
|
+
("max_time_queue_seconds"),
|
|
125
|
+
])
|
|
126
|
+
def test_task_property_default_not_send_in_json(self, property_name):
|
|
127
|
+
pool = Pool(self.conn, "pool-name", "profile")
|
|
128
|
+
pool._update(default_json_pool)
|
|
129
|
+
pool_json = pool._to_json()
|
|
130
|
+
assert property_name not in pool_json
|
|
131
|
+
|
|
105
132
|
@pytest.mark.parametrize("property_name, set_value, expected_value", [
|
|
106
133
|
("name", "name", "name")
|
|
107
134
|
])
|
|
@@ -50,14 +50,31 @@ class TestTaskProperties:
|
|
|
50
50
|
task.completion_ttl = "4.11:08:06"
|
|
51
51
|
assert "4.11:08:06" == task.completion_ttl
|
|
52
52
|
|
|
53
|
-
def
|
|
53
|
+
def test_fields_are_in_json_from_task(self, mock_conn):
|
|
54
54
|
task = Task(mock_conn, "task-name")
|
|
55
55
|
task.completion_ttl = "4.11:08:06"
|
|
56
56
|
task.auto_delete = True
|
|
57
|
+
task.max_time_queue_seconds = 10
|
|
57
58
|
json_task = task._to_json() # pylint: disable=protected-access
|
|
58
59
|
|
|
59
60
|
assert json_task['completionTimeToLive'] == '4.11:08:06'
|
|
60
61
|
assert json_task['autoDeleteOnCompletion'] is True
|
|
62
|
+
assert json_task['maxTimeQueueSeconds'] == 10
|
|
63
|
+
|
|
64
|
+
def test_fields_are_in_task_from_json(self, mock_conn):
|
|
65
|
+
json_task = {}
|
|
66
|
+
json_task['maxTimeQueueSeconds'] = 10
|
|
67
|
+
|
|
68
|
+
# fields that need to be non null for the deserialization to not fail
|
|
69
|
+
json_task['creationDate'] = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
70
|
+
json_task['uuid'] = str(uuid.uuid4())
|
|
71
|
+
json_task['state'] = 'Submitted'
|
|
72
|
+
json_task['runningCoreCount'] = 0
|
|
73
|
+
json_task['runningInstanceCount'] = 0
|
|
74
|
+
|
|
75
|
+
task = Task.from_json(mock_conn, json_task)
|
|
76
|
+
assert task.max_time_queue_seconds == 10
|
|
77
|
+
|
|
61
78
|
|
|
62
79
|
@pytest.mark.parametrize("property_name, expected_value", [
|
|
63
80
|
("previous_state", None),
|
|
@@ -69,6 +86,7 @@ class TestTaskProperties:
|
|
|
69
86
|
("execution_time", None),
|
|
70
87
|
("wall_time", None),
|
|
71
88
|
("end_date", None),
|
|
89
|
+
("max_time_queue_seconds", None),
|
|
72
90
|
("privileges", Privileges()),
|
|
73
91
|
])
|
|
74
92
|
def test_task_property_default_value(self, mock_conn, property_name, expected_value):
|
|
@@ -103,6 +121,14 @@ class TestTaskProperties:
|
|
|
103
121
|
task_json = task._to_json()
|
|
104
122
|
assert task_json[property_name] == expected_value
|
|
105
123
|
|
|
124
|
+
@pytest.mark.parametrize("property_name", [
|
|
125
|
+
("max_time_queue_seconds"),
|
|
126
|
+
])
|
|
127
|
+
def test_task_property_default_not_send_in_json(self, mock_conn, property_name):
|
|
128
|
+
task = Task(mock_conn, "task-name")
|
|
129
|
+
task_json = task._to_json()
|
|
130
|
+
assert property_name not in task_json
|
|
131
|
+
|
|
106
132
|
@pytest.mark.parametrize("property_name, set_value, expected_value", [
|
|
107
133
|
("name", "name", "name")
|
|
108
134
|
])
|
|
@@ -499,8 +525,8 @@ class TestTaskProperties:
|
|
|
499
525
|
capturedStderr = StringIO()
|
|
500
526
|
sys.stderr = capturedStderr
|
|
501
527
|
|
|
502
|
-
mock_conn.logger = Log.get_logger_for_stream(sys.stdout)
|
|
503
|
-
mock_conn.logger_stderr = Log.get_logger_for_stream(sys.stderr)
|
|
528
|
+
mock_conn.logger = Log.get_logger_for_stream(sys.stdout, "stdout")
|
|
529
|
+
mock_conn.logger_stderr = Log.get_logger_for_stream(sys.stderr, "stderr")
|
|
504
530
|
# Mock the responses for task update
|
|
505
531
|
task = Task(mock_conn, "task-name")
|
|
506
532
|
task_json = copy.deepcopy(default_json_task)
|
|
@@ -563,6 +589,10 @@ class TestTaskProperties:
|
|
|
563
589
|
assert warn_logs is not None, "the stderr should contain task update stderr"
|
|
564
590
|
for state in states:
|
|
565
591
|
assert state in info_logs, "All state updates should be printed on stdout"
|
|
592
|
+
assert info_logs.count(state) == 1, "All state logs should be printed only once"
|
|
566
593
|
for i in range(0,len(states)):
|
|
567
594
|
assert "stdout %s" % i in info_logs, "All task stdout should be printed to user logs stream with info level"
|
|
595
|
+
assert info_logs.count("stdout %s" % i) == 2, "All stdout logs should be printed only once" # each stdout logs are here sent twice in the test (before and after state change)
|
|
568
596
|
assert "stderr %s" % i in warn_logs, "All task stderr should be printed to user logs stream with warning level"
|
|
597
|
+
assert warn_logs.count("stderr %s" % i) == 2, "All stderr logs should be printed only once" # each stderr logs are here sent twice in the test (before and after state change)
|
|
598
|
+
assert "stderr %s" % i not in info_logs, "All task stderr should not be printed to user logs stream with info level"
|
qarnot-2.17.0/requirements.txt
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|