def decorator(subscriber: Callable) -> Callable: nonlocal source nonlocal identifier nonlocal constraints if identifier is None: identifier = source.__name__ if identifier == 'constraints': errmsg = "Invalid identifier. The word 'constraints' is reseved for delivering dynamic constraints." raise AKitSemanticError(errmsg) from None life_span = ResourceLifespan.Test source_info = resource_registry.lookup_resource_source(source) if constraints is not None and 'constraints' not in source_info.source_signature.parameters: raise AKitSemanticError( "Attempting to pass constraints to a parameter origin with no 'constraints' parameter." ) from None assigned_scope = "{}#{}".format(subscriber.__module__, subscriber.__name__) param_origin = ParameterOrigin(assigned_scope, identifier, source_info, life_span, constraints) resource_registry.register_parameter_origin(identifier, param_origin) return subscriber
def release_write(self): """ Method called by a thread to release write access on the :class:`ReadWriteLock`. """ tid = threading.get_ident() self._lock.acquire() try: if self._writer != tid: raise AKitSemanticError( "Thread id(%d) attempting to release write lock when it was not owned." % tid) from None if self._read_write_pivot >= 0: raise AKitSemanticError( "Thread id(%d) is attempting to release the ReadWriteLock when it is in a read or neutral state." % tid) from None self._read_write_pivot += 1 # Make the decision to allow readers before we let any waiting writers change # the count of self._writers_waiting if self._writers_waiting == 0: self._read_gate.set() # Don't release the write gate until we have checked to see if another writer is waiting self._write_gate.release() finally: self._lock.release() return
def lookup_test_root_type(test_root): test_root_module = os.path.join(test_root, os.path.join("__testroot__.py")) if not os.path.exists(test_root_module): errmsg = "The test root must have a module '__testroot__.py'. testroot={}".format( test_root_module) raise AKitSemanticError(errmsg) from None ROOT_TYPE = None trm_content = "" with open(test_root_module, 'r') as trmf: trm_content = trmf.read().strip() locals_vars = {} exec(trm_content, None, locals_vars) if "ROOT_TYPE" in locals_vars: ROOT_TYPE = locals_vars["ROOT_TYPE"] if ROOT_TYPE is None: errmsg = "The test root module must have a 'ROOT_TYPE' variable specifying (testplus, unittest).".format( test_root_module) raise AKitSemanticError(errmsg) from None if not ((ROOT_TYPE == TestRootType.TESTPLUS) or (ROOT_TYPE == TestRootType.UNITTEST)): errmsg = "Unknow test root type {}".format(ROOT_TYPE) raise AKitSemanticError(errmsg) from None return ROOT_TYPE
def activate_integration_point(self, role: str, coordinator_constructor: callable): """ This method should be called from the attach_to_environment methods from individual couplings in order to register the base level integrations. Integrations can be hierarchical so it is only necessary to register the root level integration couplings, the descendant couplings can be called from the root level couplings. :param role: The name of a role to assign for a coupling. :param coupling: The coupling to register for the associated role. """ if role.startswith("coordinator/"): if "coordinator/serial" not in self._integration_points_activated: self._integration_points_activated["coordinator/serial"] = True if "coordinator/power" not in self._integration_points_activated: self._integration_points_activated["coordinator/power"] = True _, coord_type = role.split("/") if coord_type == "upnp" or coord_type == "ssh": if role not in self._integration_points_activated: self._integration_points_activated[role] = coordinator_constructor else: raise AKitSemanticError("Attempted to activate the UPNP coordinator twice.") from None else: raise AKitSemanticError("Unknown coordinator type '%s'." % role) from None else: raise AKitSemanticError("Don't know how to activate integration point of type '%s'." % role) from None return
def __init__(self, patterns: List[Union[str, re.Pattern]], *, match_type: str = None, destination: str = None, required: bool = False, repeats: bool = False, strict: Optional[bool] = None, consume: bool = False): if match_type is not None and destination is None: errmsg = "RegExPattern: If 'match_type' is specified then you must provide a 'destination' parameter." raise AKitSemanticError(errmsg) self.destination = destination self.patterns = None if len(self.patterns) == 0: errmsg = "The RegExMultiPattern object requires at least one pattern to be passed." raise AKitSemanticError(errmsg) else: self.patterns = [ p if isinstance(p, re.Pattern) else re.compile(p) for p in patterns ] self.match_type = match_type self.required = required self.repeats = repeats self.strict = strict self.consume = consume return
def has_any_feature(self, feature_list: List[FeatureTag]): has_any = False if len(feature_list) == 0: errmsg = "has_all_features: 'feature_list' cannot be empty." raise AKitSemanticError(errmsg) first_item = feature_list[0] if isinstance(first_item, str): for feature in feature_list: fid = feature hasfeature = fid in self._feature_tags if hasfeature: has_any = True break elif issubclass(first_item, FeatureTag): for feature in feature_list: fid = feature.ID hasfeature = fid in self._feature_tags if hasfeature: has_any = True break else: errmsg = "The 'feature_list' parameter must contain items of type 'FeatureTag' or 'str'. item={}".format( repr(first_item) ) raise AKitSemanticError(errmsg) return has_any
def __init__(self, pattern: Union[str, re.Pattern], *, match_type: str = None, destination: str = None, required: bool = False, repeats: bool = False, strict: Optional[bool] = None, consume: bool = False): if match_type is not None and destination is None: errmsg = "RegExPattern: If 'match_type' is specified then you must provide a 'destination' parameter." raise AKitSemanticError(errmsg) if repeats and match_type is None: errmsg = "RegExPattern: If 'repeats=True', you must specify an 'match_type' parameter." raise AKitSemanticError(errmsg) self.pattern = pattern if isinstance(pattern, str): self.pattern = re.compile(pattern) self.match_type = match_type self.destination = destination self.required = required self.repeats = repeats self.strict = strict self.consume = consume return
def aggregate_results(self) -> Tuple[Dict, Dict]: """ """ if not self._started: errmsg = "{}: You must call the 'begin' method to start the evolution and wait for the " \ "evolution to complete before calling the 'aggregate_results' method." raise AKitSemanticError(errmsg) if not self._completed: errmsg = "{}: You must call the 'wait_for_completion' to wait for the " \ "evolution to complete before calling the 'aggregate_results' method." raise AKitSemanticError(errmsg) rtn_results = {} rtn_exceptions = {} for pident, pfuture in self._procedure_futures.items(): try: fresult = pfuture.result() rtn_results[pident] = fresult except (CancelledError, TimeoutError) as ferr: rtn_exceptions[pident] = ferr except: fexc = pfuture.exception() rtn_exceptions[pident] = fexc return rtn_results, rtn_exceptions
def originate_parameter(source_func, *, identifier: Optional[None], life_span: ResourceLifespan = None, assigned_scope: Optional[str] = None, constraints: Optional[Dict] = None): if source_func is None: errmsg = "The 'source_func' parameter cannot be 'None'." raise AKitSemanticError(errmsg) from None if life_span == ResourceLifespan.Test: errmsg = "The 'life_span' parameter cannot be 'ResourceLifespan.Test'." raise AKitSemanticError(errmsg) from None if identifier is None: identifier = source_func.__name__ if identifier == 'constraints': errmsg = "Invalid identifier. The word 'constraints' is reseved for delivering dynamic constraints." raise AKitSemanticError(errmsg) from None source_info = resource_registry.lookup_resource_source(source_func) if assigned_scope is not None: if isinstance(source_info, IntegrationSource): errmsg = "The 'assigned_scope' parameter should not be specified unless the source of the resource is of type 'scope' or 'resource'." raise AKitSemanticError(errmsg) from None if constraints is not None and 'constraints' not in source_info.source_signature.parameters: raise AKitSemanticError( "Attempting to pass constraints to a parameter origin with no 'constraints' parameter." ) from None caller_frame = inspect.stack()[1] calling_module = inspect.getmodule(caller_frame[0]) if life_span is None: res_type = source_info.resource_type if issubclass(res_type, IntegrationCoupling): life_span = ResourceLifespan.Session else: life_span = ResourceLifespan.Package if life_span == ResourceLifespan.Package: if assigned_scope is None: assigned_scope = calling_module.__name__ elif life_span == ResourceLifespan.Session: assigned_scope = "<session>" param_origin = ParameterOrigin(assigned_scope, identifier, source_info, life_span, constraints) resource_registry.register_parameter_origin(identifier, param_origin) return
def register_integration_point(self, role: str, coupling: BaseCoupling): """ This method should be called from the attach_to_environment methods from individual couplings in order to register the base level integrations. Integrations can be hierarchical so it is only necessary to register the root level integration couplings, the descendant couplings can be called from the root level couplings. :param role: The name of a role to assign for a coupling. :param coupling: The coupling to register for the associated role. """ lscapeType = type(self) lscapeType.landscape_lock.acquire() try: if role not in self._integration_points_registered: self._ordered_roles.append(role) self._integration_points_registered[role] = coupling self._integration_point_registration_counter += 1 else: raise AKitSemanticError( "A coupling with the role %r was already registered." % role) from None finally: lscapeType.landscape_lock.release() return
def add_descendent_parameter_origination( self, assigned_scope: str, parameter_origin: ParameterOrigin): """ Adds a descendent parameter origin object into the parameter origin table of the assigned scope path provided. :param assigned_scope: The full scope name of the scope to assign the parameter origin to. :param parameter_origin: The parameter origin object that is being inserted into the parameter origin table of the assigned scope. """ if self._package is not None: err_msg = "The 'add_descendent' API can only be called on the root package." raise AKitSemanticError(err_msg) from None if self._package is None and assigned_scope == "<session>": if parameter_origin is not None: identifier = parameter_origin.identifier self._parameter_originations[identifier] = parameter_origin self._parameter_originations.move_to_end(identifier, last=True) else: is_test_scope = False if assigned_scope.find("#") > -1: is_test_scope = True assigned_scope = assigned_scope.replace("#", ".") to_walk_list = assigned_scope.split(".") path_stack = [] self._add_descendent_parameter_origination(parameter_origin, to_walk_list, path_stack, is_test_scope) return
def extend_features(self, features_to_add: Union[List[FeatureTag], List[str]]): """ Used by derived class and mixins to extend the feature tags associated with a feature attached object. """ if len(features_to_add) > 0: first_item = features_to_add[0] if isinstance(first_item, str): # We insert the features into the list sorted so we can make finding # features faster. for ft in features_to_add: bisect.insort(self._feature_tags, ft) elif issubclass(first_item, FeatureTag): # We insert the features into the list sorted so we can make finding # features faster. for ft in features_to_add: bisect.insort(self._feature_tags, ft.ID) else: errmsg = "The 'features_to_add' parameter must contain items of type 'FeatureTag' or 'str'. item={}".format( repr(first_item) ) raise AKitSemanticError(errmsg) return
def establish_connectivity( cls, allow_missing_devices: bool = False) -> Tuple[List[str], dict]: """ This API is called so the `IntegrationCoupling` can establish connectivity with any compute or storage resources. :returns: A tuple with a list of error messages for failed connections and dict of connectivity reports for devices devices based on the coordinator. """ ssh_device_list = cls.landscape.get_ssh_device_list() if len(ssh_device_list) == 0: raise AKitSemanticError( "We should have not been called if no SSH devices are available." ) upnp_coord = cls.landscape._internal_get_upnp_coord() ssh_config_errors, matching_device_results, missing_device_results = cls.coordinator.attach_to_devices( ssh_device_list, upnp_coord=upnp_coord) ssh_scan_results = { "ssh": { "matching_devices": matching_device_results, "missing_devices": missing_device_results } } return (ssh_config_errors, ssh_scan_results)
def _populate_embedded_devices(self, factory, description): """ This method is overloaded to prohibit the poplulation of embedded devices insided embedded devices. """ # pylint: disable=no-self-use,unused-argument raise AKitSemanticError( "Embedded devices inside an embedded device is currently not supported." ) from None
def decorator(source_function: Callable) -> Callable: nonlocal constraints signature = inspect.signature(source_function) integration_context = signature.return_annotation resource_type = None if integration_context == inspect._empty: errmsg = "Parameters factories for 'integration' functions must have an annotated return type." raise AKitSemanticError(errmsg) from None else: if integration_context._name == "Generator": ra_yield_type, ra_send_type, ra_return_type = integration_context.__args__ if ra_yield_type is not NoneType: resource_type = ra_yield_type elif ra_return_type is not NoneType: resource_type = ra_return_type elif issubclass(integration_context, IntegrationCoupling): raise AKitSemanticError( "Your resource function is returning an integration instead of yielding one." ) from None else: raise AKitSemanticError( "You must pass a IntegrationCoupling derived object." ) from None if resource_type is not None: if not issubclass(resource_type, IntegrationCoupling): raise AKitSemanticError( "The 'integration' decorator can only be used on resources that inherit from the 'IntegrationCoupling'." ) from None isource = IntegrationSource(source_function, resource_type, constraints) resource_registry.register_integration_source(isource) else: errmsg_lines = [ "Unable to determine the resource type of the function which the 'integration' decorator was applied too.", "FUNCTION: {}".format(signature) ] errmsg = os.linesep.join(errmsg_lines) raise AKitSemanticError(errmsg) from None return source_function
def wait_for_completion(self, timeout: Optional[float]=None): if not self._started: errmsg = "{}: You must call the 'begin' method to start the evolution before calling the 'wait_for_completion' method." raise AKitSemanticError(errmsg) self._completed_gate.wait(timeout=timeout) return
def _register_root_device(self, extkey, extcls, device_table): """ Method that registers the extension class of a specific type of root device. """ if extkey not in device_table: device_table[extkey] = extcls else: raise AKitSemanticError( "A root device extension with the key=%r was already registered. (%s)" % (extkey, extcls)) from None return
def establish_connectivity( cls, allow_missing_devices: bool = False, upnp_recording: bool = False, allow_unknown_devices: bool = False) -> Tuple[List[str], dict]: """ This API is called so the `IntegrationCoupling` can establish connectivity with any compute or storage resources. :returns: A tuple with a list of error messages for failed connections and dict of connectivity reports for devices devices based on the coordinator. """ lscape = cls.landscape exclude_interfaces = ["lo"] networking_info = lscape.networking if networking_info is not None and "upnp" in networking_info: upnp_info = networking_info["upnp"] if "exclude_interfaces" in upnp_info: exclude_interfaces = upnp_info["exclude_interfaces"] upnp_hint_table = lscape.get_upnp_device_config_lookup_table() if len(upnp_hint_table) == 0: raise AKitSemanticError( "we should not have been called if the upnp device config had 0 devices." ) from None required_devices = None if not allow_missing_devices: required_devices = [usn for usn in upnp_hint_table.keys()] found_device_results, matching_device_results, missing_device_results = cls.coordinator.startup_scan( upnp_hint_table, watchlist=upnp_hint_table, required_devices=required_devices, exclude_interfaces=exclude_interfaces, upnp_recording=upnp_recording, allow_unknown_devices=allow_unknown_devices) conn_results = { "upnp": { "found": found_device_results, "matching": matching_device_results, "missing": missing_device_results } } conn_errors = [] return (conn_errors, conn_results)
def _locked_checkout_device(self, device) -> Optional[LandscapeDevice]: rtn_device = None keyid = device.keyid if keyid not in self._device_pool: raise AKitSemanticError("A device is being checked out, that is not in the device pool.") from None rtn_device = self._device_pool[keyid] del self._device_pool[keyid] self._checked_out_devices[keyid] = rtn_device return rtn_device
def add_descendent(self, test_ref: TestRef): if self._package is not None: err_msg = "The 'add_descendent' API can only be called on the root package." raise AKitSemanticError(err_msg) from None testname = test_ref.test_name module_name, _ = testname.split("#") to_walk_list = module_name.split(".") path_stack = [] self._add_descendent(test_ref, to_walk_list, path_stack) return
def decorator(source_function: Callable) -> Callable: nonlocal constraints signature = inspect.signature(source_function) resource_context = signature.return_annotation resource_type = None if resource_context == inspect._empty: errmsg = "Parameters factories or 'resource' functions must have an annotated return type." raise AKitSemanticError(errmsg) from None elif hasattr(resource_context, "_name") and resource_context._name == "Generator": ra_yield_type, ra_send_type, ra_return_type = resource_context.__args__ if ra_yield_type is not NoneType: resource_type = ra_yield_type elif ra_return_type is not NoneType: resource_type = ra_return_type elif issubclass(resource_context, IntegrationCoupling): resource_type = resource_context else: resource_type = resource_context if resource_type is not None: sref = ResourceSource(source_function, query_function, resource_type, constraints) resource_registry.register_resource_source(sref) else: errmsg_lines = [ "Unable to determine the resource type of the function which the 'resource' decorator was applied too.", "FUNCTION: {}".format(signature) ] errmsg = os.linesep.join(errmsg_lines) raise AKitSemanticError(errmsg) from None return source_function
def release_read(self): """ Method called by a thread to release read access on the :class:`ReadWriteLock`. """ tid = threading.get_ident() self._lock.acquire() try: if tid not in self._readers: raise AKitSemanticError( "Thread id(%d) attempting to release read lock when it was not owned." % tid) from None if self._read_write_pivot <= 0: raise AKitSemanticError( "Thread id(%d) is attempting to release the ReadWriteLock when it is in a write or neutral state." % tid) from None self._read_write_pivot -= 1 finally: self._lock.release() return
def has_feature(self, feature: Union[FeatureTag, str]): fid = None if isinstance(feature, str): fid = feature elif issubclass(feature, FeatureTag): fid = feature.ID else: errmsg = "The 'feature' parameter must be of type 'FeatureTag' or 'str'. item={}".format( repr(feature) ) raise AKitSemanticError(errmsg) hasfeature = fid in self._feature_tags return hasfeature
def _ensure_activation(self): """ Called by methods that require Landscape activation in order to make sure the 'activate' method has been called before the attempted use of the specified method. :param method: The name of the method guarding against the use of a Landscape that has not been activated. """ if self._operational_gate is not None: self._operational_gate.wait() else: curframe = inspect.currentframe() calframe = inspect.getouterframes(curframe, 2) guarded_method = calframe[1][3] errmsg = "The Landscape must be activated before calling the '%s' method." % guarded_method raise AKitSemanticError(errmsg) from None return
def register_monitor(self, monitor: ReportMonitor): """ """ monitor_name = "{}:{}".format(monitor.report_class, monitor.report_topic) self._lock.acquire() try: if monitor_name in self._monitor_table: errmsg = "A 'ReportMonitor' named '{}' has already been registered.".format( monitor_name) raise AKitSemanticError(errmsg) from None self._monitor_table[monitor_name] = monitor corr_ip = monitor.correspondance_ip monitor.set_report_endpoint(corr_ip, self._port) finally: self._lock.release() return
def register_parameter_origin(self, identifier: str, origin: ParameterOrigin): """ The :method:`register_parameter_origin` is used to register an alias as a wellknown parameter so that subscriber functions can consume the parameter as an input parameter. """ originating_scope = origin.originating_scope if self._scope_tree_root.has_descendent_parameter( originating_scope, identifier): errmsg = "A wellknown variable identified as '{}' has already been assigned to scope '{}'.".format( identifier, originating_scope) raise AKitSemanticError(errmsg) from None # Add the parameter origin to the identifiers_for_scope table for this scope so we # can lookup identifiers by scope self._scope_tree_root.add_descendent_parameter_origination( originating_scope, origin) return
def checkin_device(self, device: LandscapeDevice): """ Returns a landscape device to the the available device pool. """ self._ensure_activation() keyid = device.keyid if keyid not in self._checked_out_devices: errmsg = "Attempting to checkin a device that is not checked out. {}".format( device) raise AKitSemanticError(errmsg) self.landscape_lock.acquire() try: self._device_pool[keyid] = device del self._checked_out_devices[keyid] finally: self.landscape_lock.release() return
def checkin_multiple_devices(self, devices: List[LandscapeDevice]): """ Returns a landscape device to the the available device pool. """ self._ensure_activation() checkin_errors = [] self.landscape_lock.acquire() try: for dev in devices: keyid = dev.keyid if keyid not in self._checked_out_devices: self._device_pool[keyid] = dev checkin_errors.append(dev) if len(checkin_errors) > 0: err_msg_lines = [ "Attempting to checkin a device that is not checked out.", "DEVICES:" ] for dev in checkin_errors: err_msg_lines.append(" {}".format(dev)) err_msg = os.linesep.join(err_msg_lines) raise AKitSemanticError(err_msg) for dev in devices: keyid = dev.keyid if keyid in self._checked_out_devices: self._device_pool[keyid] = dev del self._checked_out_devices[keyid] finally: self.landscape_lock.release() return
def execute_tests(self, runid: str, recorder): """ Called in order to execute the tests contained in the :class:`TestPacks` being run. """ res_name = "<session>" self._recorder = recorder self._root_result = self.create_job_result_container(runid, res_name) recorder.record(self._root_result) if self._sequence_document is None: errmsg = "The 'execute_tests' method should not be called without first generating the test sequence document." raise AKitSemanticError(errmsg) from None # Import the test sequence document sequence_mod = import_file("sequence_document", self._sequence_document) sequence_mod.session(self) return
def _process_content(self): pattern_queue = [p for p in self.EXPECTED_LINES] content_queue = [cl for cl in self._content] if len(pattern_queue) == 0: errmsg = "The 'EXPECTED_LINES' list must be a list with at least one 'RegExPattern' or 'RegExMultiPattern' object." raise AKitSemanticError(errmsg) current_pattern = None current_line = None strict = None while len(content_queue) > 0: current_line = content_queue.pop(0) if self.CONSUME_WHITESPACE: while current_line.strip() == "" and len(content_queue) > 0: current_line = content_queue.pop(0) if current_line is None: break # If pattern was set to None then we either found a match and # the pattern that was matched was not a repeating patter or # it was a repeating pattern but the repeating condition was # terminated due to mis-match or match for a following pattern. if current_pattern is None: if len(pattern_queue) == 0: break current_pattern = pattern_queue.pop(0) strict = self._strict if current_pattern.strict is not None: strict = current_pattern.strict matches = None if isinstance(current_pattern, RegExPattern): matches = self._process_pattern_simple(current_pattern, current_line, strict) if matches is not None: if not current_pattern.consume: if current_pattern.match_type is None: self._process_name_based_match( current_pattern, matches) else: self._process_match_type_based_match( current_pattern, matches) elif isinstance(current_pattern, RegExMultiPattern): matches = self._process_pattern_multimatch( current_pattern, content_queue, strict) if matches is not None: if not current_pattern.consume: if current_pattern.match_type is None: self._process_name_based_match( current_pattern, matches) else: self._process_match_type_based_match( current_pattern, matches) else: errmsg = "The 'EXPECTED_LINES' list must be a list of 'RegExPattern' or 'RegExMultiPattern' objects." raise AKitSemanticError(errmsg) if matches is not None: if not current_pattern.repeats: current_pattern = None elif len(pattern_queue) > 0 and len(content_queue) > 0: # If we are processing a repeating pattern, we need to see if the next # pattern is going to match and result in a termination of the current # repeating pattern. preview_pattern = pattern_queue[0] preview_line = self._get_preview_line(content_queue) if preview_line is not None: # Perform a preview operation, if the next pattern is a match for the # next line then clear the current pattern pmobj = None if isinstance(preview_pattern, RegExPattern): pmobj = preview_pattern.pattern.match(preview_line) elif isinstance(preview_pattern, RegExMultiPattern): pmobj = preview_pattern.patterns[0].match( preview_line) if pmobj is not None: current_pattern = None else: current_pattern = None if len(self._pattern_misses) > 0 or len(pattern_queue) > 0: # We shouldn't have pattern misses, if we have a miss, let the # reader register bugs. self._pattern_misses.extend(pattern_queue) self._register_pattern_match_bugs() return