illumio-pylo 0.3.11__py3-none-any.whl → 0.3.13__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.
Files changed (30) hide show
  1. illumio_pylo/API/APIConnector.py +138 -106
  2. illumio_pylo/API/CredentialsManager.py +168 -3
  3. illumio_pylo/API/Explorer.py +619 -14
  4. illumio_pylo/API/JsonPayloadTypes.py +64 -4
  5. illumio_pylo/FilterQuery.py +892 -0
  6. illumio_pylo/Helpers/exports.py +1 -1
  7. illumio_pylo/LabelCommon.py +13 -3
  8. illumio_pylo/LabelDimension.py +109 -0
  9. illumio_pylo/LabelStore.py +97 -38
  10. illumio_pylo/WorkloadStore.py +58 -0
  11. illumio_pylo/__init__.py +9 -3
  12. illumio_pylo/cli/__init__.py +5 -2
  13. illumio_pylo/cli/commands/__init__.py +1 -0
  14. illumio_pylo/cli/commands/credential_manager.py +555 -4
  15. illumio_pylo/cli/commands/label_delete_unused.py +0 -3
  16. illumio_pylo/cli/commands/traffic_export.py +358 -0
  17. illumio_pylo/cli/commands/ui/credential_manager_ui/app.js +638 -0
  18. illumio_pylo/cli/commands/ui/credential_manager_ui/index.html +217 -0
  19. illumio_pylo/cli/commands/ui/credential_manager_ui/styles.css +581 -0
  20. illumio_pylo/cli/commands/update_pce_objects_cache.py +1 -2
  21. illumio_pylo/cli/commands/ven_duplicate_remover.py +79 -59
  22. illumio_pylo/cli/commands/workload_export.py +29 -0
  23. illumio_pylo/utilities/cli.py +4 -1
  24. illumio_pylo/utilities/health_monitoring.py +5 -1
  25. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/METADATA +2 -1
  26. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/RECORD +29 -24
  27. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/WHEEL +1 -1
  28. illumio_pylo/Query.py +0 -331
  29. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/licenses/LICENSE +0 -0
  30. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/top_level.txt +0 -0
@@ -30,7 +30,7 @@ requests.packages.urllib3.disable_warnings()
30
30
 
31
31
  default_retry_count_if_api_call_limit_reached = 3
32
32
  default_retry_wait_time_if_api_call_limit_reached = 10
33
- default_max_objects_for_sync_calls = 99999
33
+ default_max_objects_for_sync_calls = 200000
34
34
 
35
35
 
36
36
  def get_field_or_die(field_name: str, data):
@@ -45,7 +45,7 @@ def get_field_or_die(field_name: str, data):
45
45
 
46
46
 
47
47
  ObjectTypes = Literal['iplists', 'workloads', 'virtual_services', 'labels', 'labelgroups', 'services', 'rulesets',
48
- 'security_principals', 'label_dimensions']
48
+ 'security_principals', 'label_dimensions']
49
49
 
