def diff_cibs_xml( runner: CommandRunner, reporter: ReportProcessor, cib_old_xml, cib_new_xml, ): """ Return xml diff of two CIBs runner reporter string cib_old_xml -- original CIB string cib_new_xml -- modified CIB """ try: cib_old_tmp_file = write_tmpfile(cib_old_xml) reporter.report( ReportItem.debug( reports.messages.TmpFileWrite( cib_old_tmp_file.name, cib_old_xml ) ) ) cib_new_tmp_file = write_tmpfile(cib_new_xml) reporter.report( ReportItem.debug( reports.messages.TmpFileWrite( cib_new_tmp_file.name, cib_new_xml ) ) ) except EnvironmentError as e: raise LibraryError( ReportItem.error(reports.messages.CibSaveTmpError(str(e))) ) from e command = [ __exec("crm_diff"), "--original", cib_old_tmp_file.name, "--new", cib_new_tmp_file.name, "--no-version", ] # 0 (CRM_EX_OK) - success with no difference # 1 (CRM_EX_ERROR) - success with difference # 64 (CRM_EX_USAGE) - usage error # 65 (CRM_EX_DATAERR) - XML fragments not parseable stdout, stderr, retval = runner.run(command) if retval == 0: return "" if retval > 1: raise LibraryError( ReportItem.error( reports.messages.CibDiffError( stderr.strip(), cib_old_xml, cib_new_xml ) ) ) return stdout.strip()
def cmd_runner(self) -> CommandRunner: runner_env = { # make sure to get output of external processes in English and ASCII "LC_ALL": "C", } if self.user_login: runner_env["CIB_user"] = self.user_login if not self.is_cib_live: # Dump CIB data to a temporary file and set it up in the runner. # This way every called pacemaker tool can access the CIB and we # don't need to take care of it every time the runner is called. if not self._cib_data_tmp_file: try: cib_data = self._cib_data self._cib_data_tmp_file = write_tmpfile(cib_data) self.report_processor.report( ReportItem.debug( reports.messages.TmpFileWrite( self._cib_data_tmp_file.name, cib_data))) except EnvironmentError as e: raise LibraryError( ReportItem.error( reports.messages.CibSaveTmpError(str(e)))) runner_env["CIB_file"] = self._cib_data_tmp_file.name return CommandRunner(self.logger, self.report_processor, runner_env)
def _log_response_failure(self, response): msg = "Unable to connect to {node} ({reason})" self._logger.debug( msg.format( node=response.request.host_label, reason=response.error_msg ) ) self._reporter.report( ReportItem.debug( reports.messages.NodeCommunicationNotConnected( response.request.host_label, response.error_msg, ) ) ) if is_proxy_set(os.environ): self._logger.warning("Proxy is set") self._reporter.report( ReportItem.warning( reports.messages.NodeCommunicationProxyIsSet( response.request.host_label, response.request.dest.addr, ) ) )
def log_request_start(self, request): msg = "Sending HTTP Request to: {url}" if request.data: msg += "\n--Debug Input Start--\n{data}\n--Debug Input End--" self._logger.debug(msg.format(url=request.url, data=request.data)) self._reporter.report( ReportItem.debug( reports.messages.NodeCommunicationStarted( request.url, request.data, )))
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 _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 run( self, args: Sequence[str], stdin_string: Optional[str] = None, env_extend: Optional[Mapping[str, str]] = None, binary_output: bool = False, ) -> Tuple[str, str, int]: # Allow overriding default settings. If a piece of code really wants to # set own PATH or CIB_file, we must allow it. I.e. it wants to run # a pacemaker tool on a CIB in a file but cannot afford the risk of # changing the CIB in the file specified by the user. env_vars = dict(self._env_vars) env_vars.update(dict(env_extend) if env_extend else {}) log_args = " ".join([shell_quote(x) for x in args]) self._logger.debug( "Running: {args}\nEnvironment:{env_vars}{stdin_string}".format( args=log_args, stdin_string=("" if not stdin_string else ( "\n--Debug Input Start--\n{0}\n--Debug Input End--" ).format(stdin_string)), env_vars=("" if not env_vars else ("\n" + "\n".join([ " {0}={1}".format(key, val) for key, val in sorted(env_vars.items()) ]))), )) self._reporter.report( ReportItem.debug( reports.messages.RunExternalProcessStarted( log_args, stdin_string, env_vars, ))) try: # pylint: disable=subprocess-popen-preexec-fn, consider-using-with # this is OK as pcs is only single-threaded application process = subprocess.Popen( args, # Some commands react differently if they get anything via stdin stdin=(subprocess.PIPE if stdin_string is not None else subprocess.DEVNULL), stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=( lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)), close_fds=True, shell=False, env=env_vars, # decodes newlines and in python3 also converts bytes to str universal_newlines=(not binary_output), ) out_std, out_err = process.communicate(stdin_string) retval = process.returncode except OSError as e: raise LibraryError( ReportItem.error( reports.messages.RunExternalProcessError( log_args, e.strerror, ))) from e self._logger.debug( ("Finished running: {args}\nReturn value: {retval}" + "\n--Debug Stdout Start--\n{out_std}\n--Debug Stdout End--" + "\n--Debug Stderr Start--\n{out_err}\n--Debug Stderr End--" ).format(args=log_args, retval=retval, out_std=out_std, out_err=out_err)) self._reporter.report( ReportItem.debug( reports.messages.RunExternalProcessFinished( log_args, retval, out_std, out_err, ))) return out_std, out_err, retval