yellowdog-python-examples 7.13.0__tar.gz → 7.14.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.
- {yellowdog_python_examples-7.13.0/yellowdog_python_examples.egg-info → yellowdog_python_examples-7.14.0}/PKG-INFO +1 -1
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/README.md +24 -1
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/pyproject.toml +1 -0
- yellowdog_python_examples-7.14.0/yellowdog_cli/__init__.py +1 -0
- yellowdog_python_examples-7.14.0/yellowdog_cli/analyse.py +715 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/provision.py +3 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/args.py +46 -19
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/interactive.py +18 -3
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/load_config.py +10 -7
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/printing.py +2 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0/yellowdog_python_examples.egg-info}/PKG-INFO +1 -1
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_python_examples.egg-info/SOURCES.txt +1 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_python_examples.egg-info/entry_points.txt +1 -0
- yellowdog_python_examples-7.13.0/yellowdog_cli/__init__.py +0 -1
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/LICENSE +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/PYPI_README.md +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/requirements.txt +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/setup.cfg +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/tests/test_create_remove.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/tests/test_demos.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/tests/test_dryruns.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/tests/test_entrypoints.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/tests/test_gui.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/tests/test_list.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/tests/test_objects.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/tests/test_variable_processing.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/abort.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/admin.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/boost.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/cancel.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/cloudwizard.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/create.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/delete.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/download.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/follow.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/format_json.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/hold.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/instantiate.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/jsonnet2json.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/list.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/remove.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/resize.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/show.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/shutdown.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/start.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/submit.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/terminate.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/upload.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/__init__.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/check_imports.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/cloudwizard_aws.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/cloudwizard_aws_types.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/cloudwizard_azure.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/cloudwizard_common.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/cloudwizard_gcp.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/compact_json.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/config_types.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/csv_data.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/entity_utils.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/follow_utils.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/items.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/load_resources.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/misc_utils.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/property_names.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/provision_utils.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/rich_console_input_fixed.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/settings.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/start_hold_common.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/submit_utils.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/type_check.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/upload_utils.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/validate_properties.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/variables.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/wrapper.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/ydid_utils.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/version.py +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_python_examples.egg-info/dependency_links.txt +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_python_examples.egg-info/requires.txt +0 -0
- {yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_python_examples.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: yellowdog-python-examples
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.14.0
|
|
4
4
|
Summary: Python CLI commands using the YellowDog Python SDK
|
|
5
5
|
Author-email: YellowDog Limited <support@yellowdog.co>
|
|
6
6
|
Project-URL: Homepage, https://github.com/yellowdog/python-examples
|
|
@@ -123,9 +123,10 @@
|
|
|
123
123
|
* [yd-hold](#yd-hold)
|
|
124
124
|
* [yd-boost](#yd-boost)
|
|
125
125
|
* [yd-show](#yd-show)
|
|
126
|
+
* [yd-analyse](#yd-analyse)
|
|
126
127
|
|
|
127
128
|
<!-- Created by https://github.com/ekalinin/github-markdown-toc -->
|
|
128
|
-
<!-- Added by: pwt, at:
|
|
129
|
+
<!-- Added by: pwt, at: Wed Feb 12 12:13:59 GMT 2025 -->
|
|
129
130
|
|
|
130
131
|
<!--te-->
|
|
131
132
|
|
|
@@ -159,6 +160,7 @@ The commands provide the following capabilities:
|
|
|
159
160
|
- **Creating, Updating and Removing** Source Templates, Compute Templates, Keyrings, Credentials, Namespace Storage Configurations, Image Families, Allowances, Configured Worker Pools, User Attributes, and Namespace Policies with the **`yd-create`** and **`yd-remove`** commands
|
|
160
161
|
- **Following Event Streams** for Work Requirements, Worker Pools and Compute Requirements with the **`yd-follow`** command
|
|
161
162
|
- **Showing** the details of any YellowDog entity using its YellowDog ID with the **`yd-show`** command
|
|
163
|
+
- **Analysing** whether worker pools are match for task groups
|
|
162
164
|
|
|
163
165
|
The operation of the commands is controlled using TOML configuration files and/or environment variables and command line arguments. In addition, Work Requirements and Worker Pools can be defined using JSON files providing extensive configurability.
|
|
164
166
|
|
|
@@ -2765,3 +2767,24 @@ The `yd-show` command will show the details (in JSON) of any YellowDog entity th
|
|
|
2765
2767
|
- Image Families, Image Groups, and Images
|
|
2766
2768
|
- Keyrings
|
|
2767
2769
|
- Allowances
|
|
2770
|
+
|
|
2771
|
+
## yd-analyse
|
|
2772
|
+
|
|
2773
|
+
The `yd-analyse` command takes a Work Requirement or Task Group ID, and matches the selected Task Group against the available Nodes/Workers in selected Worker Pools.
|
|
2774
|
+
|
|
2775
|
+
```commandline
|
|
2776
|
+
yd-analyse ydid:taskgrp:000000:83587010-5e26-4174-92a7-c7cc2612638d:1
|
|
2777
|
+
```
|
|
2778
|
+
|
|
2779
|
+
The command checks if the **Run Specification** of a Task Group matches the properties of the selected Worker Pools and their registered Nodes and Workers, meaning there are Workers in the Worker Pool that could be claimed by the Task Group and that the Worker Pool would be a candidate for scaling up to meet the demands of the Task Group.
|
|
2780
|
+
|
|
2781
|
+
A detailed matching report showing the match against each specific property can be created, which can be used to highlight which properties are preventing a Worker Pool match.
|
|
2782
|
+
|
|
2783
|
+
The match status of a Worker Pool falls into one of four categories:
|
|
2784
|
+
|
|
2785
|
+
| **Match Status** | **Meaning** |
|
|
2786
|
+
|------------------|-----------------------------------------------------------------------------------------------------------------|
|
|
2787
|
+
| **YES** | The Worker Pool and every Node/Worker that has registered so far match the Task Group. |
|
|
2788
|
+
| **NO** | The Worker Pool and/or none of the Nodes/Workers that have registered so far match the Task Group. |
|
|
2789
|
+
| **MAYBE** | The Worker Pool matches the Task Group but no Nodes have yet registered, so Node/Worker properties are unknown. |
|
|
2790
|
+
| **PARTIAL** | The Worker Pool and some of the Nodes/Workers that have registered are a match for the Task Group. |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "7.14.0"
|
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
A script to analyse task groups and check for matching worker pools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from functools import cache
|
|
10
|
+
from typing import List
|
|
11
|
+
|
|
12
|
+
from tabulate import tabulate
|
|
13
|
+
from yellowdog_client.model import (
|
|
14
|
+
DoubleRange,
|
|
15
|
+
Node,
|
|
16
|
+
NodeSearch,
|
|
17
|
+
TaskGroup,
|
|
18
|
+
WorkerPool,
|
|
19
|
+
WorkRequirement,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from yellowdog_cli.utils.interactive import get_selected_list_items, select
|
|
23
|
+
from yellowdog_cli.utils.printing import (
|
|
24
|
+
indent,
|
|
25
|
+
print_error,
|
|
26
|
+
print_log,
|
|
27
|
+
print_table_core,
|
|
28
|
+
print_warning,
|
|
29
|
+
)
|
|
30
|
+
from yellowdog_cli.utils.wrapper import ARGS_PARSER, CLIENT, main_wrapper
|
|
31
|
+
from yellowdog_cli.utils.ydid_utils import YDIDType, get_ydid_type
|
|
32
|
+
|
|
33
|
+
NONE_STRING = "NONE"
|
|
34
|
+
UNKNOWN_STRING = "NOT YET KNOWN"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MatchType(Enum):
|
|
38
|
+
YES = "YES" # Definite match to the worker pool (so far)
|
|
39
|
+
NO = "NO" # Definite non-match to the worker pool
|
|
40
|
+
MAYBE = "MAYBE (no Nodes have registered yet)" # Possible match to the worker pool; no nodes yet
|
|
41
|
+
PARTIAL = "PARTIAL (at least one Node matches, but not all)" # Some nodes in the worker pool match
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class PropertyMatch:
|
|
46
|
+
property_name: str
|
|
47
|
+
task_group_value: str
|
|
48
|
+
worker_pool_values: str
|
|
49
|
+
match: MatchType
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MatchReport:
|
|
53
|
+
"""
|
|
54
|
+
Class to contain and report on whether a worker pool
|
|
55
|
+
matches a task group.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
worker_pool_name: str,
|
|
61
|
+
worker_pool_id: str,
|
|
62
|
+
worker_tags: PropertyMatch,
|
|
63
|
+
task_types: PropertyMatch,
|
|
64
|
+
instance_types: PropertyMatch,
|
|
65
|
+
providers: PropertyMatch,
|
|
66
|
+
regions: PropertyMatch,
|
|
67
|
+
namespaces: PropertyMatch,
|
|
68
|
+
ram: PropertyMatch,
|
|
69
|
+
vcpus: PropertyMatch,
|
|
70
|
+
):
|
|
71
|
+
self.worker_pool_name = worker_pool_name
|
|
72
|
+
self.worker_pool_id = worker_pool_id
|
|
73
|
+
self._namespaces = namespaces
|
|
74
|
+
self._worker_tags = worker_tags
|
|
75
|
+
self._task_types = task_types
|
|
76
|
+
self._instance_types = instance_types
|
|
77
|
+
self._providers = providers
|
|
78
|
+
self._regions = regions
|
|
79
|
+
self._ram = ram
|
|
80
|
+
self._vcpus = vcpus
|
|
81
|
+
self._property_match_list = [
|
|
82
|
+
self._worker_tags,
|
|
83
|
+
self._namespaces,
|
|
84
|
+
self._task_types,
|
|
85
|
+
self._instance_types,
|
|
86
|
+
self._providers,
|
|
87
|
+
self._regions,
|
|
88
|
+
self._ram,
|
|
89
|
+
self._vcpus,
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
@cache
|
|
93
|
+
def summary(self) -> MatchType:
|
|
94
|
+
"""
|
|
95
|
+
Summarise the overall match status for the worker pool.
|
|
96
|
+
"""
|
|
97
|
+
if all(p.match == MatchType.YES for p in self._property_match_list):
|
|
98
|
+
return MatchType.YES
|
|
99
|
+
|
|
100
|
+
elif any(p.match == MatchType.NO for p in self._property_match_list):
|
|
101
|
+
return MatchType.NO
|
|
102
|
+
|
|
103
|
+
elif all(
|
|
104
|
+
p.match == MatchType.YES or p.match == MatchType.PARTIAL
|
|
105
|
+
for p in self._property_match_list
|
|
106
|
+
):
|
|
107
|
+
return MatchType.PARTIAL
|
|
108
|
+
|
|
109
|
+
elif all(
|
|
110
|
+
p.match == MatchType.YES or p.match == MatchType.MAYBE
|
|
111
|
+
for p in self._property_match_list
|
|
112
|
+
):
|
|
113
|
+
return MatchType.MAYBE
|
|
114
|
+
|
|
115
|
+
# Shouldn't get here
|
|
116
|
+
print_warning("Unable to calculate YES/PARTIAL/MAYBE/NO summary")
|
|
117
|
+
return MatchType.NO
|
|
118
|
+
|
|
119
|
+
def print_detailed_report(self):
|
|
120
|
+
"""
|
|
121
|
+
Print a detailed matching report for the worker pool.
|
|
122
|
+
"""
|
|
123
|
+
if self.summary() == MatchType.YES:
|
|
124
|
+
match_str = "MATCHING"
|
|
125
|
+
elif self.summary() == MatchType.MAYBE:
|
|
126
|
+
match_str = "POSSIBLY MATCHING"
|
|
127
|
+
elif self.summary() == MatchType.PARTIAL:
|
|
128
|
+
match_str = "PARTIALLY MATCHING"
|
|
129
|
+
else:
|
|
130
|
+
match_str = "NON-MATCHING"
|
|
131
|
+
print_log(
|
|
132
|
+
f"Detailed report for {match_str} worker pool "
|
|
133
|
+
f"'{self.worker_pool_name}' ({self.worker_pool_id})",
|
|
134
|
+
override_quiet=True,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Print table
|
|
138
|
+
header_row = [
|
|
139
|
+
"Property Name",
|
|
140
|
+
"Task Group Run Specification",
|
|
141
|
+
"Worker Pool Nodes/Workers",
|
|
142
|
+
"Match Status",
|
|
143
|
+
]
|
|
144
|
+
table_rows = []
|
|
145
|
+
for p in self._property_match_list:
|
|
146
|
+
table_rows.append(
|
|
147
|
+
[
|
|
148
|
+
p.property_name,
|
|
149
|
+
p.task_group_value,
|
|
150
|
+
p.worker_pool_values,
|
|
151
|
+
p.match.value,
|
|
152
|
+
]
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
print_table_core(
|
|
156
|
+
indent(
|
|
157
|
+
tabulate(table_rows, headers=header_row, tablefmt="simple_outline"),
|
|
158
|
+
indent_width=4,
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class WorkerPools:
|
|
164
|
+
"""
|
|
165
|
+
Class to contain cached worker pools, and to check for matches.
|
|
166
|
+
Populates once for each run of the script.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def __init__(self):
|
|
170
|
+
self._populated = False
|
|
171
|
+
self._worker_pools: List[WorkerPool] = []
|
|
172
|
+
|
|
173
|
+
def populate(self) -> bool:
|
|
174
|
+
"""
|
|
175
|
+
Populate the worker pool and compute requirement data, if not
|
|
176
|
+
already populated.
|
|
177
|
+
"""
|
|
178
|
+
if self._populated:
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
self._worker_pools = self._get_selected_worker_pools()
|
|
182
|
+
if len(self._worker_pools) == 0:
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
self._populated = True
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
def check_task_group_for_matching_worker_pools(
|
|
189
|
+
self, task_group: TaskGroup
|
|
190
|
+
) -> List[MatchReport]:
|
|
191
|
+
"""
|
|
192
|
+
Check a task group for matches with the selected worker pools.
|
|
193
|
+
"""
|
|
194
|
+
return [
|
|
195
|
+
self._check_worker_pool_for_match(worker_pool, task_group)
|
|
196
|
+
for worker_pool in self._worker_pools
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
def _check_worker_pool_for_match(
|
|
200
|
+
self, worker_pool: WorkerPool, task_group: TaskGroup
|
|
201
|
+
) -> MatchReport:
|
|
202
|
+
"""
|
|
203
|
+
Check a worker pool against the requirements of
|
|
204
|
+
a task group.
|
|
205
|
+
"""
|
|
206
|
+
return MatchReport(
|
|
207
|
+
worker_pool_name=worker_pool.name,
|
|
208
|
+
worker_pool_id=worker_pool.id,
|
|
209
|
+
namespaces=self._match_namespaces(task_group, worker_pool),
|
|
210
|
+
worker_tags=self._match_worker_tags(task_group, worker_pool),
|
|
211
|
+
instance_types=self._match_instance_types(task_group, worker_pool),
|
|
212
|
+
task_types=self._match_task_types(task_group, worker_pool),
|
|
213
|
+
providers=self._match_providers(task_group, worker_pool),
|
|
214
|
+
regions=self._match_regions(task_group, worker_pool),
|
|
215
|
+
ram=self._match_ram(task_group, worker_pool),
|
|
216
|
+
vcpus=self._match_vcpus(task_group, worker_pool),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
def _match_worker_tags(
|
|
221
|
+
task_group: TaskGroup, worker_pool: WorkerPool
|
|
222
|
+
) -> PropertyMatch:
|
|
223
|
+
return PropertyMatch(
|
|
224
|
+
property_name="Worker Tag(s)",
|
|
225
|
+
task_group_value=(
|
|
226
|
+
NONE_STRING
|
|
227
|
+
if task_group.runSpecification.workerTags is None
|
|
228
|
+
else ", ".join(task_group.runSpecification.workerTags)
|
|
229
|
+
),
|
|
230
|
+
worker_pool_values=(
|
|
231
|
+
NONE_STRING
|
|
232
|
+
if worker_pool.properties.workerTag is None
|
|
233
|
+
else worker_pool.properties.workerTag
|
|
234
|
+
),
|
|
235
|
+
# Any single workerTag in the list can match
|
|
236
|
+
match=(
|
|
237
|
+
MatchType.YES
|
|
238
|
+
if task_group.runSpecification.workerTags is None
|
|
239
|
+
or (
|
|
240
|
+
worker_pool.properties.workerTag
|
|
241
|
+
in task_group.runSpecification.workerTags
|
|
242
|
+
)
|
|
243
|
+
else MatchType.NO
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def _match_instance_types(
|
|
248
|
+
self, task_group: TaskGroup, worker_pool: WorkerPool
|
|
249
|
+
) -> PropertyMatch:
|
|
250
|
+
runspec_instance_types = (
|
|
251
|
+
set()
|
|
252
|
+
if task_group.runSpecification.instanceTypes is None
|
|
253
|
+
else set(task_group.runSpecification.instanceTypes)
|
|
254
|
+
)
|
|
255
|
+
nodes = self._get_all_nodes_in_worker_pool(worker_pool)
|
|
256
|
+
node_instance_types = {
|
|
257
|
+
node.details.instanceType
|
|
258
|
+
for node in nodes
|
|
259
|
+
if node.details.instanceType != ""
|
|
260
|
+
}
|
|
261
|
+
worker_pool_values = (
|
|
262
|
+
UNKNOWN_STRING
|
|
263
|
+
if len(nodes) == 0
|
|
264
|
+
else (
|
|
265
|
+
", ".join(node_instance_types)
|
|
266
|
+
if len(node_instance_types) > 0
|
|
267
|
+
else NONE_STRING
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Calculate match
|
|
272
|
+
if len(runspec_instance_types) == 0:
|
|
273
|
+
match_type = MatchType.YES
|
|
274
|
+
elif len(nodes) == 0:
|
|
275
|
+
match_type = MatchType.MAYBE
|
|
276
|
+
else:
|
|
277
|
+
matching_nodes_counter = 0
|
|
278
|
+
for node in nodes:
|
|
279
|
+
if node.details.instanceType in runspec_instance_types:
|
|
280
|
+
matching_nodes_counter += 1
|
|
281
|
+
if matching_nodes_counter == 0:
|
|
282
|
+
match_type = MatchType.NO
|
|
283
|
+
elif matching_nodes_counter < len(nodes):
|
|
284
|
+
match_type = MatchType.PARTIAL
|
|
285
|
+
else:
|
|
286
|
+
match_type = MatchType.YES
|
|
287
|
+
|
|
288
|
+
return PropertyMatch(
|
|
289
|
+
property_name="Instance Type(s)",
|
|
290
|
+
task_group_value=(
|
|
291
|
+
NONE_STRING
|
|
292
|
+
if task_group.runSpecification.instanceTypes is None
|
|
293
|
+
else ", ".join(task_group.runSpecification.instanceTypes)
|
|
294
|
+
),
|
|
295
|
+
worker_pool_values=worker_pool_values,
|
|
296
|
+
match=match_type,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def _match_task_types(
|
|
300
|
+
self, task_group: TaskGroup, worker_pool: WorkerPool
|
|
301
|
+
) -> PropertyMatch:
|
|
302
|
+
runspec_task_types = (
|
|
303
|
+
set()
|
|
304
|
+
if task_group.runSpecification.taskTypes is None
|
|
305
|
+
else set(task_group.runSpecification.taskTypes)
|
|
306
|
+
)
|
|
307
|
+
nodes = self._get_all_nodes_in_worker_pool(worker_pool)
|
|
308
|
+
node_task_types = set(
|
|
309
|
+
[
|
|
310
|
+
task_type
|
|
311
|
+
for node in nodes
|
|
312
|
+
for task_type in node.details.supportedTaskTypes
|
|
313
|
+
]
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
worker_pool_values = (
|
|
317
|
+
UNKNOWN_STRING
|
|
318
|
+
if len(nodes) == 0
|
|
319
|
+
else (
|
|
320
|
+
", ".join(node_task_types) if len(node_task_types) > 0 else NONE_STRING
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Calculate match
|
|
325
|
+
if len(nodes) == 0:
|
|
326
|
+
match_type = MatchType.MAYBE
|
|
327
|
+
else:
|
|
328
|
+
matching_node_counter = 0
|
|
329
|
+
for node in nodes:
|
|
330
|
+
if runspec_task_types <= set(node.details.supportedTaskTypes):
|
|
331
|
+
matching_node_counter += 1
|
|
332
|
+
if matching_node_counter == 0:
|
|
333
|
+
match_type = MatchType.NO
|
|
334
|
+
elif matching_node_counter < len(nodes):
|
|
335
|
+
match_type = MatchType.PARTIAL
|
|
336
|
+
else:
|
|
337
|
+
match_type = MatchType.YES
|
|
338
|
+
|
|
339
|
+
return PropertyMatch(
|
|
340
|
+
property_name="Task Type(s)",
|
|
341
|
+
task_group_value=(
|
|
342
|
+
NONE_STRING
|
|
343
|
+
if task_group.runSpecification.taskTypes is None
|
|
344
|
+
else ", ".join(task_group.runSpecification.taskTypes)
|
|
345
|
+
),
|
|
346
|
+
worker_pool_values=worker_pool_values,
|
|
347
|
+
match=match_type,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
def _match_providers(
|
|
351
|
+
self, task_group: TaskGroup, worker_pool: WorkerPool
|
|
352
|
+
) -> PropertyMatch:
|
|
353
|
+
runspec_providers = (
|
|
354
|
+
set()
|
|
355
|
+
if task_group.runSpecification.providers is None
|
|
356
|
+
else set(task_group.runSpecification.providers)
|
|
357
|
+
)
|
|
358
|
+
nodes = self._get_all_nodes_in_worker_pool(worker_pool)
|
|
359
|
+
node_providers = {node.details.provider.value for node in nodes}
|
|
360
|
+
|
|
361
|
+
# Calculate match
|
|
362
|
+
if len(runspec_providers) == 0:
|
|
363
|
+
match_type = MatchType.YES
|
|
364
|
+
elif len(nodes) == 0:
|
|
365
|
+
match_type = MatchType.MAYBE
|
|
366
|
+
else:
|
|
367
|
+
matching_node_counter = 0
|
|
368
|
+
for node in nodes:
|
|
369
|
+
if node.details.provider in runspec_providers:
|
|
370
|
+
matching_node_counter += 1
|
|
371
|
+
if matching_node_counter == 0:
|
|
372
|
+
match_type = MatchType.NO
|
|
373
|
+
elif matching_node_counter < len(nodes):
|
|
374
|
+
match_type = MatchType.PARTIAL
|
|
375
|
+
else:
|
|
376
|
+
match_type = MatchType.YES
|
|
377
|
+
|
|
378
|
+
return PropertyMatch(
|
|
379
|
+
property_name="Provider(s)",
|
|
380
|
+
task_group_value=(
|
|
381
|
+
NONE_STRING
|
|
382
|
+
if task_group.runSpecification.providers is None
|
|
383
|
+
else ", ".join([x.value for x in task_group.runSpecification.providers])
|
|
384
|
+
),
|
|
385
|
+
worker_pool_values=(
|
|
386
|
+
UNKNOWN_STRING
|
|
387
|
+
if len(nodes) == 0
|
|
388
|
+
else (
|
|
389
|
+
NONE_STRING
|
|
390
|
+
if len(node_providers) == 0
|
|
391
|
+
else ", ".join(node_providers)
|
|
392
|
+
)
|
|
393
|
+
),
|
|
394
|
+
match=match_type,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
def _match_regions(
|
|
398
|
+
self, task_group: TaskGroup, worker_pool: WorkerPool
|
|
399
|
+
) -> PropertyMatch:
|
|
400
|
+
runspec_regions = (
|
|
401
|
+
set()
|
|
402
|
+
if task_group.runSpecification.regions is None
|
|
403
|
+
else set(task_group.runSpecification.regions)
|
|
404
|
+
)
|
|
405
|
+
nodes = self._get_all_nodes_in_worker_pool(worker_pool)
|
|
406
|
+
node_regions = {
|
|
407
|
+
node.details.region for node in nodes if node.details.region != ""
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
# Calculate match
|
|
411
|
+
if len(runspec_regions) == 0:
|
|
412
|
+
match_type = MatchType.YES
|
|
413
|
+
elif len(nodes) == 0:
|
|
414
|
+
match_type = MatchType.MAYBE
|
|
415
|
+
else:
|
|
416
|
+
matching_node_counter = 0
|
|
417
|
+
for node in nodes:
|
|
418
|
+
if node.details.region in runspec_regions:
|
|
419
|
+
matching_node_counter += 1
|
|
420
|
+
if matching_node_counter == 0:
|
|
421
|
+
match_type = MatchType.NO
|
|
422
|
+
elif matching_node_counter < len(nodes):
|
|
423
|
+
match_type = MatchType.PARTIAL
|
|
424
|
+
else:
|
|
425
|
+
match_type = MatchType.YES
|
|
426
|
+
|
|
427
|
+
return PropertyMatch(
|
|
428
|
+
property_name="Region(s)",
|
|
429
|
+
task_group_value=(
|
|
430
|
+
NONE_STRING
|
|
431
|
+
if task_group.runSpecification.regions is None
|
|
432
|
+
else ", ".join(task_group.runSpecification.regions)
|
|
433
|
+
),
|
|
434
|
+
worker_pool_values=(
|
|
435
|
+
UNKNOWN_STRING
|
|
436
|
+
if len(nodes) == 0
|
|
437
|
+
else ", ".join(node_regions) if len(node_regions) > 0 else NONE_STRING
|
|
438
|
+
),
|
|
439
|
+
match=match_type,
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
@staticmethod
|
|
443
|
+
def _match_namespaces(
|
|
444
|
+
task_group: TaskGroup, worker_pool: WorkerPool
|
|
445
|
+
) -> PropertyMatch:
|
|
446
|
+
return PropertyMatch(
|
|
447
|
+
property_name="Namespace(s)",
|
|
448
|
+
task_group_value=(
|
|
449
|
+
NONE_STRING
|
|
450
|
+
if task_group.runSpecification.namespaces is None
|
|
451
|
+
else ", ".join(task_group.runSpecification.namespaces)
|
|
452
|
+
),
|
|
453
|
+
worker_pool_values=(
|
|
454
|
+
NONE_STRING if worker_pool.namespace is None else worker_pool.namespace
|
|
455
|
+
),
|
|
456
|
+
match=(
|
|
457
|
+
MatchType.YES
|
|
458
|
+
if task_group.runSpecification.namespaces is None
|
|
459
|
+
or worker_pool.namespace in task_group.runSpecification.namespaces
|
|
460
|
+
else MatchType.NO
|
|
461
|
+
),
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
def _match_ram(
|
|
465
|
+
self, task_group: TaskGroup, worker_pool: WorkerPool
|
|
466
|
+
) -> PropertyMatch:
|
|
467
|
+
|
|
468
|
+
nodes = self._get_all_nodes_in_worker_pool(worker_pool)
|
|
469
|
+
nodes_ram = {node.details.ram for node in nodes}
|
|
470
|
+
|
|
471
|
+
# Calculate match
|
|
472
|
+
if task_group.runSpecification.ram is None:
|
|
473
|
+
match_type = MatchType.YES
|
|
474
|
+
elif len(nodes) == 0:
|
|
475
|
+
match_type = MatchType.MAYBE
|
|
476
|
+
else:
|
|
477
|
+
matching_node_counter = 0
|
|
478
|
+
for node in nodes:
|
|
479
|
+
if self._check_in_range(
|
|
480
|
+
node.details.ram, task_group.runSpecification.ram
|
|
481
|
+
):
|
|
482
|
+
matching_node_counter += 1
|
|
483
|
+
if matching_node_counter == 0:
|
|
484
|
+
match_type = MatchType.NO
|
|
485
|
+
elif matching_node_counter < len(nodes):
|
|
486
|
+
match_type = MatchType.PARTIAL
|
|
487
|
+
else:
|
|
488
|
+
match_type = MatchType.YES
|
|
489
|
+
|
|
490
|
+
return PropertyMatch(
|
|
491
|
+
property_name="RAM (GB)",
|
|
492
|
+
task_group_value=(
|
|
493
|
+
NONE_STRING
|
|
494
|
+
if task_group.runSpecification.ram is None
|
|
495
|
+
else self._doublerange_str(task_group.runSpecification.ram)
|
|
496
|
+
),
|
|
497
|
+
worker_pool_values=(
|
|
498
|
+
UNKNOWN_STRING
|
|
499
|
+
if len(nodes) == 0
|
|
500
|
+
else ", ".join([str(node_ram) for node_ram in nodes_ram])
|
|
501
|
+
),
|
|
502
|
+
match=match_type,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
def _match_vcpus(
|
|
506
|
+
self, task_group: TaskGroup, worker_pool: WorkerPool
|
|
507
|
+
) -> PropertyMatch:
|
|
508
|
+
|
|
509
|
+
nodes = self._get_all_nodes_in_worker_pool(worker_pool)
|
|
510
|
+
nodes_vcpus = {node.details.vcpus for node in nodes}
|
|
511
|
+
|
|
512
|
+
# Calculate match
|
|
513
|
+
if task_group.runSpecification.vcpus is None:
|
|
514
|
+
match_type = MatchType.YES
|
|
515
|
+
elif len(nodes) == 0:
|
|
516
|
+
match_type = MatchType.MAYBE
|
|
517
|
+
else:
|
|
518
|
+
matching_node_counter = 0
|
|
519
|
+
for node in nodes:
|
|
520
|
+
if self._check_in_range(
|
|
521
|
+
node.details.vcpus, task_group.runSpecification.vcpus
|
|
522
|
+
):
|
|
523
|
+
matching_node_counter += 1
|
|
524
|
+
if matching_node_counter == 0:
|
|
525
|
+
match_type = MatchType.NO
|
|
526
|
+
elif matching_node_counter < len(nodes):
|
|
527
|
+
match_type = MatchType.PARTIAL
|
|
528
|
+
else:
|
|
529
|
+
match_type = MatchType.YES
|
|
530
|
+
|
|
531
|
+
return PropertyMatch(
|
|
532
|
+
property_name="vCPUs Count",
|
|
533
|
+
task_group_value=(
|
|
534
|
+
NONE_STRING
|
|
535
|
+
if task_group.runSpecification.vcpus is None
|
|
536
|
+
else self._doublerange_str(task_group.runSpecification.vcpus)
|
|
537
|
+
),
|
|
538
|
+
worker_pool_values=(
|
|
539
|
+
UNKNOWN_STRING
|
|
540
|
+
if len(nodes) == 0
|
|
541
|
+
else ", ".join([str(node_vcpus) for node_vcpus in nodes_vcpus])
|
|
542
|
+
),
|
|
543
|
+
match=match_type,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
@staticmethod
|
|
547
|
+
def _check_in_range(value: float, range: DoubleRange) -> bool:
|
|
548
|
+
"""
|
|
549
|
+
Check whether a value is within a DoubleRange.
|
|
550
|
+
"""
|
|
551
|
+
return True if range.min <= value <= range.max else False
|
|
552
|
+
|
|
553
|
+
def _get_all_nodes_in_worker_pool(self, worker_pool: WorkerPool) -> List[Node]:
|
|
554
|
+
"""
|
|
555
|
+
Return all nodes in the worker pool.
|
|
556
|
+
"""
|
|
557
|
+
return self._get_all_nodes_in_worker_pool_cached(worker_pool.id)
|
|
558
|
+
|
|
559
|
+
@staticmethod
|
|
560
|
+
def _doublerange_str(dr: DoubleRange) -> str:
|
|
561
|
+
"""
|
|
562
|
+
Convert a DoubleRange into a tidy string.
|
|
563
|
+
"""
|
|
564
|
+
if dr.min == dr.max:
|
|
565
|
+
return str(dr.min)
|
|
566
|
+
else:
|
|
567
|
+
return f"{dr.min} to {dr.max}"
|
|
568
|
+
|
|
569
|
+
@staticmethod
|
|
570
|
+
@cache
|
|
571
|
+
def _get_all_nodes_in_worker_pool_cached(worker_pool_id: str) -> List[Node]:
|
|
572
|
+
"""
|
|
573
|
+
Cached version of the above with hashable argument.
|
|
574
|
+
"""
|
|
575
|
+
try:
|
|
576
|
+
return CLIENT.worker_pool_client.get_nodes(
|
|
577
|
+
search=NodeSearch(worker_pool_id)
|
|
578
|
+
).list_all()
|
|
579
|
+
except Exception as e:
|
|
580
|
+
raise Exception(f"Unable to get details of nodes: {e}")
|
|
581
|
+
|
|
582
|
+
@staticmethod
|
|
583
|
+
def _get_selected_worker_pools() -> List[WorkerPool]:
|
|
584
|
+
"""
|
|
585
|
+
Get the list of worker pools.
|
|
586
|
+
"""
|
|
587
|
+
print_log("Select Worker Pools to be analysed", override_quiet=True)
|
|
588
|
+
try:
|
|
589
|
+
return [
|
|
590
|
+
CLIENT.worker_pool_client.get_worker_pool_by_id(wps.id)
|
|
591
|
+
for wps in select(
|
|
592
|
+
client=CLIENT,
|
|
593
|
+
objects=CLIENT.worker_pool_client.find_all_worker_pools(),
|
|
594
|
+
force_interactive=True,
|
|
595
|
+
override_quiet=True,
|
|
596
|
+
)
|
|
597
|
+
]
|
|
598
|
+
except Exception as e:
|
|
599
|
+
raise Exception(f"Failed to obtain list of Worker Pools: {e}")
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
WORKER_POOLS = WorkerPools()
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
@main_wrapper
|
|
606
|
+
def main():
|
|
607
|
+
|
|
608
|
+
if get_ydid_type(ARGS_PARSER.ydid) == YDIDType.TASK_GROUP:
|
|
609
|
+
_analyse_task_group(_get_task_group_by_id(ARGS_PARSER.ydid))
|
|
610
|
+
|
|
611
|
+
elif get_ydid_type(ARGS_PARSER.ydid) == YDIDType.WORK_REQUIREMENT:
|
|
612
|
+
work_requirement = _get_work_requirement_by_id(ARGS_PARSER.ydid)
|
|
613
|
+
if len(work_requirement.taskGroups) == 1:
|
|
614
|
+
_analyse_task_group(work_requirement.taskGroups[0])
|
|
615
|
+
else:
|
|
616
|
+
print_log("Please select the Task Group to analyse", override_quiet=True)
|
|
617
|
+
task_group = select(
|
|
618
|
+
CLIENT,
|
|
619
|
+
work_requirement.taskGroups,
|
|
620
|
+
override_quiet=True,
|
|
621
|
+
force_interactive=True,
|
|
622
|
+
single_result=True,
|
|
623
|
+
result_required=True,
|
|
624
|
+
)
|
|
625
|
+
print_log(
|
|
626
|
+
f"Selected Task Group '{task_group[0].name}' ({task_group[0].id})",
|
|
627
|
+
override_quiet=True,
|
|
628
|
+
)
|
|
629
|
+
_analyse_task_group(task_group[0])
|
|
630
|
+
|
|
631
|
+
else:
|
|
632
|
+
print_error(
|
|
633
|
+
f"Not a YellowDog Work Requirement or Task Group ID: '{ARGS_PARSER.ydid}'"
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _get_task_group_by_id(task_group_id) -> TaskGroup:
|
|
638
|
+
work_requirement_id = task_group_id[:-2].replace("taskgrp", "workreq")
|
|
639
|
+
try:
|
|
640
|
+
work_requirement: WorkRequirement = (
|
|
641
|
+
CLIENT.work_client.get_work_requirement_by_id(work_requirement_id)
|
|
642
|
+
)
|
|
643
|
+
return work_requirement.taskGroups[int(task_group_id[-1:]) - 1]
|
|
644
|
+
except Exception as e:
|
|
645
|
+
if "404" in str(e):
|
|
646
|
+
raise Exception(f"Task Group ID '{task_group_id}' not found")
|
|
647
|
+
else:
|
|
648
|
+
raise Exception(
|
|
649
|
+
f"Unable to obtain Task Group details for '{task_group_id}': {e}"
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _get_work_requirement_by_id(work_requirement_id) -> WorkRequirement:
|
|
654
|
+
try:
|
|
655
|
+
return CLIENT.work_client.get_work_requirement_by_id(work_requirement_id)
|
|
656
|
+
except Exception as e:
|
|
657
|
+
if "404" in str(e):
|
|
658
|
+
raise Exception(f"Work Requirement ID '{work_requirement_id}' not found")
|
|
659
|
+
else:
|
|
660
|
+
raise Exception(
|
|
661
|
+
f"Unable to obtain Work Requirement details for '{work_requirement_id}': {e}"
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def _analyse_task_group(task_group: TaskGroup):
|
|
666
|
+
"""
|
|
667
|
+
Analyse a Task Group.
|
|
668
|
+
"""
|
|
669
|
+
print_log(
|
|
670
|
+
f"Analysing Task Group: '{task_group.name}' ({task_group.id})",
|
|
671
|
+
override_quiet=True,
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
if not WORKER_POOLS.populate():
|
|
675
|
+
print_log("No Worker Pools selected")
|
|
676
|
+
return
|
|
677
|
+
|
|
678
|
+
match_reports: List[MatchReport] = (
|
|
679
|
+
WORKER_POOLS.check_task_group_for_matching_worker_pools(task_group=task_group)
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
# Summary table
|
|
683
|
+
print_log("Summary of Worker Pool matches:", override_quiet=True)
|
|
684
|
+
header_row = ["", "Worker Pool Name", "Worker Pool ID", "Worker Pool Match?"]
|
|
685
|
+
table_rows = []
|
|
686
|
+
for index, match_report in enumerate(match_reports):
|
|
687
|
+
table_rows.append(
|
|
688
|
+
[
|
|
689
|
+
index + 1,
|
|
690
|
+
match_report.worker_pool_name,
|
|
691
|
+
match_report.worker_pool_id,
|
|
692
|
+
match_report.summary().value,
|
|
693
|
+
]
|
|
694
|
+
)
|
|
695
|
+
print_table_core(
|
|
696
|
+
indent(
|
|
697
|
+
tabulate(table_rows, headers=header_row, tablefmt="simple_outline"),
|
|
698
|
+
indent_width=4,
|
|
699
|
+
),
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
# Detailed reporting
|
|
703
|
+
print_log(
|
|
704
|
+
"Please select Worker Pools for which to show a detailed report",
|
|
705
|
+
override_quiet=True,
|
|
706
|
+
)
|
|
707
|
+
for selected_item in get_selected_list_items(len(match_reports)):
|
|
708
|
+
match_reports[selected_item - 1].print_detailed_report()
|
|
709
|
+
|
|
710
|
+
print_log("Task Group analysis complete")
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
# Entry point
|
|
714
|
+
if __name__ == "__main__":
|
|
715
|
+
main()
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/args.py
RENAMED
|
@@ -38,6 +38,9 @@ class CLIParser:
|
|
|
38
38
|
Create the argument parser, and parse the command
|
|
39
39
|
line arguments. Argument availability depends on module.
|
|
40
40
|
"""
|
|
41
|
+
self.tag_required = False
|
|
42
|
+
self.namespace_required = False
|
|
43
|
+
|
|
41
44
|
parser = argparse.ArgumentParser(description=description)
|
|
42
45
|
|
|
43
46
|
# Common arguments across all commands
|
|
@@ -83,25 +86,6 @@ class CLIParser:
|
|
|
83
86
|
help="the URL of the YellowDog Platform API",
|
|
84
87
|
metavar="<url>",
|
|
85
88
|
)
|
|
86
|
-
parser.add_argument(
|
|
87
|
-
"--variable",
|
|
88
|
-
"-v",
|
|
89
|
-
type=str,
|
|
90
|
-
required=False,
|
|
91
|
-
action="append",
|
|
92
|
-
help=(
|
|
93
|
-
"user-defined variable substitution; the option can be supplied"
|
|
94
|
-
" multiple times, one per variable"
|
|
95
|
-
),
|
|
96
|
-
metavar="<var1=v1>",
|
|
97
|
-
)
|
|
98
|
-
parser.add_argument(
|
|
99
|
-
"--quiet",
|
|
100
|
-
"-q",
|
|
101
|
-
action="store_true",
|
|
102
|
-
required=False,
|
|
103
|
-
help="suppress (non-error, non-interactive) status and progress messages",
|
|
104
|
-
)
|
|
105
89
|
parser.add_argument(
|
|
106
90
|
"--debug",
|
|
107
91
|
action="store_true",
|
|
@@ -122,7 +106,28 @@ class CLIParser:
|
|
|
122
106
|
help="disable colouring and text wrapping in command output",
|
|
123
107
|
)
|
|
124
108
|
|
|
109
|
+
parser.add_argument(
|
|
110
|
+
"--quiet",
|
|
111
|
+
"-q",
|
|
112
|
+
action="store_true",
|
|
113
|
+
required=False,
|
|
114
|
+
help="suppress (non-error, non-interactive) status and progress messages",
|
|
115
|
+
)
|
|
116
|
+
|
|
125
117
|
# Module-specific argument sets
|
|
118
|
+
if not any(module in sys.argv[0] for module in ["analyse"]):
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
"--variable",
|
|
121
|
+
"-v",
|
|
122
|
+
type=str,
|
|
123
|
+
required=False,
|
|
124
|
+
action="append",
|
|
125
|
+
help=(
|
|
126
|
+
"user-defined variable substitution; the option can be supplied"
|
|
127
|
+
" multiple times, one per variable"
|
|
128
|
+
),
|
|
129
|
+
metavar="<var1=v1>",
|
|
130
|
+
)
|
|
126
131
|
|
|
127
132
|
if not any(
|
|
128
133
|
module in sys.argv[0]
|
|
@@ -132,8 +137,10 @@ class CLIParser:
|
|
|
132
137
|
"cloudwizard",
|
|
133
138
|
"follow",
|
|
134
139
|
"list",
|
|
140
|
+
"analyse",
|
|
135
141
|
]
|
|
136
142
|
):
|
|
143
|
+
self.namespace_required = True
|
|
137
144
|
parser.add_argument(
|
|
138
145
|
"--namespace",
|
|
139
146
|
"-n",
|
|
@@ -147,6 +154,7 @@ class CLIParser:
|
|
|
147
154
|
),
|
|
148
155
|
metavar="<namespace>",
|
|
149
156
|
)
|
|
157
|
+
self.tag_required = True
|
|
150
158
|
parser.add_argument(
|
|
151
159
|
"--tag",
|
|
152
160
|
"-t",
|
|
@@ -1105,6 +1113,16 @@ class CLIParser:
|
|
|
1105
1113
|
),
|
|
1106
1114
|
)
|
|
1107
1115
|
|
|
1116
|
+
if "analyse" in sys.argv[0]:
|
|
1117
|
+
parser.add_argument(
|
|
1118
|
+
"ydid",
|
|
1119
|
+
metavar="<ydid-of-work-requirement-or-task-group>",
|
|
1120
|
+
type=str,
|
|
1121
|
+
help=(
|
|
1122
|
+
"the YellowDog ID of the work requirement or task group to be analysed"
|
|
1123
|
+
),
|
|
1124
|
+
)
|
|
1125
|
+
|
|
1108
1126
|
self.args = parser.parse_args()
|
|
1109
1127
|
|
|
1110
1128
|
if self.args.docs:
|
|
@@ -1585,6 +1603,11 @@ class CLIParser:
|
|
|
1585
1603
|
def substitute_ids(self) -> Optional[bool]:
|
|
1586
1604
|
return self.args.substitute_ids
|
|
1587
1605
|
|
|
1606
|
+
@property
|
|
1607
|
+
@allow_missing_attribute
|
|
1608
|
+
def ydid(self) -> Optional[str]:
|
|
1609
|
+
return self.args.ydid
|
|
1610
|
+
|
|
1588
1611
|
|
|
1589
1612
|
def lookup_module_description(module_name: str) -> Optional[str]:
|
|
1590
1613
|
"""
|
|
@@ -1633,6 +1656,10 @@ def lookup_module_description(module_name: str) -> Optional[str]:
|
|
|
1633
1656
|
suffix = "boosting Allowances"
|
|
1634
1657
|
elif "show" in module_name:
|
|
1635
1658
|
suffix = "showing the JSON details of entities referenced by their YDIDs"
|
|
1659
|
+
elif "analyse" in module_name:
|
|
1660
|
+
suffix = (
|
|
1661
|
+
"analysing if a task group is matched by workers in selected worker pools"
|
|
1662
|
+
)
|
|
1636
1663
|
|
|
1637
1664
|
return None if suffix is None else prefix + suffix
|
|
1638
1665
|
|
|
@@ -63,8 +63,23 @@ def select(
|
|
|
63
63
|
if not ARGS_PARSER.interactive and force_interactive is False:
|
|
64
64
|
return objects
|
|
65
65
|
|
|
66
|
+
return [
|
|
67
|
+
objects[x - 1]
|
|
68
|
+
for x in get_selected_list_items(
|
|
69
|
+
len(objects), result_required=result_required, single_result=single_result
|
|
70
|
+
)
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_selected_list_items(
|
|
75
|
+
num_items, result_required: bool = False, single_result: bool = False
|
|
76
|
+
) -> List[int]:
|
|
77
|
+
"""
|
|
78
|
+
Get a numbered selection list.
|
|
79
|
+
"""
|
|
80
|
+
|
|
66
81
|
def in_range(num: int) -> bool:
|
|
67
|
-
if 1 <= num <=
|
|
82
|
+
if 1 <= num <= num_items:
|
|
68
83
|
return True
|
|
69
84
|
else:
|
|
70
85
|
print_error(f"'{num}' is out of range")
|
|
@@ -79,7 +94,7 @@ def select(
|
|
|
79
94
|
)
|
|
80
95
|
selector_string = _get_user_input(print_string(input_string) + " ")
|
|
81
96
|
if selector_string.strip() == "*":
|
|
82
|
-
selector_string = f"1-{
|
|
97
|
+
selector_string = f"1-{num_items}"
|
|
83
98
|
selector_list = selector_string.split(",")
|
|
84
99
|
selector_set: Set[int] = set()
|
|
85
100
|
error_flag = False
|
|
@@ -133,7 +148,7 @@ def select(
|
|
|
133
148
|
else:
|
|
134
149
|
print_log("No items selected")
|
|
135
150
|
|
|
136
|
-
return
|
|
151
|
+
return selected_list
|
|
137
152
|
|
|
138
153
|
|
|
139
154
|
def confirmed(msg: str) -> bool:
|
|
@@ -121,15 +121,18 @@ def load_config_common() -> ConfigCommon:
|
|
|
121
121
|
# Provide default values for namespace and tag
|
|
122
122
|
if common_section.get(NAMESPACE, None) is None:
|
|
123
123
|
common_section[NAMESPACE] = "default"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
if ARGS_PARSER.namespace_required:
|
|
125
|
+
print_log(
|
|
126
|
+
"Using default value for 'namespace': "
|
|
127
|
+
f"'{common_section[NAMESPACE]}'"
|
|
128
|
+
)
|
|
127
129
|
if common_section.get(NAME_TAG, None) is None:
|
|
128
130
|
common_section[NAME_TAG] = "{{username}}"
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
if ARGS_PARSER.tag_required:
|
|
132
|
+
print_log(
|
|
133
|
+
"Using default value for 'tag/prefix/name' = "
|
|
134
|
+
f"'{VARIABLE_SUBSTITUTIONS['username']}'"
|
|
135
|
+
)
|
|
133
136
|
|
|
134
137
|
url = process_variable_substitutions(common_section.get(URL, DEFAULT_URL))
|
|
135
138
|
if url != DEFAULT_URL:
|
|
@@ -103,6 +103,8 @@ class PrintTableHighlighter(RegexHighlighter):
|
|
|
103
103
|
rf"(?P<table_outline>[{table_outline_chars}]*)",
|
|
104
104
|
rf"(?P<table_content>[^{table_outline_chars}]*)",
|
|
105
105
|
r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9abcdef-]*)",
|
|
106
|
+
r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9abcdef-]*:[0-9]*)",
|
|
107
|
+
r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9abcdef-]*:[0-9]*:[0-9]*)",
|
|
106
108
|
] + HIGHLIGHTED_STATES
|
|
107
109
|
|
|
108
110
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: yellowdog-python-examples
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.14.0
|
|
4
4
|
Summary: Python CLI commands using the YellowDog Python SDK
|
|
5
5
|
Author-email: YellowDog Limited <support@yellowdog.co>
|
|
6
6
|
Project-URL: Homepage, https://github.com/yellowdog/python-examples
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "7.13.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/tests/test_create_remove.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/tests/test_entrypoints.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/abort.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/admin.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/boost.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/cancel.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/cloudwizard.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/create.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/delete.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/download.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/follow.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/format_json.py
RENAMED
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/instantiate.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/jsonnet2json.py
RENAMED
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/remove.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/resize.py
RENAMED
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/shutdown.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/start.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/submit.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/terminate.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/upload.py
RENAMED
|
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
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/items.py
RENAMED
|
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
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/utils/wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.13.0 → yellowdog_python_examples-7.14.0}/yellowdog_cli/version.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|