pdmv-http-client 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pdmv_http_client-2.1.0.dist-info/METADATA +68 -0
- pdmv_http_client-2.1.0.dist-info/RECORD +26 -0
- pdmv_http_client-2.1.0.dist-info/WHEEL +5 -0
- pdmv_http_client-2.1.0.dist-info/licenses/LICENSE +21 -0
- pdmv_http_client-2.1.0.dist-info/top_level.txt +1 -0
- rest/__init__.py +10 -0
- rest/_version.py +34 -0
- rest/applications/__init__.py +0 -0
- rest/applications/base.py +238 -0
- rest/applications/mcm/__init__.py +0 -0
- rest/applications/mcm/core.py +277 -0
- rest/applications/mcm/invalidate_request.py +399 -0
- rest/applications/mcm/resubmission.py +431 -0
- rest/applications/rereco/core.py +78 -0
- rest/applications/stats/core.py +95 -0
- rest/client/__init__.py +0 -0
- rest/client/auth/__init__.py +0 -0
- rest/client/auth/auth_interface.py +102 -0
- rest/client/auth/handlers/__init__.py +0 -0
- rest/client/auth/handlers/oauth2_tokens.py +307 -0
- rest/client/auth/handlers/session_cookies.py +110 -0
- rest/client/session.py +99 -0
- rest/utils/__init__.py +0 -0
- rest/utils/logger.py +36 -0
- rest/utils/miscellaneous.py +47 -0
- rest/utils/shell.py +57 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""
|
|
2
|
+
REST client for the McM application.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import warnings
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
from rest.applications.base import BaseClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class McM(BaseClient):
|
|
12
|
+
"""
|
|
13
|
+
Initializes an HTTP client for querying McM.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
id: str = BaseClient.SSO,
|
|
19
|
+
debug: bool = False,
|
|
20
|
+
cookie: Union[str, None] = None,
|
|
21
|
+
dev: bool = True,
|
|
22
|
+
client_id: str = "",
|
|
23
|
+
client_secret: str = "",
|
|
24
|
+
):
|
|
25
|
+
# Set the HTTP session
|
|
26
|
+
super().__init__(
|
|
27
|
+
app="mcm",
|
|
28
|
+
id=id,
|
|
29
|
+
debug=debug,
|
|
30
|
+
cookie=cookie,
|
|
31
|
+
dev=dev,
|
|
32
|
+
client_id=client_id,
|
|
33
|
+
client_secret=client_secret,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def __get(self, url):
|
|
37
|
+
warnings.warn(
|
|
38
|
+
"This name mangled method will be removed in the future, use self._get(...) instead",
|
|
39
|
+
DeprecationWarning,
|
|
40
|
+
stacklevel=2,
|
|
41
|
+
)
|
|
42
|
+
return self._get(url=url)
|
|
43
|
+
|
|
44
|
+
def __put(self, url, data):
|
|
45
|
+
warnings.warn(
|
|
46
|
+
"This name mangled method will be removed in the future, use self._put(...) instead",
|
|
47
|
+
DeprecationWarning,
|
|
48
|
+
stacklevel=2,
|
|
49
|
+
)
|
|
50
|
+
return self._put(url=url, data=data)
|
|
51
|
+
|
|
52
|
+
def __post(self, url, data):
|
|
53
|
+
warnings.warn(
|
|
54
|
+
"This name mangled method will be removed in the future, use self._post(...) instead",
|
|
55
|
+
DeprecationWarning,
|
|
56
|
+
stacklevel=2,
|
|
57
|
+
)
|
|
58
|
+
return self._post(url=url, data=data)
|
|
59
|
+
|
|
60
|
+
def __delete(self, url):
|
|
61
|
+
warnings.warn(
|
|
62
|
+
"This name mangled method will be removed in the future, use self._delete(...) instead",
|
|
63
|
+
DeprecationWarning,
|
|
64
|
+
stacklevel=2,
|
|
65
|
+
)
|
|
66
|
+
return self._delete(url=url)
|
|
67
|
+
|
|
68
|
+
# McM methods
|
|
69
|
+
def get(self, object_type, object_id=None, query="", method="get", page=-1):
|
|
70
|
+
"""
|
|
71
|
+
Get data from McM
|
|
72
|
+
object_type - [chained_campaigns, chained_requests, campaigns, requests, flows, etc.]
|
|
73
|
+
object_id - usually prep id of desired object
|
|
74
|
+
query - query to be run in order to receive an object, e.g. tags=M17p1A, multiple parameters can be used with & tags=M17p1A&pwg=HIG
|
|
75
|
+
method - action to be performed, such as get, migrate or inspect
|
|
76
|
+
page - which page to be fetched. -1 means no pagination, return all results
|
|
77
|
+
"""
|
|
78
|
+
object_type = object_type.strip()
|
|
79
|
+
if object_id:
|
|
80
|
+
object_id = object_id.strip()
|
|
81
|
+
self.logger.debug(
|
|
82
|
+
"Object ID %s provided, method is %s, database %s",
|
|
83
|
+
object_id,
|
|
84
|
+
method,
|
|
85
|
+
object_type,
|
|
86
|
+
)
|
|
87
|
+
url = "restapi/%s/%s/%s" % (object_type, method, object_id)
|
|
88
|
+
result = self._get(url)
|
|
89
|
+
if type(result) == list:
|
|
90
|
+
return result
|
|
91
|
+
elif type(result) == str:
|
|
92
|
+
return result
|
|
93
|
+
result = result.get("results")
|
|
94
|
+
if not result:
|
|
95
|
+
return None
|
|
96
|
+
return result
|
|
97
|
+
elif query:
|
|
98
|
+
if page != -1:
|
|
99
|
+
self.logger.debug(
|
|
100
|
+
"Fetching page %s of %s for query %s", page, object_type, query
|
|
101
|
+
)
|
|
102
|
+
url = "search/?db_name=%s&limit=50&page=%d&%s" % (
|
|
103
|
+
object_type,
|
|
104
|
+
page,
|
|
105
|
+
query,
|
|
106
|
+
)
|
|
107
|
+
results = self._get(url).get("results", [])
|
|
108
|
+
self.logger.debug(
|
|
109
|
+
"Found %s %s in page %s for query %s",
|
|
110
|
+
len(results),
|
|
111
|
+
object_type,
|
|
112
|
+
page,
|
|
113
|
+
query,
|
|
114
|
+
)
|
|
115
|
+
return results
|
|
116
|
+
else:
|
|
117
|
+
self.logger.debug(
|
|
118
|
+
"Page not given, will use pagination to build response"
|
|
119
|
+
)
|
|
120
|
+
page_results = [{}]
|
|
121
|
+
results = []
|
|
122
|
+
page = 0
|
|
123
|
+
while page_results:
|
|
124
|
+
page_results = self.get(
|
|
125
|
+
object_type=object_type, query=query, method=method, page=page
|
|
126
|
+
)
|
|
127
|
+
results += page_results
|
|
128
|
+
page += 1
|
|
129
|
+
|
|
130
|
+
return results
|
|
131
|
+
else:
|
|
132
|
+
self.logger.error("Neither object ID, nor query is given, doing nothing...")
|
|
133
|
+
|
|
134
|
+
def update(self, object_type, object_data):
|
|
135
|
+
"""
|
|
136
|
+
Update data in McM
|
|
137
|
+
object_type - [chained_campaigns, chained_requests, campaigns, requests, flows, etc.]
|
|
138
|
+
object_data - new JSON of an object to be updated
|
|
139
|
+
"""
|
|
140
|
+
return self.put(object_type, object_data, method="update")
|
|
141
|
+
|
|
142
|
+
def put(self, object_type, object_data, method="save"):
|
|
143
|
+
"""
|
|
144
|
+
Put data into McM
|
|
145
|
+
object_type - [chained_campaigns, chained_requests, campaigns, requests, flows, etc.]
|
|
146
|
+
object_data - new JSON of an object to be updated
|
|
147
|
+
method - action to be performed, default is 'save'
|
|
148
|
+
"""
|
|
149
|
+
url = "restapi/%s/%s" % (object_type, method)
|
|
150
|
+
res = self._put(url, object_data)
|
|
151
|
+
return res
|
|
152
|
+
|
|
153
|
+
def post(self, object_type, object_data, method):
|
|
154
|
+
"""
|
|
155
|
+
Post data into McM
|
|
156
|
+
object_type - [chained_campaigns, chained_requests, campaigns, requests, flows, etc.]
|
|
157
|
+
object_data - JSON to be posted
|
|
158
|
+
method - restapi to be called
|
|
159
|
+
"""
|
|
160
|
+
url = "restapi/%s/%s" % (object_type, method)
|
|
161
|
+
res = self._post(url, object_data)
|
|
162
|
+
return res
|
|
163
|
+
|
|
164
|
+
def approve(self, object_type, object_id, level=None):
|
|
165
|
+
if level is None:
|
|
166
|
+
url = "restapi/%s/approve/%s" % (object_type, object_id)
|
|
167
|
+
else:
|
|
168
|
+
url = "restapi/%s/approve/%s/%d" % (object_type, object_id, level)
|
|
169
|
+
|
|
170
|
+
return self._get(url)
|
|
171
|
+
|
|
172
|
+
def clone_request(self, object_data):
|
|
173
|
+
return self.put("requests", object_data, method="clone")
|
|
174
|
+
|
|
175
|
+
def get_range_of_requests(self, query):
|
|
176
|
+
res = self._put("restapi/requests/listwithfile", data={"contents": query})
|
|
177
|
+
return res.get("results", None)
|
|
178
|
+
|
|
179
|
+
def delete(self, object_type, object_id):
|
|
180
|
+
url = "restapi/%s/delete/%s" % (object_type, object_id)
|
|
181
|
+
self._delete(url)
|
|
182
|
+
|
|
183
|
+
def forceflow(self, prepid):
|
|
184
|
+
"""
|
|
185
|
+
Force a flow on a chained request with given `prepid`
|
|
186
|
+
"""
|
|
187
|
+
res = self._get("restapi/chained_requests/flow/%s/force" % (prepid))
|
|
188
|
+
return res.get("results", None)
|
|
189
|
+
|
|
190
|
+
def reset(self, prepid):
|
|
191
|
+
"""
|
|
192
|
+
Reset a request
|
|
193
|
+
"""
|
|
194
|
+
res = self._get("restapi/requests/reset/%s" % (prepid))
|
|
195
|
+
return res.get("results", None)
|
|
196
|
+
|
|
197
|
+
def soft_reset(self, prepid):
|
|
198
|
+
"""
|
|
199
|
+
Soft reset a request
|
|
200
|
+
"""
|
|
201
|
+
res = self._get("restapi/requests/soft_reset/%s" % (prepid))
|
|
202
|
+
return res.get("results", None)
|
|
203
|
+
|
|
204
|
+
def option_reset(self, prepid):
|
|
205
|
+
"""
|
|
206
|
+
Option reset a request
|
|
207
|
+
"""
|
|
208
|
+
res = self._get("restapi/requests/option_reset/%s" % (prepid))
|
|
209
|
+
return res.get("results", None)
|
|
210
|
+
|
|
211
|
+
def ticket_generate(self, ticket_prepid):
|
|
212
|
+
"""
|
|
213
|
+
Generate chains for a ticket
|
|
214
|
+
"""
|
|
215
|
+
res = self._get("restapi/mccms/generate/%s" % (ticket_prepid))
|
|
216
|
+
return res.get("results", None)
|
|
217
|
+
|
|
218
|
+
def ticket_generate_reserve(self, ticket_prepid):
|
|
219
|
+
"""
|
|
220
|
+
Generate and reserve chains for a ticket
|
|
221
|
+
"""
|
|
222
|
+
res = self._get("restapi/mccms/generate/%s/reserve" % (ticket_prepid))
|
|
223
|
+
return res.get("results", None)
|
|
224
|
+
|
|
225
|
+
def rewind(self, chained_request_prepid):
|
|
226
|
+
"""
|
|
227
|
+
Rewind a chained request
|
|
228
|
+
"""
|
|
229
|
+
res = self._get("restapi/chained_requests/rewind/%s" % (chained_request_prepid))
|
|
230
|
+
return res.get("results", None)
|
|
231
|
+
|
|
232
|
+
def flow(self, chained_request_prepid):
|
|
233
|
+
"""
|
|
234
|
+
Flow a chained request
|
|
235
|
+
"""
|
|
236
|
+
res = self._get("restapi/chained_requests/flow/%s" % (chained_request_prepid))
|
|
237
|
+
return res.get("results", None)
|
|
238
|
+
|
|
239
|
+
def root_requests_from_ticket(self, ticket_prepid):
|
|
240
|
+
"""
|
|
241
|
+
Return list of all root (first ones in the chain) requests of a ticket
|
|
242
|
+
"""
|
|
243
|
+
mccm = self.get("mccms", ticket_prepid)
|
|
244
|
+
query = ""
|
|
245
|
+
for root_request in mccm.get("requests", []):
|
|
246
|
+
if isinstance(root_request, str):
|
|
247
|
+
query += "%s\n" % (root_request)
|
|
248
|
+
elif isinstance(root_request, list):
|
|
249
|
+
# List always contains two elements - start and end of a range
|
|
250
|
+
query += "%s -> %s\n" % (root_request[0], root_request[1])
|
|
251
|
+
else:
|
|
252
|
+
self.logger.error(
|
|
253
|
+
"%s is of unsupported type %s", root_request, type(root_request)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
requests = self.get_range_of_requests(query)
|
|
257
|
+
return requests
|
|
258
|
+
|
|
259
|
+
def is_root_request(self, request: Union[str, dict]) -> bool:
|
|
260
|
+
"""
|
|
261
|
+
Checks if a request is a root request.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
request: Request prepid or request data.
|
|
265
|
+
The request must exists and its data must comply the expected
|
|
266
|
+
schema.
|
|
267
|
+
"""
|
|
268
|
+
request_data: dict = {}
|
|
269
|
+
if isinstance(request, str):
|
|
270
|
+
# Received request prepid
|
|
271
|
+
request_data = self.get(object_type="requests", object_id=request) or {}
|
|
272
|
+
elif isinstance(request, dict):
|
|
273
|
+
request_data = request
|
|
274
|
+
else:
|
|
275
|
+
raise ValueError(f"Unexpected value: {request} - {type(request)}")
|
|
276
|
+
|
|
277
|
+
return request_data.get("type", "") in ("LHE", "Prod")
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module allows invalidating
|
|
3
|
+
root requests and its generated chains.
|
|
4
|
+
This is intended to be used only for
|
|
5
|
+
`production_manager` users or `administrators`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
import logging
|
|
10
|
+
from copy import deepcopy
|
|
11
|
+
from itertools import groupby
|
|
12
|
+
from typing import Union
|
|
13
|
+
|
|
14
|
+
from rest import McM
|
|
15
|
+
from rest.utils.miscellaneous import pformat
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InvalidateDeleteRequests:
|
|
19
|
+
"""
|
|
20
|
+
Invalidate and delete all the chain requests in McM
|
|
21
|
+
linked to a root request in McM and delete the
|
|
22
|
+
root requests if required.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, mcm: McM) -> None:
|
|
26
|
+
self.mcm = mcm
|
|
27
|
+
self.logger = self._get_logger()
|
|
28
|
+
self._chain_request_type = "chained_requests"
|
|
29
|
+
self._request_type = "requests"
|
|
30
|
+
self.logger.warning(
|
|
31
|
+
"Sending requests to target environment: %s", self.mcm.server
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _get_logger(self) -> logging.Logger:
|
|
35
|
+
"""
|
|
36
|
+
Creates a logger to record the performed steps.
|
|
37
|
+
"""
|
|
38
|
+
logger: logging.Logger = logging.getLogger(__name__)
|
|
39
|
+
logger.handlers.clear() # Avoid to record the same message twice in the log
|
|
40
|
+
formatter = logging.Formatter("[%(asctime)s][%(levelname)s] %(message)s")
|
|
41
|
+
current_date = str(datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S"))
|
|
42
|
+
fh: logging.Handler = logging.FileHandler(
|
|
43
|
+
f"mcm_invalidate_requests_{current_date}.log"
|
|
44
|
+
)
|
|
45
|
+
fh.setFormatter(formatter)
|
|
46
|
+
logger.addHandler(fh)
|
|
47
|
+
return logger
|
|
48
|
+
|
|
49
|
+
def _get_invalidations(self, requests: list[str]) -> list[dict]:
|
|
50
|
+
"""
|
|
51
|
+
Get all the invalidation records related to the given requests.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
requests: List of `prepids` used to retrieve its invalidations.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Invalidations related to the requests.
|
|
58
|
+
"""
|
|
59
|
+
results: list[dict] = []
|
|
60
|
+
for req in requests:
|
|
61
|
+
inv_req = self.mcm.get("invalidations", query=f"prepid={req}")
|
|
62
|
+
assert isinstance(inv_req, list)
|
|
63
|
+
results += inv_req
|
|
64
|
+
|
|
65
|
+
return results
|
|
66
|
+
|
|
67
|
+
def _announce_invalidations(self, invalidations: list[dict]) -> dict:
|
|
68
|
+
"""
|
|
69
|
+
Announce an invalidation for the given invalidation records.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
invalidations: List of `invalidation` records to announce.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Announce result.
|
|
76
|
+
"""
|
|
77
|
+
# Flatten and just get the `_id` field
|
|
78
|
+
inv_ids: list[str] = [
|
|
79
|
+
i.get("_id", "") for i in invalidations if i.get("_id", "")
|
|
80
|
+
]
|
|
81
|
+
announce_result = self.mcm.put(
|
|
82
|
+
object_type="invalidations", object_data=inv_ids, method="announce"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return announce_result
|
|
86
|
+
|
|
87
|
+
def _process_invalidation(self, request_prepids: list[str]) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Get all the invalidations related to a request and announces them
|
|
90
|
+
in case they exist.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
request_prepids: List of request's prepid to process its
|
|
94
|
+
invalidation.
|
|
95
|
+
"""
|
|
96
|
+
invalidations_to_announce = self._get_invalidations(requests=request_prepids)
|
|
97
|
+
if invalidations_to_announce:
|
|
98
|
+
announce_result = self._announce_invalidations(
|
|
99
|
+
invalidations=invalidations_to_announce
|
|
100
|
+
)
|
|
101
|
+
self.logger.info("Invalidation result: %s", announce_result)
|
|
102
|
+
if not announce_result.get("results"):
|
|
103
|
+
msg = f"Unable to invalidate records for all the following requests: {request_prepids}"
|
|
104
|
+
self.logger.error(msg)
|
|
105
|
+
self.logger.error("Invalidation records: %s", invalidations_to_announce)
|
|
106
|
+
raise RuntimeError(msg)
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
def _invalidate_delete_chain_requests(
|
|
110
|
+
self, chain_req_data: list[dict], remove_chain: bool = False
|
|
111
|
+
) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Rewinds the chain request to the root request
|
|
114
|
+
invalidating and deleting all the intermediate requests.
|
|
115
|
+
|
|
116
|
+
Also, deletes the chain request also if required.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
chain_req_data: Chain requests data objects
|
|
120
|
+
to process.
|
|
121
|
+
remove_chain: Deletes the chain request after
|
|
122
|
+
perform the invalidation. If False, the chain request
|
|
123
|
+
disable `flag` is re-enabled.
|
|
124
|
+
"""
|
|
125
|
+
chain_req_operate: list[dict] = []
|
|
126
|
+
chain_request_only_with_root: list[dict] = []
|
|
127
|
+
chain_request_more_than_root: list[dict] = []
|
|
128
|
+
|
|
129
|
+
# Filter the chain requests that only have the root request.
|
|
130
|
+
for chain_request in chain_req_data:
|
|
131
|
+
if len(chain_request.get("chain", [])) == 1:
|
|
132
|
+
chain_request_only_with_root.append(chain_request)
|
|
133
|
+
else:
|
|
134
|
+
chain_request_more_than_root.append(chain_request)
|
|
135
|
+
|
|
136
|
+
# Group the chained request by the second request in the chain
|
|
137
|
+
# (The first that is not the `root` request).
|
|
138
|
+
if chain_request_more_than_root:
|
|
139
|
+
grouped_chain = {
|
|
140
|
+
k: list(g)
|
|
141
|
+
for k, g in groupby(
|
|
142
|
+
chain_request_more_than_root, lambda el: el["chain"][1]
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
if len(grouped_chain) != 1:
|
|
146
|
+
error_chained_request_prepids: list[str] = [
|
|
147
|
+
ch["prepid"]
|
|
148
|
+
for ch in chain_request_more_than_root
|
|
149
|
+
if ch.get("prepid")
|
|
150
|
+
]
|
|
151
|
+
msg = (
|
|
152
|
+
"Unable to process chained requests. After grouping them by the second request, "
|
|
153
|
+
f"more than one group has been found. This expects only one.\n"
|
|
154
|
+
f"Request groups: {grouped_chain.keys()}\n"
|
|
155
|
+
f"Chained requests: {pformat(error_chained_request_prepids)}"
|
|
156
|
+
)
|
|
157
|
+
self.logger.error(msg)
|
|
158
|
+
raise ValueError(msg)
|
|
159
|
+
|
|
160
|
+
# Include in the list to operate.
|
|
161
|
+
chain_req_operate += list(grouped_chain.values())[0]
|
|
162
|
+
|
|
163
|
+
if chain_request_only_with_root:
|
|
164
|
+
only_prepids = [el.get("prepid") for el in chain_request_only_with_root]
|
|
165
|
+
self.logger.warning(
|
|
166
|
+
"The following chain request only contain the root request in its chain: %s",
|
|
167
|
+
pformat(only_prepids),
|
|
168
|
+
)
|
|
169
|
+
chain_req_operate += chain_request_only_with_root
|
|
170
|
+
|
|
171
|
+
# Order the chain requests by `step` descending
|
|
172
|
+
chain_req_operate = sorted(
|
|
173
|
+
chain_req_operate, key=lambda el: el["step"], reverse=True
|
|
174
|
+
)
|
|
175
|
+
self.logger.info(
|
|
176
|
+
"Chain request pre-reset order (after sort): %s",
|
|
177
|
+
[el.get("prepid") for el in chain_req_operate],
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Set the flag to `False` and save
|
|
181
|
+
for ch_r in chain_req_operate:
|
|
182
|
+
ch_req_prepid = ch_r.get("prepid")
|
|
183
|
+
updated_ch_r = self.mcm.get(
|
|
184
|
+
object_type=self._chain_request_type, object_id=ch_req_prepid
|
|
185
|
+
)
|
|
186
|
+
updated_ch_r["action_parameters"]["flag"] = False
|
|
187
|
+
updated_ch_r = self.mcm.update(
|
|
188
|
+
object_type=self._chain_request_type, object_data=updated_ch_r
|
|
189
|
+
)
|
|
190
|
+
self.logger.info("Disable 'flag' response: %s", updated_ch_r)
|
|
191
|
+
|
|
192
|
+
if not updated_ch_r or not updated_ch_r.get("results"):
|
|
193
|
+
msg = f"Error updating chain requests: {ch_req_prepid}"
|
|
194
|
+
self.logger.error(msg)
|
|
195
|
+
raise RuntimeError(msg)
|
|
196
|
+
|
|
197
|
+
for ch_r in chain_req_operate:
|
|
198
|
+
ch_req_prepid = ch_r.get("prepid")
|
|
199
|
+
|
|
200
|
+
# Rewind the chain request to `root`.
|
|
201
|
+
rewind_endpoint = f"restapi/chained_requests/rewind_to_root/{ch_req_prepid}"
|
|
202
|
+
rewind_response = self.mcm._get(rewind_endpoint)
|
|
203
|
+
self.logger.info("Rewind chain request response: %s", rewind_response)
|
|
204
|
+
if not rewind_response or not rewind_response.get("results"):
|
|
205
|
+
msg = f"Unable to rewind chain request to root ({ch_r}) - Details: {rewind_response}"
|
|
206
|
+
self.logger.error(msg)
|
|
207
|
+
raise RuntimeError(msg)
|
|
208
|
+
|
|
209
|
+
# Announce the invalidation.
|
|
210
|
+
ch_req_requests: list[str] = ch_r.get("chain")
|
|
211
|
+
self._process_invalidation(request_prepids=ch_r.get("chain"))
|
|
212
|
+
|
|
213
|
+
# Delete the other requests EXCEPT for the `root`
|
|
214
|
+
# The first record in this list is the `root` request
|
|
215
|
+
chain_delete = ch_req_requests[1:]
|
|
216
|
+
|
|
217
|
+
# INFO: They must be deleted in order from the deepest
|
|
218
|
+
# data tier to upwards
|
|
219
|
+
chain_delete = reversed(chain_delete)
|
|
220
|
+
|
|
221
|
+
for rd in chain_delete:
|
|
222
|
+
self.mcm.delete(object_type=self._request_type, object_id=rd)
|
|
223
|
+
|
|
224
|
+
# Process the chain request
|
|
225
|
+
if remove_chain:
|
|
226
|
+
# Precondition: All the chained request share the same
|
|
227
|
+
# root request.
|
|
228
|
+
root_request_prepid: str = chain_req_operate[0].get("chain", [])[0]
|
|
229
|
+
self.logger.warning(
|
|
230
|
+
"Full resetting the root request (%s) as it is required to properly delete the chains",
|
|
231
|
+
root_request_prepid,
|
|
232
|
+
)
|
|
233
|
+
result = self.mcm.reset(prepid=root_request_prepid)
|
|
234
|
+
if not result:
|
|
235
|
+
raise RuntimeError("Unable to reset the root request", result)
|
|
236
|
+
|
|
237
|
+
# Process the root request invalidation.
|
|
238
|
+
self._process_invalidation([root_request_prepid])
|
|
239
|
+
|
|
240
|
+
# Remove all the chain requests
|
|
241
|
+
for ch_r in chain_req_operate:
|
|
242
|
+
ch_req_prepid = ch_r.get("prepid")
|
|
243
|
+
self.mcm.delete(
|
|
244
|
+
object_type=self._chain_request_type, object_id=ch_req_prepid
|
|
245
|
+
)
|
|
246
|
+
else:
|
|
247
|
+
# Re-enable the `flag`
|
|
248
|
+
for ch_r in chain_req_operate:
|
|
249
|
+
ch_req_prepid = ch_r.get("prepid")
|
|
250
|
+
updated_ch_r = self.mcm.get(
|
|
251
|
+
object_type=self._chain_request_type, object_id=ch_req_prepid
|
|
252
|
+
)
|
|
253
|
+
updated_ch_r["action_parameters"]["flag"] = True
|
|
254
|
+
updated_ch_r = self.mcm.update(
|
|
255
|
+
object_type=self._chain_request_type, object_data=updated_ch_r
|
|
256
|
+
)
|
|
257
|
+
self.logger.info("Disable 'flag' response: %s", updated_ch_r)
|
|
258
|
+
if not updated_ch_r or not updated_ch_r.get("results"):
|
|
259
|
+
msg = f"Error updating chain requests: {ch_req_prepid}"
|
|
260
|
+
self.logger.error(msg)
|
|
261
|
+
raise RuntimeError(msg)
|
|
262
|
+
|
|
263
|
+
def _invalidate_delete_root_request(
|
|
264
|
+
self, root_prepid: str, remove_root: bool = False, remove_chain: bool = False
|
|
265
|
+
) -> None:
|
|
266
|
+
"""
|
|
267
|
+
Invalidates and deletes all the chained request
|
|
268
|
+
linked to a root request and remove the root
|
|
269
|
+
request if required.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
root_prepid: Root request prepid
|
|
273
|
+
remove_root: Invalidate and delete the root
|
|
274
|
+
request after processing its chains. If True,
|
|
275
|
+
the parameter `remove_chains` will be forced to
|
|
276
|
+
`True`
|
|
277
|
+
remove_chain: Delete the chain request after
|
|
278
|
+
processing it. If False, the chained requests
|
|
279
|
+
`flag` is re-enabled.
|
|
280
|
+
"""
|
|
281
|
+
self.logger.info(
|
|
282
|
+
"Processing root request: %s, remove root: %s, remove chains: %s",
|
|
283
|
+
root_prepid,
|
|
284
|
+
remove_root,
|
|
285
|
+
remove_chain,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
if remove_root and not remove_chain:
|
|
289
|
+
self.logger.warning(
|
|
290
|
+
"Setting `remove_chain` to True as a cascade delete is requested"
|
|
291
|
+
)
|
|
292
|
+
remove_chain = True
|
|
293
|
+
|
|
294
|
+
if remove_chain:
|
|
295
|
+
self.logger.warning(
|
|
296
|
+
(
|
|
297
|
+
"Root request will be FULL RESET "
|
|
298
|
+
"and its output dataset will be invalidated! "
|
|
299
|
+
"This is required to delete all its related chained requests."
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# 1. Get all the chained requests and process them.
|
|
304
|
+
chain_req: list[dict] = self.mcm.get(
|
|
305
|
+
object_type=self._chain_request_type, query=f"contains={root_prepid}"
|
|
306
|
+
)
|
|
307
|
+
self.logger.info(
|
|
308
|
+
"Request (%s), operating chain requests: %s",
|
|
309
|
+
root_prepid,
|
|
310
|
+
pformat([ch.get("prepid") for ch in chain_req]),
|
|
311
|
+
)
|
|
312
|
+
self._invalidate_delete_chain_requests(
|
|
313
|
+
chain_req_data=chain_req, remove_chain=remove_chain
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# 2. Remove the root request if required.
|
|
317
|
+
if remove_root:
|
|
318
|
+
self.mcm.delete(object_type=self._request_type, object_id=root_prepid)
|
|
319
|
+
|
|
320
|
+
def _filter_root_requests(self, requests_prepid: list[str]) -> list[str]:
|
|
321
|
+
"""
|
|
322
|
+
Filter the given request's prepids and only pick
|
|
323
|
+
those related only to root requests.
|
|
324
|
+
"""
|
|
325
|
+
root_requests: list[str] = []
|
|
326
|
+
for rid in requests_prepid:
|
|
327
|
+
if self.mcm.is_root_request(request=rid):
|
|
328
|
+
root_requests.append(rid)
|
|
329
|
+
return root_requests
|
|
330
|
+
|
|
331
|
+
def invalidate_delete_cascade_requests(
|
|
332
|
+
self,
|
|
333
|
+
requests_prepid: list[str],
|
|
334
|
+
remove_root: bool = False,
|
|
335
|
+
remove_chain: bool = False,
|
|
336
|
+
limit: int = 2**64,
|
|
337
|
+
) -> dict[str, list[str]]:
|
|
338
|
+
"""
|
|
339
|
+
Invalidate and delete all the request including into the
|
|
340
|
+
chain requests related to the provided root request.
|
|
341
|
+
This could be considered as a "delete in cascade" process.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
requests_prepid: List of root requests to process.
|
|
345
|
+
In case a non-root request is provided, it will be
|
|
346
|
+
filtered.
|
|
347
|
+
remove_root: After processing the chained requests,
|
|
348
|
+
invalidate the dataset related to the root request and
|
|
349
|
+
delete it.
|
|
350
|
+
remove_chain: After removing the intermediate requests,
|
|
351
|
+
remove the chain or re-enable it chain again.
|
|
352
|
+
limit: Process root requests until the limit is reached.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Details about the requests processed in three categories: Success, Failed, Filtered.
|
|
356
|
+
"""
|
|
357
|
+
root_request_prepids: list[str] = self._filter_root_requests(
|
|
358
|
+
requests_prepid=deepcopy(requests_prepid)
|
|
359
|
+
)
|
|
360
|
+
discarded_prepids = list(set(requests_prepid) - set(root_request_prepids))
|
|
361
|
+
if discarded_prepids:
|
|
362
|
+
self.logger.info(
|
|
363
|
+
"Following requests are not valid root requests (%s): %s",
|
|
364
|
+
len(discarded_prepids),
|
|
365
|
+
pformat(discarded_prepids),
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
failed_requests: list[str] = []
|
|
369
|
+
success_requests: list[str] = []
|
|
370
|
+
total_to_process = len(root_request_prepids)
|
|
371
|
+
for idx, root_prepid in enumerate(root_request_prepids, start=1):
|
|
372
|
+
try:
|
|
373
|
+
self.logger.info(
|
|
374
|
+
"(%s/%s) Processing root request", idx, total_to_process
|
|
375
|
+
)
|
|
376
|
+
self._invalidate_delete_root_request(
|
|
377
|
+
root_prepid=root_prepid,
|
|
378
|
+
remove_root=remove_root,
|
|
379
|
+
remove_chain=remove_chain,
|
|
380
|
+
)
|
|
381
|
+
success_requests.append(root_prepid)
|
|
382
|
+
except Exception as e:
|
|
383
|
+
self.logger.error(
|
|
384
|
+
"Unable to process root request (%s): %s",
|
|
385
|
+
root_prepid,
|
|
386
|
+
e,
|
|
387
|
+
exc_info=True,
|
|
388
|
+
)
|
|
389
|
+
failed_requests.append(root_prepid)
|
|
390
|
+
finally:
|
|
391
|
+
if limit <= idx:
|
|
392
|
+
self.logger.info("Early stopping processing root requests...")
|
|
393
|
+
break
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
"success": success_requests,
|
|
397
|
+
"failed": failed_requests,
|
|
398
|
+
"filtered": discarded_prepids,
|
|
399
|
+
}
|