50
50
  all_object_types: Dict[ObjectTypes, ObjectTypes] = {
51
51
  'iplists': 'iplists',
@@ -153,6 +153,33 @@ class APIConnector:
153
153
 
154
154
  return url
155
155
 
156
+ def _get_objects_auto_switch_async(self, path: str, data: Dict[str, Any], async_mode: bool, max_results: Optional[int]) -> Any:
157
+ # use a copy of data to not modify the original
158
+ data_copy = data.copy()
159
+
160
+ if async_mode:
161
+ if max_results is not None:
162
+ data_copy['max_results'] = max_results
163
+ return self.do_get_call(path=path, async_call=True, params=data_copy)
164
+
165
+ if max_results is not None:
166
+ data_copy['max_results'] = max_results
167
+ return self.do_get_call(path=path, async_call=False, params=data_copy)
168
+
169
+ # We will grab the maximum allowed with sync mode (variable default_max_objects_for_sync_calls) and switch to async if it's higher
170
+ data_copy['max_results'] = default_max_objects_for_sync_calls
171
+ results = self.do_get_call(path=path, async_call=False, params=data_copy, return_headers=True)
172
+ total_count = results[1].get('x-total-count')
173
+ if total_count is None:
174
+ raise pylo.PyloApiEx('API didnt provide field "x-total-count" in headers')
175
+ if not total_count.isdigit():
176
+ raise pylo.PyloApiEx('API returned invalid value for "x-total-count": {}'.format(total_count))
177
+ if int(total_count) > default_max_objects_for_sync_calls:
178
+ # remove the max_results from data
179
+ del data_copy['max_results']
180
+ return self.do_get_call(path=path, async_call=True, params=data_copy)
181
+ return results[0]
182
+
156
183
  def do_get_call(self, path, json_arguments=None, include_org_id=True, json_output_expected=True, async_call=False, params=None, skip_product_version_check=False,
157
184
  retry_count_if_api_call_limit_reached=default_retry_count_if_api_call_limit_reached,
158
185
  retry_wait_time_if_api_call_limit_reached=default_retry_wait_time_if_api_call_limit_reached,
@@ -275,7 +302,7 @@ class APIConnector:
275
302
  log.info("Job status is " + job_poll_status)
276
303
 
277
304
  log.info("Job is done, we will now download the resulting dataset")
278
- dataset = self.do_get_call(result_href, include_org_id=False)
305
+ dataset = self.do_get_call(result_href, include_org_id=False, return_headers=return_headers)
279
306
 
280
307
  return dataset
281
308
 
@@ -285,7 +312,7 @@ class APIConnector:
285
312
  or\
286
313
  method == 'DELETE' and req.status_code != 204 \
287
314
  or \
288
- method == 'PUT' and req.status_code != 204 and req.status_code != 200:
315
+ method == 'PUT' and req.status_code != 204 and req.status_code != 202 and req.status_code != 200:
289
316
 
290
317
  if req.status_code == 429:
291
318
  # too many requests sent in short amount of time? [{"token":"too_many_requests_error", ....}]
@@ -313,9 +340,6 @@ class APIConnector:
313
340
  raise pylo.PyloApiEx('API returned error status "' + str(req.status_code) + ' ' + req.reason
314
341
  + '" and error message: ' + req.text)
315
342
 
316
- if return_headers:
317
- return req.headers
318
-
319
343
  if json_output_expected:
320
344
  log.info("Parsing API answer to JSON (with a size of " + str(int(answer_size)) + "KB)")
321
345
  json_out = req.json()
@@ -325,8 +349,12 @@ class APIConnector:
325
349
  log.info(json.dumps(json_out, indent=2, sort_keys=True))
326
350
  else:
327
351
  log.info("Answer is too large to be printed")
352
+ if return_headers:
353
+ return json_out, req.headers
328
354
  return json_out
329
355
 
356
+ if return_headers:
357
+ return req.text, req.headers
330
358
  return req.text
331
359
 
332
360
  raise pylo.PyloApiEx("Unexpected API output or race condition")
@@ -339,33 +367,39 @@ class APIConnector:
339
367
  self.collect_pce_infos()
340
368
  return self.version_string
341
369
 
342
- def get_objects_count_by_type(self, object_type: str) -> int:
370
+ def get_objects_count_by_type(self, object_type: ObjectTypes) -> int:
343
371
 
344
372
  def extract_count(headers):
345
373
  count = headers.get('x-total-count')
346
374
  if count is None:
347
375
  raise pylo.PyloApiEx('API didnt provide field "x-total-count"')
348
376
 
377
+ # count should be an integer
378
+ if not count.isdigit():
379
+ raise pylo.PyloApiEx('API returned invalid value for "x-total-count": {}'.format(count))
380
+
349
381
  return int(count)
350
382
 
383
+ params = {'max_results': 1}
384
+
351
385
  if object_type == 'workloads':
352
- return extract_count(self.do_get_call('/workloads', async_call=False, return_headers=True))
386
+ return extract_count(self.do_get_call('/workloads', async_call=False, return_headers=True, params=params)[1])
353
387
  elif object_type == 'virtual_services':
354
- return extract_count(self.do_get_call('/sec_policy/draft/virtual_services', async_call=False, return_headers=True))
388
+ return extract_count(self.do_get_call('/sec_policy/draft/virtual_services', async_call=False, return_headers=True, params=params)[1])
355
389
  elif object_type == 'labels':
356
- return extract_count(self.do_get_call('/labels', async_call=False, return_headers=True))
390
+ return extract_count(self.do_get_call('/labels', async_call=False, return_headers=True, params=params)[1])
357
391
  elif object_type == 'labelgroups':
358
- return extract_count(self.do_get_call('/sec_policy/draft/label_groups', async_call=False, return_headers=True))
392
+ return extract_count(self.do_get_call('/sec_policy/draft/label_groups', async_call=False, return_headers=True, params=params)[1])
359
393
  elif object_type == 'iplists':
360
- return extract_count(self.do_get_call('/sec_policy/draft/ip_lists', async_call=False, return_headers=True))
394
+ return extract_count(self.do_get_call('/sec_policy/draft/ip_lists', async_call=False, return_headers=True, params=params)[1])
361
395
  elif object_type == 'services':
362
- return extract_count(self.do_get_call('/sec_policy/draft/services', async_call=False, return_headers=True))
396
+ return extract_count(self.do_get_call('/sec_policy/draft/services', async_call=False, return_headers=True, params=params)[1])
363
397
  elif object_type == 'rulesets':
364
- return extract_count(self.do_get_call('/sec_policy/draft/rule_sets', async_call=False, return_headers=True))
398
+ return extract_count(self.do_get_call('/sec_policy/draft/rule_sets', async_call=False, return_headers=True, params=params)[1])
365
399
  elif object_type == 'security_principals':
366
- return extract_count(self.do_get_call('/security_principals', async_call=False, return_headers=True))
400
+ return extract_count(self.do_get_call('/security_principals', async_call=False, return_headers=True, params=params)[1])
367
401
  elif object_type == 'label_dimensions':
368
- return extract_count(self.do_get_call('/label_dimensions', async_call=False, return_headers=True))
402
+ return extract_count(self.do_get_call('/label_dimensions', async_call=False, return_headers=True, params=params)[1])
369
403
  else:
370
404
  raise pylo.PyloEx("Unsupported object type '{}'".format(object_type))
371
405
 
@@ -403,57 +437,32 @@ class APIConnector:
403
437
  q.task_done()
404
438
  continue
405
439
  if object_type == 'workloads':
406
- if self.get_objects_count_by_type(object_type) > default_max_objects_for_sync_calls or force_async_mode:
407
- data['workloads'] = self.objects_workload_get(include_deleted=include_deleted_workloads)
408
- else:
409
- data['workloads'] = self.objects_workload_get(include_deleted=include_deleted_workloads, async_mode=False, max_results=default_max_objects_for_sync_calls)
440
+ data['workloads'] = self.objects_workload_get(include_deleted=include_deleted_workloads, async_mode=force_async_mode)
410
441
 
411
442
  elif object_type == 'virtual_services':
412
- if self.get_objects_count_by_type(object_type) > default_max_objects_for_sync_calls:
413
- data['virtual_services'] = self.objects_virtual_service_get()
414
- else:
415
- data['virtual_services'] = self.objects_virtual_service_get(async_mode=False, max_results=default_max_objects_for_sync_calls)
443
+ data['virtual_services'] = self.objects_virtual_service_get(async_mode=force_async_mode)
416
444
 
417
445
  elif object_type == 'labels':
418
- if self.get_objects_count_by_type(object_type) > default_max_objects_for_sync_calls:
419
- data['labels'] = self.objects_label_get()
420
- else:
421
- data['labels'] = self.objects_label_get(async_mode=False, max_results=default_max_objects_for_sync_calls)
446
+ data['labels'] = self.objects_label_get(async_mode=force_async_mode)
422
447
 
423
448
  elif object_type == 'labelgroups':
424
- if self.get_objects_count_by_type(object_type) > default_max_objects_for_sync_calls:
425
- data['labelgroups'] = self.objects_labelgroup_get()
426
- else:
427
- data['labelgroups'] = self.objects_labelgroup_get(async_mode=False, max_results=default_max_objects_for_sync_calls)
449
+ data['labelgroups'] = self.objects_labelgroup_get(async_mode=force_async_mode)
428
450
 
429
451
  elif object_type == 'iplists':
430
- if self.get_objects_count_by_type(object_type) > default_max_objects_for_sync_calls:
431
- data['iplists'] = self.objects_iplist_get()
432
- else:
433
- data['iplists'] = self.objects_iplist_get(async_mode=False, max_results=default_max_objects_for_sync_calls)
452
+ data['iplists'] = self.objects_iplist_get(async_mode=force_async_mode)
434
453
 
435
454
  elif object_type == 'services':
436
- if self.get_objects_count_by_type(object_type) > default_max_objects_for_sync_calls:
437
- data['services'] = self.objects_service_get()
438
- else:
439
- data['services'] = self.objects_service_get(async_mode=False, max_results=default_max_objects_for_sync_calls)
455
+ data['services'] = self.objects_service_get(async_mode=force_async_mode)
440
456
 
441
457
  elif object_type == 'rulesets':
442
- if self.get_objects_count_by_type(object_type) > default_max_objects_for_sync_calls:
443
- data['rulesets'] = self.objects_ruleset_get()
444
- else:
445
- data['rulesets'] = self.objects_ruleset_get(async_mode=False, max_results=default_max_objects_for_sync_calls)
458
+ data['rulesets'] = self.objects_ruleset_get(async_mode=force_async_mode)
446
459
 
447
460
  elif object_type == 'security_principals':
448
- if self.get_objects_count_by_type(object_type) > default_max_objects_for_sync_calls:
449
- data['security_principals'] = self.objects_securityprincipal_get()
450
- else:
451
- data['security_principals'] = self.objects_securityprincipal_get(async_mode=False, max_results=default_max_objects_for_sync_calls)
461
+ data['security_principals'] = self.objects_securityprincipal_get(async_mode=force_async_mode)
462
+
452
463
  elif object_type == 'label_dimensions':
453
- if self.get_objects_count_by_type(object_type) > default_max_objects_for_sync_calls:
454
- data['label_dimensions'] = self.objects_label_dimension_get()
455
- else:
456
- data['label_dimensions'] = self.objects_label_dimension_get(async_mode=False, max_results=default_max_objects_for_sync_calls)
464
+ data['label_dimensions'] = self.objects_label_dimension_get()
465
+
457
466
  else:
458
467
  raise pylo.PyloEx("Unsupported object type '{}'".format(object_type))
459
468
  except Exception as e:
@@ -533,20 +542,17 @@ class APIConnector:
533
542
  params = {'include_deny_rules': include_boundary_rules}
534
543
  return self.do_post_call(path='/sec_policy/draft/rule_coverage', json_arguments=data, include_org_id=True, json_output_expected=True, async_call=False, params=params)
535
544
 
536
- def objects_label_get(self, max_results: int = None, async_mode=True, get_usage: bool = False, get_deleted: bool = False) -> List[LabelObjectJsonStructure]:
545
+ def objects_label_get(self, max_results: int = None, async_mode=False, get_usage: bool = False, get_deleted: bool = False) -> List[LabelObjectJsonStructure]:
537
546
  path = '/labels'
538
547
  data = {}
539
548
 
540
- if max_results is not None:
541
- data['max_results'] = max_results
542
-
543
549
  if get_usage:
544
550
  data['usage'] = 'true'
545
551
 
546
552
  if get_deleted:
547
553
  data['includeDeleted'] = 'true'
548
554
 
549
- return self.do_get_call(path=path, async_call=async_mode, params=data)
555
+ return self._get_objects_auto_switch_async(path=path, data=data, async_mode=async_mode, max_results=max_results)
550
556
 
551
557
  def objects_label_update(self, href: str, data: LabelObjectUpdateJsonStructure):
552
558
  path = href
@@ -618,14 +624,11 @@ class APIConnector:
618
624
  data: LabelObjectCreationJsonStructure = {'key': label_type, 'value': label_name}
619
625
  return self.do_post_call(path=path, json_arguments=data)
620
626
 
621
- def objects_labelgroup_get(self, max_results: int = None, async_mode=True) -> List[LabelGroupObjectJsonStructure]:
627
+ def objects_labelgroup_get(self, max_results: int = None, async_mode=False) -> List[LabelGroupObjectJsonStructure]:
622
628
  path = '/sec_policy/draft/label_groups'
623
629
  data = {}
624
630
 
625
- if max_results is not None:
626
- data['max_results'] = max_results
627
-
628
- return self.do_get_call(path=path, async_call=async_mode, params=data)
631
+ return self._get_objects_auto_switch_async(path=path, data=data, async_mode=async_mode, max_results=max_results)
629
632
 
630
633
  def objects_labelgroup_update(self, href: str, data: LabelGroupObjectUpdateJsonStructure):
631
634
  path = href
@@ -635,34 +638,26 @@ class APIConnector:
635
638
  path = '/label_dimensions'
636
639
  data = {}
637
640
 
638
- if max_results is not None:
639
- data['max_results'] = max_results
640
- return self.do_get_call(path=path, async_call=async_mode, params=data)
641
+ return self._get_objects_auto_switch_async(path=path, data=data, async_mode=async_mode, max_results=max_results)
641
642
 
642
- def objects_virtual_service_get(self, max_results: int = None, async_mode=True) -> List[VirtualServiceObjectJsonStructure]:
643
+ def objects_virtual_service_get(self, max_results: int = None, async_mode=False) -> List[VirtualServiceObjectJsonStructure]:
643
644
  path = '/sec_policy/draft/virtual_services'
644
645
  data = {}
645
646
 
646
- if max_results is not None:
647
- data['max_results'] = max_results
648
-
649
- results = self.do_get_call(path=path, async_call=async_mode, params=data)
647
+ results = self._get_objects_auto_switch_async(path=path, data=data, async_mode=async_mode, max_results=max_results)
650
648
  # check type
651
649
  if type(results) is list:
652
650
  return results
653
651
  raise pylo.PyloEx("Unexpected result type '{}' while expecting an array of Virtual Service objects".format(type(results)), results)
654
652
 
655
- def objects_iplist_get(self, max_results: int = None, async_mode=True, search_name: str = None) -> List[IPListObjectJsonStructure]:
653
+ def objects_iplist_get(self, max_results: int = None, async_mode=False, search_name: str = None) -> List[IPListObjectJsonStructure]:
656
654
  path = '/sec_policy/draft/ip_lists'
657
655
  data = {}
658
656
 
659
657
  if search_name is not None:
660
658
  data['name'] = search_name
661
659
 
662
- if max_results is not None:
663
- data['max_results'] = max_results
664
-
665
- results: List[IPListObjectJsonStructure] = self.do_get_call(path=path, async_call=async_mode, params=data)
660
+ results: List[IPListObjectJsonStructure] = self._get_objects_auto_switch_async(path=path, data=data, async_mode=async_mode, max_results=max_results)
666
661
  # check type
667
662
  if type(results) is list:
668
663
  return results
@@ -714,10 +709,7 @@ class APIConnector:
714
709
  if representation is not None:
715
710
  data['representation'] = representation
716
711
 
717
- if max_results is not None:
718
- data['max_results'] = max_results
719
-
720
- return self.do_get_call(path=path, async_call=async_mode, params=data)
712
+ return self._get_objects_auto_switch_async(path=path, data=data, async_mode=async_mode, max_results=max_results)
721
713
 
722
714
  def objects_workload_get(self,
723
715
  include_deleted=False,
@@ -727,7 +719,7 @@ class APIConnector:
727
719
  filter_by_managed: bool = None,
728
720
  filer_by_policy_health: Literal['active', 'warning', 'error'] = None,
729
721
  max_results: int = None,
730
- async_mode=True) -> List[WorkloadObjectJsonStructure]:
722
+ async_mode=False) -> List[WorkloadObjectJsonStructure]:
731
723
  path = '/workloads'
732
724
  data = {}
733
725
 
@@ -750,10 +742,8 @@ class APIConnector:
750
742
  if filer_by_policy_health is not None:
751
743
  data['policy_health'] = filer_by_policy_health
752
744
 
753
- if max_results is not None:
754
- data['max_results'] = max_results
745
+ return self._get_objects_auto_switch_async(path=path, data=data, async_mode=async_mode, max_results=max_results)
755
746
 
756
- return self.do_get_call(path=path, async_call=async_mode, params=data)
757
747
 
758
748
  def objects_workload_agent_upgrade(self, workload_href: str, target_version: str):
759
749
  path = '{}/upgrade'.format(workload_href)
@@ -1001,14 +991,11 @@ class APIConnector:
1001
991
  path = '/workloads/bulk_create'
1002
992
  return self.do_put_call(path=path, json_arguments=workloads_json_payload)
1003
993
 
1004
- def objects_service_get(self, max_results: int = None, async_mode=True):
994
+ def objects_service_get(self, max_results: int = None, async_mode=False):
1005
995
  path = '/sec_policy/draft/services'
1006
996
  data = {}
1007
997
 
1008
- if max_results is not None:
1009
- data['max_results'] = max_results
1010
-
1011
- return self.do_get_call(path=path, async_call=async_mode, params=data)
998
+ return self._get_objects_auto_switch_async(path=path, data=data, async_mode=async_mode, max_results=max_results)
1012
999
 
1013
1000
  def objects_service_delete(self, href):
1014
1001
  """
@@ -1050,14 +1037,11 @@ class APIConnector:
1050
1037
 
1051
1038
  return self.do_post_call(path=path, async_call=False, include_org_id=False, json_arguments=data, json_output_expected=True)
1052
1039
 
1053
- def objects_ruleset_get(self, max_results: int = None, async_mode=True) -> List[RulesetObjectJsonStructure]:
1040
+ def objects_ruleset_get(self, max_results: int = None, async_mode=False) -> List[RulesetObjectJsonStructure]:
1054
1041
  path = '/sec_policy/draft/rule_sets'
1055
1042
  data = {}
1056
1043
 
1057
- if max_results is not None:
1058
- data['max_results'] = max_results
1059
-
1060
- return self.do_get_call(path=path, async_call=async_mode, params=data)
1044
+ return self._get_objects_auto_switch_async(path=path, data=data, async_mode=async_mode, max_results=max_results)
1061
1045
 
1062
1046
  def objects_ruleset_create(self, name: str,
1063
1047
  scope_app: 'pylo.Label' = None,
@@ -1176,14 +1160,11 @@ class APIConnector:
1176
1160
 
1177
1161
  return self.do_post_call(path, json_arguments=data, json_output_expected=True, include_org_id=False)
1178
1162
 
1179
- def objects_securityprincipal_get(self, max_results: int = None, async_mode=True) -> List[SecurityPrincipalObjectJsonStructure]:
1163
+ def objects_securityprincipal_get(self, max_results: int = None, async_mode=False) -> List[SecurityPrincipalObjectJsonStructure]:
1180
1164
  path = '/security_principals'
1181
1165
  data = {}
1182
1166
 
1183
- if max_results is not None:
1184
- data['max_results'] = max_results
1185
-
1186
- return self.do_get_call(path=path, async_call=async_mode, params=data)
1167
+ return self._get_objects_auto_switch_async(path=path, data=data, async_mode=async_mode, max_results=max_results)
1187
1168
 
1188
1169
  def objects_securityprincipal_create(self, name: str = None, sid: str = None, json_data=None) -> str:
1189
1170
  """
@@ -1338,12 +1319,23 @@ class APIConnector:
1338
1319
 
1339
1320
  raise pylo.PyloObjectNotFound("Request with ID {} not found".format(request_href))
1340
1321
 
1341
- def explorer_search(self, filters: Union[Dict, 'pylo.ExplorerFilterSetV1'],
1342
- max_running_time_seconds=1800,
1343
- check_for_update_interval_seconds=10) -> 'pylo.ExplorerResultSetV1':
1322
+ def explorer_search(self, filters: Union[Dict, 'pylo.ExplorerFilterSetV1', 'pylo.ExplorerFilterSetV2'],
1323
+ max_running_time_seconds=1800,check_for_update_interval_seconds=10, draft_mode_enabled=False)\
1324
+ -> Union['pylo.ExplorerResultSetV1', 'pylo.ExplorerResultSetV2']:
1325
+ """
1326
+
1327
+ :param filters:
1328
+ :param max_running_time_seconds:
1329
+ :param check_for_update_interval_seconds:
1330
+ :param draft_mode_enabled: only for V2 filters
1331
+ :return:
1332
+ """
1333
+
1344
1334
  path = "/traffic_flows/async_queries"
1345
1335
  if isinstance(filters, pylo.ExplorerFilterSetV1):
1346
1336
  data = filters.generate_json_query()
1337
+ elif isinstance(filters, pylo.ExplorerFilterSetV2):
1338
+ data = filters.generate_json_query()
1347
1339
  else:
1348
1340
  data = filters
1349
1341
 
@@ -1388,11 +1380,45 @@ class APIConnector:
1388
1380
  if query_status is None:
1389
1381
  raise pylo.PyloEx("Unexpected logic where query_status is None", query_queued_json_response)
1390
1382
 
1391
- query_json_response = self.do_get_call(query_href + "/download", json_output_expected=True, include_org_id=False)
1383
+ # if draft mode is not enabled we can download the results and pass them over
1384
+ if not draft_mode_enabled or isinstance(filters, pylo.ExplorerFilterSetV1):
1385
+ query_json_response = self.do_get_call(query_href + "/download", json_output_expected=True, include_org_id=False)
1392
1386
 
1393
- result = pylo.ExplorerResultSetV1(query_json_response,
1394
- owner=self,
1395
- emulated_process_exclusion=filters.exclude_processes_emulate)
1387
+ if isinstance(filters, pylo.ExplorerFilterSetV1):
1388
+ result = pylo.ExplorerResultSetV1(query_json_response,
1389
+ owner=self,
1390
+ emulated_process_exclusion=filters.exclude_processes_emulate)
1391
+ else:
1392
+ result = pylo.ExplorerResultSetV2(query_json_response)
1393
+
1394
+ return result
1395
+
1396
+ # from here we are in draft mode with V2 filters so we must request API to calculate the draft results
1397
+ draft_mode_trigger_url = query_href + "/update_rules?label_based_rules=false&offset=0&limit=250000"
1398
+ draft_mode_trigger_response = self.do_put_call(draft_mode_trigger_url, json_output_expected=False, include_org_id=False)
1399
+
1400
+ time.sleep(5) # wait a bit before checking for results
1401
+
1402
+ while True:
1403
+ # check that we don't wait too long
1404
+ if time.time() - start_time > max_running_time_seconds:
1405
+ raise pylo.PyloApiEx("Timeout while waiting for draft mode results to be calculated", draft_mode_trigger_response)
1406
+
1407
+ draft_mode_status_response = self.explorer_async_query_get_specific_request_status(query_href)
1408
+ if draft_mode_status_response['rules'] == "completed":
1409
+ query_status = draft_mode_status_response
1410
+ break
1411
+
1412
+ if draft_mode_status_response['rules'] not in ["queued", "working"]:
1413
+ raise pylo.PyloApiEx("Draft mode results calculation failed with status {}".format(draft_mode_status_response['status']),
1414
+ draft_mode_status_response)
1415
+
1416
+ time.sleep(check_for_update_interval_seconds)
1417
+
1418
+ if query_status is None:
1419
+ raise pylo.PyloEx("Unexpected logic where query_status is None", query_queued_json_response)
1420
+ query_json_response = self.do_get_call(query_href + "/download", json_output_expected=True, include_org_id=False)
1421
+ result = pylo.ExplorerResultSetV2(query_json_response)
1396
1422
 
1397
1423
  return result
1398
1424
 
@@ -1422,6 +1448,12 @@ class APIConnector:
1422
1448
  check_for_update_interval_seconds: int = 10) -> 'pylo.ExplorerQuery':
1423
1449
  return pylo.ExplorerQuery(self, max_results, max_running_time_seconds, check_for_update_interval_seconds)
1424
1450
 
1451
+ def new_explorer_query_v2(self, max_results: int = 2500, draft_mode_enabled=False, max_running_time_seconds: int = 1800,
1452
+ check_for_update_interval_seconds: int = 10) -> 'pylo.ExplorerQueryV2':
1453
+ return pylo.ExplorerQueryV2(self, max_results=max_results, draft_mode_enabled=draft_mode_enabled,
1454
+ max_running_time_seconds=max_running_time_seconds,
1455
+ check_for_update_interval_seconds=check_for_update_interval_seconds)
1456
+
1425
1457
  def new_audit_log_query(self, max_results: int = 10000, max_running_time_seconds: int = 1800,
1426
1458
  check_for_update_interval_seconds: int = 10) -> 'pylo.AuditLogQuery':
1427
1459
  return pylo.AuditLogQuery(self, max_results, max_running_time_seconds)