-
Notifications
You must be signed in to change notification settings - Fork 48
issue244 solved #276
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v4.2.0-dev
Are you sure you want to change the base?
issue244 solved #276
Changes from all commits
e4f6d94
04fef19
233359a
6d7d4d4
e4defbf
b335a56
080308c
3e53f03
6f55a21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,6 +50,7 @@ coverage.xml | |
| .hypothesis/ | ||
| .pytest_cache/ | ||
| cover/ | ||
| preupgrade_validator*.tgz | ||
|
|
||
| # Translations | ||
| *.mo | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,7 +38,7 @@ | |
| import os | ||
| import re | ||
|
|
||
| SCRIPT_VERSION = "v4.0.1" | ||
| SCRIPT_VERSION = "v4.1.0-dev" | ||
| DEFAULT_TIMEOUT = 600 # sec | ||
| # result constants | ||
| DONE = 'DONE' | ||
|
|
@@ -1023,13 +1023,17 @@ def __init__( | |
| common_kwargs, | ||
| monitor_interval=0.5, # sec | ||
| monitor_timeout=600, # sec | ||
| max_threads=None, | ||
| callback_on_monitoring=None, | ||
| callback_on_start_failure=None, | ||
| callback_on_timeout=None, | ||
| ): | ||
| self.funcs = funcs | ||
| self.threads = None | ||
| self.common_kwargs = common_kwargs | ||
| # Semaphore to cap the number of concurrently running check threads. | ||
| # None means unlimited. | ||
| self.semaphore = threading.Semaphore(max_threads) if max_threads and max_threads > 0 else None | ||
|
|
||
| # Not using `thread.join(timeout)` because it waits for each thread sequentially, | ||
| # which means the program may wait for "timeout * num of threads" at worst case. | ||
|
|
@@ -1053,7 +1057,7 @@ def start(self): | |
| raise RuntimeError("Threading on going. Cannot start again.") | ||
|
|
||
| self.threads = [ | ||
| self._generate_thread(target=func, kwargs=self.common_kwargs) | ||
| self._generate_thread(target=func, kwargs=self.common_kwargs, use_semaphore=True) | ||
| for func in self.funcs | ||
| ] | ||
|
|
||
|
|
@@ -1080,9 +1084,19 @@ def join(self): | |
| def is_timeout(self): | ||
| return self.timeout_event.is_set() | ||
|
|
||
| def _generate_thread(self, target, args=(), kwargs=None): | ||
| def _generate_thread(self, target, args=(), kwargs=None, use_semaphore=False): | ||
| if kwargs is None: | ||
| kwargs = {} | ||
| if use_semaphore and self.semaphore is not None: | ||
| semaphore = self.semaphore | ||
| original_target = target | ||
| def _wrapped_target(*a, **kw): | ||
| try: | ||
| original_target(*a, **kw) | ||
| finally: | ||
| semaphore.release() | ||
| _wrapped_target.__name__ = target.__name__ | ||
| target = _wrapped_target | ||
| thread = CustomThread( | ||
| target=target, name=target.__name__, args=args, kwargs=kwargs | ||
| ) | ||
|
|
@@ -1102,11 +1116,16 @@ def _start_thread(self, thread): | |
| thread_started = False | ||
| while not self.is_timeout(): | ||
| try: | ||
| if self.semaphore is not None: | ||
| log.info("({}) Waiting for an available thread slot.".format(thread.name)) | ||
| self.semaphore.acquire() | ||
| log.info("({}) Starting thread.".format(thread.name)) | ||
| thread.start() | ||
| thread_started = True | ||
| break | ||
| except RuntimeError as e: | ||
| if self.semaphore is not None: | ||
| self.semaphore.release() | ||
| if str(e) != "can't start new thread": | ||
| log.error("({}) Unexpected error to start a thread.".format(thread.name), exc_info=True) | ||
| break | ||
|
|
@@ -1121,6 +1140,8 @@ def _start_thread(self, thread): | |
| time_elapsed += queue_interval | ||
| continue | ||
| except Exception: | ||
| if self.semaphore is not None: | ||
| self.semaphore.release() | ||
| log.error("({}) Unexpected error to start a thread.".format(thread.name), exc_info=True) | ||
| break | ||
|
|
||
|
|
@@ -1493,11 +1514,14 @@ def get_row(widths, values, spad=" ", lpad=""): | |
|
|
||
| def prints(objects, sep=' ', end='\n'): | ||
| with open(RESULT_FILE, 'a') as f: | ||
| print(objects, sep=sep, end=end, file=sys.stdout) | ||
| try: | ||
| print(objects, sep=sep, end=end, file=sys.stdout) | ||
| sys.stdout.flush() | ||
| except OSError: | ||
| pass | ||
| if end == "\r": | ||
| end = "\n" # easier to read with \n in a log file | ||
| print(objects, sep=sep, end=end, file=f) | ||
| sys.stdout.flush() | ||
| f.flush() | ||
|
|
||
|
|
||
|
|
@@ -4748,7 +4772,13 @@ def fabricPathEp_target_check(**kwargs): | |
| elif int(third) > 16: | ||
| data.append([dn, "eth port {} is invalid (1-16 expected) for breakout ports".format(third)]) | ||
| else: | ||
| data.append([dn, "PathEp 'eth' syntax is invalid"]) | ||
| # CHECK eth1//0 malform scenario (double slashes) | ||
| if "//" in path: | ||
| data.append([dn, "PathEp 'eth' syntax is invalid"]) | ||
| # CHECK Ethx/y malform scenario (should not be caps) | ||
| elif path.startswith("Eth"): | ||
| data.append([dn, "PathEp 'eth' should be lowercase 'eth'"]) | ||
|
|
||
| else: | ||
| data.append([dn, "target is not a valid fabricPathEp DN"]) | ||
|
|
||
|
|
@@ -6026,6 +6056,62 @@ def apic_downgrade_compat_warning_check(cversion, tversion, **kwargs): | |
| return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) | ||
|
|
||
|
|
||
| @check_wrapper(check_title='Shared Services Providers with Preferred Group enabled') | ||
| def pg_and_shared_svc_contract_check(cversion, tversion, **kwargs): | ||
| result= PASS | ||
| headers = ["Shared Service Contract", "Provider in Preferred Group", "PcTag"] | ||
| data = [] | ||
| recommended_action = 'an EPG in a Contract Preferred Group can consume a shared service contract, but cannot be a provider for a shared service contract.' | ||
| doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#preferred_group_shared_service_provider' | ||
|
|
||
| if not tversion: | ||
| return Result(result=MANUAL, msg=TVER_MISSING) | ||
| # Configuration becomes faulted after 4.2-6d and 5.1-1h | ||
| if tversion.older_than("4.2(6d)"): | ||
| return Result(result=NA) | ||
| elif tversion.older_than("5.1(1h)"): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Gate these thresholds by release train. With the current sequential comparisons, every 4.2 target from 4.2(6d) onward passes the first check but is still older than 5.1(1h), so this returns N/A for an affected upgrade. I reproduced that behavior with target 4.2(6d). Please model the 4.2(6d)+ and 5.1(1h)+ branches explicitly and add boundary tests for affected 4.2 releases. |
||
| return Result(result=NA) | ||
| shrd_contracts_api = 'vzBrCP.json' | ||
| shrd_contracts_api += '?query-target-filter=and(eq(vzBrCP.scope,"global"))' | ||
| shrd_contracts = icurl('class', shrd_contracts_api) | ||
| if not shrd_contracts: | ||
| return Result(result=NA) | ||
| list_of_shrd_contracts =[] | ||
| for shrd_contract in shrd_contracts: | ||
| list_of_shrd_contracts.append(shrd_contract["vzBrCP"]["attributes"]["dn"]) | ||
| # Configuration only becomes faulted for extEPGs after 6.0(1g), normal epgs permitted. | ||
| if tversion.older_than("6.0(1g)"): | ||
| glbl_epgs_api = 'fvAEPg.json' | ||
| glbl_epgs_api += '?query-target-filter=and(le(fvAEPg.pcTag,"16385"),ge(fvAEPg.pcTag,"16"),eq(fvAEPg.prefGrMemb,"include"))' | ||
| glbl_epgs_api += '&rsp-subtree=children&rsp-subtree-class=fvRsProv' | ||
| glbl_epgs = icurl('class', glbl_epgs_api) | ||
| if glbl_epgs: | ||
| for glbl_epg in glbl_epgs: | ||
| for prov_contract in glbl_epg["fvAEPg"]["children"]: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Treat |
||
| if prov_contract["fvRsProv"]["attributes"]["tDn"] in list_of_shrd_contracts: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Correlating only the provider with a global-scope contract is broader than the documented defect condition. CSCvm63145 and the new documentation require an L3Out EPG consumer in another VRF, but this condition flags any preferred-group provider of a global contract, including contracts without an L3Out consumer. Please query/correlate the consumer relation before reporting FAIL_O and add PASS cases for no consumer or a non-L3Out consumer. |
||
| contract = prov_contract["fvRsProv"]["attributes"]["tDn"] | ||
| pctag = glbl_epg["fvAEPg"]["attributes"]["pcTag"] | ||
| provider = glbl_epg["fvAEPg"]["attributes"]["dn"] | ||
| data.append([contract, provider, pctag]) | ||
| # Regardless of version, check extEPGs | ||
| glbl_ext_epgs_api = 'l3extInstP.json' | ||
| glbl_ext_epgs_api += '?query-target-filter=and(le(l3extInstP.pcTag,"16385"),ge(l3extInstP.pcTag,"16"),eq(l3extInstP.prefGrMemb,"include"))' | ||
| glbl_ext_epgs_api += '&rsp-subtree=children&rsp-subtree-class=fvRsProv' | ||
| glbl_ext_epgs = icurl('class', glbl_ext_epgs_api) | ||
| if glbl_ext_epgs: | ||
| for glbl_ext_epg in glbl_ext_epgs: | ||
| for prov_ext_contract in glbl_ext_epg["l3extInstP"]["children"]: | ||
| if prov_ext_contract["fvRsProv"]["attributes"]["tDn"] in list_of_shrd_contracts: | ||
| contract = prov_ext_contract["fvRsProv"]["attributes"]["tDn"] | ||
| pctag = glbl_ext_epg["l3extInstP"]["attributes"]["pcTag"] | ||
| provider = glbl_ext_epg["l3extInstP"]["attributes"]["dn"] | ||
| data.append([contract, provider, pctag]) | ||
|
|
||
| if data: | ||
| result = FAIL_O | ||
|
|
||
| return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) | ||
|
|
||
| # ---- Script Execution ---- | ||
|
|
||
|
|
||
|
|
@@ -6039,6 +6125,7 @@ def parse_args(args): | |
| parser.add_argument("-v", "--version", action="store_true", help="Only show the script version, then end.") | ||
| parser.add_argument("--total-checks", action="store_true", help="Only show the total number of checks, then end.") | ||
| parser.add_argument("--timeout", action="store", nargs="?", type=int, const=-1, default=DEFAULT_TIMEOUT, help="Show default script timeout (sec) or overwrite it when a number is provided (e.g. --timeout 1200).") | ||
| parser.add_argument("--max-threads", action="store", type=int, default=None, help="Maximum number of check threads to run concurrently. Defaults to unlimited.") | ||
| parsed_args = parser.parse_args(args) | ||
| return parsed_args | ||
|
|
||
|
|
@@ -6161,6 +6248,7 @@ class CheckManager: | |
| service_bd_forceful_routing_check, | ||
| ave_eol_check, | ||
| consumer_vzany_shared_services_check, | ||
| pg_and_shared_svc_contract_check, | ||
|
|
||
| # Bugs | ||
| ep_announce_check, | ||
|
|
@@ -6209,11 +6297,12 @@ class CheckManager: | |
| apic_ca_cert_validation, | ||
| ] | ||
|
|
||
| def __init__(self, api_only=False, debug_function="", timeout=600, monitor_interval=0.5): | ||
| def __init__(self, api_only=False, debug_function="", timeout=600, monitor_interval=0.5, max_threads=None): | ||
| self.api_only = api_only | ||
| self.debug_function = debug_function | ||
| self.monitor_interval = monitor_interval # sec | ||
| self.monitor_timeout = timeout # sec | ||
| self.max_threads = max_threads | ||
| self.timeout_event = None | ||
|
|
||
| self.check_funcs = self.get_check_funcs() | ||
|
|
@@ -6284,6 +6373,7 @@ def run_checks(self, common_data): | |
| common_kwargs=dict({"finalize_check": self.finalize_check}, **common_data), | ||
| monitor_interval=self.monitor_interval, | ||
| monitor_timeout=self.monitor_timeout, | ||
| max_threads=self.max_threads, | ||
| callback_on_monitoring=print_progress, | ||
| callback_on_start_failure=self.finalize_check_on_thread_failure, | ||
| callback_on_timeout=self.finalize_check_on_thread_timeout, | ||
|
|
@@ -6303,7 +6393,7 @@ def main(_args=None): | |
| print("Timeout(sec): {}".format(DEFAULT_TIMEOUT)) | ||
| return | ||
|
|
||
| cm = CheckManager(args.api_only, args.debug_function, args.timeout) | ||
| cm = CheckManager(args.api_only, args.debug_function, args.timeout, max_threads=args.max_threads) | ||
|
|
||
| if args.total_checks: | ||
| print("Total Number of Checks: {}".format(cm.total_checks)) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] This recommendation restates the restriction but does not tell an operator how to make the upgrade safe. Please provide an actionable remediation, such as removing the EPG from the preferred group or stopping it from providing the shared-service contract, and verify the recommendation in the tests.