def prepare_options_with_set(cib, options, resource_set_list): options = constraint.prepare_options( tuple(ATTRIB.keys()), options, create_id_fn=partial(constraint.create_id, cib, "ticket", resource_set_list), validate_id=partial(tools.check_new_id_applicable, cib, DESCRIPTION), ) report_list = _validate_options_common(options) if "ticket" not in options or not options["ticket"].strip(): report_list.append( ReportItem.error( reports.messages.RequiredOptionsAreMissing(["ticket"]))) if report_list: raise LibraryError(*report_list) return options
def _process_response(self, response): report = response_to_report_item(response) if report is None: self._online_target_list.append(response.request.target) return if not response.was_connected: report = ( ReportItem.warning( reports.messages.OmittingNode(response.request.target.label) ) if self._ignore_offline_targets else response_to_report_item( response, forceable=report_codes.SKIP_OFFLINE_NODES ) ) self._report(report)
def _validate_level(level) -> Tuple[ReportItemList, Optional[int]]: report_list: ReportItemList = [] try: candidate = int(level) if candidate > 0: return report_list, candidate except ValueError: pass report_list.append( ReportItem.error( reports.messages.InvalidOptionValue( "level", level, "a positive integer" ) ) ) return report_list, None
def _log_response_successful(self, response): url = response.request.url msg = ( "Finished calling: {url}\nResponse Code: {code}" + "\n--Debug Response Start--\n{response}\n--Debug Response End--") self._logger.debug( msg.format(url=url, code=response.response_code, response=response.data)) self._reporter.report( ReportItem.debug( reports.messages.NodeCommunicationFinished( url, response.response_code, response.data, )))
def from_string(cls, config_string): """ Parse corosync config and create a facade around it config_string corosync config text """ try: return cls(config_parser.parse_string(config_string)) except config_parser.MissingClosingBraceException as e: raise LibraryError( ReportItem.error( reports.messages.ParseErrorCorosyncConfMissingClosingBrace( ))) from e except config_parser.UnexpectedClosingBraceException as e: # pylint: disable=line-too-long raise LibraryError( ReportItem.error( reports.messages. ParseErrorCorosyncConfUnexpectedClosingBrace())) from e except config_parser.MissingSectionNameBeforeOpeningBraceException as e: # pylint: disable=line-too-long raise LibraryError( ReportItem.error( reports.messages. ParseErrorCorosyncConfMissingSectionNameBeforeOpeningBrace( ))) from e except config_parser.ExtraCharactersAfterOpeningBraceException as e: # pylint: disable=line-too-long raise LibraryError( ReportItem.error( reports.messages. ParseErrorCorosyncConfExtraCharactersAfterOpeningBrace()) ) from e except config_parser.ExtraCharactersBeforeOrAfterClosingBraceException as e: # pylint: disable=line-too-long raise LibraryError( ReportItem.error( reports.messages. ParseErrorCorosyncConfExtraCharactersBeforeOrAfterClosingBrace( ))) from e except config_parser.LineIsNotSectionNorKeyValueException as e: # pylint: disable=line-too-long raise LibraryError( ReportItem.error( reports. messages.ParseErrorCorosyncConfLineIsNotSectionNorKeyValue( ))) from e except config_parser.CorosyncConfParserException as e: raise LibraryError( ReportItem.error( reports.messages.ParseErrorCorosyncConf())) from e
def _log_debug(self, response): url = response.request.url debug_data = response.debug self._logger.debug( ( "Communication debug info for calling: {url}\n" "--Debug Communication Info Start--\n" "{data}\n" "--Debug Communication Info End--" ).format(url=url, data=debug_data) ) self._reporter.report( ReportItem.debug( reports.messages.NodeCommunicationDebugInfo(url, debug_data) ) )
def get_available_watchdogs(cmd_runner): regex = (r"\[\d+\] (?P<watchdog>.+)$\n" r"Identity: (?P<identity>.+)$\n" r"Driver: (?P<driver>.+)$" r"(\nCAUTION: (?P<caution>.+)$)?") std_out, std_err, ret_val = cmd_runner.run( [settings.sbd_binary, "query-watchdog"]) if ret_val != 0: raise LibraryError( ReportItem.error(reports.messages.SbdListWatchdogError(std_err))) return { match.group("watchdog"): {key: match.group(key) for key in ["identity", "driver", "caution"]} for match in re.finditer(regex, std_out, re.MULTILINE) }
def _validate_target_typewise(target_type) -> ReportItemList: report_list: ReportItemList = [] if target_type not in [ TARGET_TYPE_NODE, TARGET_TYPE_ATTRIBUTE, TARGET_TYPE_REGEXP, ]: report_list.append( ReportItem.error( reports.messages.InvalidOptionType( "target", ["node", "regular expression", "attribute_name=value"], ) ) ) return report_list
def create_target(acl_section, target_id): """ Creates new acl_target element with id target_id. Raises LibraryError if target with wpecified id aleready exists. acl_section -- etree node target_id -- id of new target """ # id of element acl_target is not type ID in CIB ACL schema so we don't need # to check if it is unique ID in whole CIB if acl_section.xpath(f"./{TAG_TARGET}[@id=$target_id]", target_id=target_id): raise LibraryError( ReportItem.error( reports.messages.CibAclTargetAlreadyExists(target_id))) return etree.SubElement(acl_section, TAG_TARGET, id=target_id)
def set_message(cmd_runner, device, node_name, message): """ Set message of specified type 'message' on SBD device for node. cmd_runner -- CommandRunner device -- string, device path node_name -- string, nae of node for which message should be set message -- string, message type """ dummy_std_out, std_err, ret_val = cmd_runner.run( [settings.sbd_binary, "-d", device, "message", node_name, message]) if ret_val != 0: raise LibraryError( ReportItem.error( reports.messages.SbdDeviceMessageError(device, node_name, message, std_err)))
def _set_instance_attrs_local_node(lib_env, attrs, wait): if not lib_env.is_cib_live: # If we are not working with a live cluster we cannot get the local node # name. raise LibraryError( ReportItem.error( reports.messages.LiveEnvironmentRequiredForLocalNode())) with cib_runner_nodes(lib_env, wait) as (cib, runner, state_nodes): update_node_instance_attrs( cib, IdProvider(cib), get_local_node_name(runner), attrs, state_nodes=state_nodes, )
def _validate_options_common(options): report_list = [] if "loss-policy" in options: loss_policy = options["loss-policy"].lower() if options["loss-policy"] not in ATTRIB["loss-policy"]: report_list.append( ReportItem.error( reports.messages.InvalidOptionValue( "loss-policy", options["loss-policy"], ATTRIB["loss-policy"], ) ) ) options["loss-policy"] = loss_policy return report_list
def _process_response(self, response): report_item = response_to_report_item(response) if report_item: self._report(report_item) return report_list = [] node_label = response.request.target.label try: data = json.loads(response.data) if not data["sbd"]["installed"]: report_list.append( ReportItem.error( reports.messages.SbdNotInstalled(node_label))) if "watchdog" in data: if data["watchdog"]["exist"]: if not data["watchdog"].get("is_supported", True): report_list.append( ReportItem.error( reports.messages.SbdWatchdogNotSupported( node_label, data["watchdog"]["path"]))) else: report_list.append( ReportItem.error( reports.messages.WatchdogNotFound( node_label, data["watchdog"]["path"]))) for device in data.get("device_list", []): if not device["exist"]: report_list.append( ReportItem.error( reports.messages.SbdDeviceDoesNotExist( device["path"], node_label))) elif not device["block_device"]: report_list.append( ReportItem.error( reports.messages.SbdDeviceIsNotBlockDevice( device["path"], node_label))) # TODO maybe we can check whenever device is initialized by sbd # (by running 'sbd -d <dev> dump;') except (ValueError, KeyError, TypeError): report_list.append( ReportItem.error( reports.messages.InvalidResponseFormat(node_label))) if report_list: self._report_list(report_list) else: self._report( ReportItem.info( reports.messages.SbdCheckSuccess( response.request.target.label)))
def qdevice_destroy(lib_env: LibraryEnvironment, model, proceed_if_used=False): """ Stop and disable qdevice on local host and remove its configuration string model qdevice model to destroy bool procced_if_used destroy qdevice even if it is used by clusters """ _check_model(model) _check_qdevice_not_used( lib_env.report_processor, lib_env.cmd_runner(), model, proceed_if_used ) _service_stop(lib_env, qdevice_net.qdevice_stop) _service_disable(lib_env, qdevice_net.qdevice_disable) qdevice_net.qdevice_destroy() lib_env.report_processor.report( ReportItem.info(reports.messages.QdeviceDestroySuccess(model)) )
def qdevice_setup(lib_env: LibraryEnvironment, model, enable, start): """ Initialize qdevice on local host with specified model string model qdevice model to initialize bool enable make qdevice service start on boot bool start start qdevice now """ _check_model(model) qdevice_net.qdevice_setup(lib_env.cmd_runner()) lib_env.report_processor.report( ReportItem.info(reports.messages.QdeviceInitializationSuccess(model)) ) if enable: _service_enable(lib_env, qdevice_net.qdevice_enable) if start: _service_start(lib_env, qdevice_net.qdevice_start)
def book_ids(self, *id_list: str) -> ReportItemList: """ Check if the ids are not already used and reserve them for future use """ reported_ids = set() report_list = [] for _id in id_list: if _id in reported_ids: continue if _id in self._booked_ids or does_id_exist(self._cib, _id): report_list.append( ReportItem.error(reports.messages.IdAlreadyExists(_id))) reported_ids.add(_id) continue self._booked_ids.add(_id) return report_list
def _service_disable( report_processor: ReportProcessor, service_manager: ServiceManagerInterface, service: str, ) -> None: try: service_manager.disable(service) except ManageServiceError as e: raise LibraryError(service_exception_to_report(e)) from e report_processor.report( ReportItem.info( reports.messages.ServiceActionSucceeded( reports.const.SERVICE_ACTION_DISABLE, "quorum device" ) ) )
def _set_instance_attrs_node_list(lib_env, attrs, node_names, wait): with cib_runner_nodes(lib_env, wait) as (cib, dummy_runner, state_nodes): known_nodes = [node.attrs.name for node in state_nodes] report_list = [] for node in node_names: if node not in known_nodes: report_list.append( ReportItem.error(reports.messages.NodeNotFound(node))) if report_list: raise LibraryError(*report_list) for node in node_names: update_node_instance_attrs(cib, IdProvider(cib), node, attrs, state_nodes=state_nodes)
def is_resource_in_same_group(cib, resource_id_list): # We don't care about not found elements here, that is a job of another # validator. We do not care if the id doesn't belong to a resource either # for the same reason. element_list, _ = get_elements_by_ids(cib, set(resource_id_list)) parent_list = [] for element in element_list: parent = get_parent_resource(element) if parent is not None and group.is_group(parent): parent_list.append(parent) if len(set(parent_list)) != len(parent_list): raise LibraryError( ReportItem.error( reports.messages. CannotSetOrderConstraintsForResourcesInTheSameGroup()))
def client_net_import_certificate(lib_env: LibraryEnvironment, certificate): """ Import qnetd client certificate to local node certificate storage certificate base64 encoded qnetd client certificate """ try: certificate_data = base64.b64decode(certificate) except (TypeError, binascii.Error): raise LibraryError( ReportItem.error( reports.messages.InvalidOptionValue( "qnetd client certificate", certificate, ["base64 encoded certificate"], ))) qdevice_net.client_import_certificate_and_key(lib_env.cmd_runner(), certificate_data)
def validate_new_nodes_devices(nodes_devices): """ Validate if SBD devices are set for new nodes when they should be dict nodes_devices -- name: node name, key: list of SBD devices """ if is_device_set_local(): return validate_nodes_devices( nodes_devices, adding_nodes_to_sbd_enabled_cluster=True ) return [ ReportItem.error( reports.messages.SbdWithDevicesNotUsedCannotSetDevice(node) ) for node, devices in nodes_devices.items() if devices ]
def prepare_options_with_set(cib, options, resource_set_list): options = constraint.prepare_options( ("score", ), options, partial(constraint.create_id, cib, "colocation", resource_set_list), partial(check_new_id_applicable, cib, DESCRIPTION), ) if "score" in options: if not is_score(options["score"]): raise LibraryError( ReportItem.error( reports.messages.InvalidScore(options["score"]))) else: options["score"] = SCORE_INFINITY return options
def client_net_setup(lib_env: LibraryEnvironment, ca_certificate): """ Initialize qdevice net client on local host ca_certificate -- base64 encoded qnetd CA certificate """ try: ca_certificate_data = base64.b64decode(ca_certificate) except (TypeError, binascii.Error) as e: raise LibraryError( ReportItem.error( reports.messages.InvalidOptionValue( "qnetd CA certificate", ca_certificate, ["base64 encoded certificate"], ))) from e qdevice_net.client_setup(lib_env.cmd_runner(), ca_certificate_data)
def remove_levels_by_params( topology_el, level=None, target_type=None, target_value=None, devices=None, ignore_if_missing=False, ) -> ReportItemList: """ Remove specified fencing level(s) etree topology_el -- etree element to remove the levels from int|string level -- level (index) of the fencing level to remove constant target_type -- the removed fencing level target value type mixed target_value -- the removed fencing level target value Iterable devices -- list of stonith devices of the removed fencing level bool ignore_if_missing -- when True, do not report if level not found """ # Do not ever remove a fencing-topology element, even if it is empty. There # may be ACLs set in pacemaker which allow "write" for fencing-level # elements (adding, changing and removing) but not fencing-topology # elements. In such a case, removing a fencing-topology element would cause # the whole change to be rejected by pacemaker with a "permission denied" # message. # https://bugzilla.redhat.com/show_bug.cgi?id=1642514 report_list: ReportItemList = [] if target_type: report_list.extend(_validate_target_typewise(target_type)) if has_errors(report_list): return report_list level_el_list = _find_level_elements(topology_el, level, target_type, target_value, devices) if not level_el_list: if ignore_if_missing: return report_list report_list.append( ReportItem.error( reports.messages.CibFencingLevelDoesNotExist( level, target_type, target_value, devices or []))) if has_errors(report_list): return report_list for el in level_el_list: el.getparent().remove(el) return report_list
def qdevice_status_generic_text(runner, verbose=False): """ get qdevice runtime status in plain text bool verbose get more detailed output """ args = ["-s"] if verbose: args.append("-v") stdout, stderr, retval = _qdevice_run_tool(runner, args) if retval != 0: raise LibraryError( ReportItem.error( reports.messages.QdeviceGetStatusError( __model, join_multilines([stderr, stdout]), ))) return stdout
def set_expected_votes_live(lib_env, expected_votes): """ set expected votes in live cluster to specified value numeric expected_votes desired value of expected votes """ try: votes_int = int(expected_votes) if votes_int < 1: raise ValueError() except ValueError: raise LibraryError( ReportItem.error( reports.messages.InvalidOptionValue( "expected votes", expected_votes, "positive integer"))) from None corosync_live.set_expected_votes(lib_env.cmd_runner(), votes_int)
def update_quorum_device( self, model_options, generic_options, heuristics_options ): """ Update existing quorum device configuration dict model_options -- model specific options dict generic_options -- generic quorum device options dict heuristics_options -- heuristics options """ if not self.has_quorum_device(): raise LibraryError( ReportItem.error(reports.messages.QdeviceNotDefined()) ) model = self.get_quorum_device_model() # set new configuration device_sections = [] model_sections = [] heuristics_sections = [] for quorum in self.config.get_sections("quorum"): device_sections.extend(quorum.get_sections("device")) for device in quorum.get_sections("device"): model_sections.extend(device.get_sections(model)) heuristics_sections.extend(device.get_sections("heuristics")) # we know device sections exist, otherwise the function would exit at # has_quorum_device line above if not model_sections: new_model = config_parser.Section(model) device_sections[-1].add_section(new_model) model_sections.append(new_model) if not heuristics_sections: new_heuristics = config_parser.Section("heuristics") device_sections[-1].add_section(new_heuristics) heuristics_sections.append(new_heuristics) self.__set_section_options(device_sections, generic_options) self.__set_section_options(model_sections, model_options) self.__set_section_options(heuristics_sections, heuristics_options) self.__update_qdevice_votes() self.__update_two_node() self.__remove_empty_sections(self.config) self._need_qdevice_reload = True
def add_resource(bundle_element, primitive_element): """ Add an existing resource to an existing bundle etree bundle_element -- where to add the resource to etree primitive_element -- the resource to be added to the bundle """ # TODO possibly split to 'validate' and 'do' functions # a bundle may currently contain at most one primitive resource inner_primitive = bundle_element.find(TAG_PRIMITIVE) if inner_primitive is not None: raise LibraryError( ReportItem.error( reports.messages.ResourceBundleAlreadyContainsAResource( bundle_element.get("id"), inner_primitive.get("id"), ))) bundle_element.append(primitive_element)
def _process_response(self, response): report_item = response_to_report_item( response, severity=ReportItemSeverity.WARNING) node_label = response.request.target.label if report_item is not None: if not response.was_connected: self._report(report_item) self._report( ReportItem.warning( reports.messages.UnableToGetSbdConfig(node_label, ""))) return self._config_list.append({ "node": node_label, "config": environment_file_to_dict(response.data), }) self._successful_target_list.append(node_label)
def get_valid_timeout_seconds( timeout_candidate: Union[str, int, None], ) -> Optional[int]: """ Transform pacemaker style timeout to number of seconds, raise LibraryError on invalid timeout timeout_candidate timeout string or None """ if timeout_candidate is None: return None wait_timeout = timeout_to_seconds(timeout_candidate) if wait_timeout is None: raise LibraryError( ReportItem.error( reports.messages.InvalidTimeoutValue(str(timeout_candidate)) ) ) return wait_timeout