def test_stop_warning(self, m_log_warning, m_subp): err = exceptions.ProcessExecutionError("cmd") m_subp.side_effect = err stop() assert [mock.call(["systemctl", "stop", "ubuntu-advantage.service"]) ] == m_subp.call_args_list assert [mock.call(err)] == m_log_warning.call_args_list
def fake_subp(cmd): if cmd[0] == "git": # Not matching tag on git-ubuntu pkg branches raise exceptions.ProcessExecutionError( "fatal: No names found, cannot describe anything.") if cmd[0] == "dpkg-parsechangelog": return ("24.1\n", "") assert False, "Unexpected subp cmd {}".format(cmd)
def test_status_command_retry_on_application_status( self, m_which, m_sleep, entitlement): from uaclient import util with mock.patch.object(util, "_subp") as m_subp: m_subp.side_effect = exceptions.ProcessExecutionError("error msg") status, details = entitlement.application_status() assert m_subp.call_count == 3 assert m_sleep.call_count == 2 assert status == ApplicationStatus.DISABLED assert "error msg" in details.msg
def test_remove_packages_output_message_when_fail(self, m_get_installed_packages, m_subp, _m_get_platform, entitlement): m_get_installed_packages.return_value = ["ubuntu-fips"] m_subp.side_effect = exceptions.ProcessExecutionError(cmd="test") expected_msg = "Could not disable {}.".format(entitlement.title) with pytest.raises(exceptions.UserFacingError) as exc_info: entitlement.remove_packages() assert exc_info.value.msg.strip() == expected_msg
def test_enable_alerts_user_that_snapd_does_not_wait_command( self, m_subp, m_installed_pkgs, m_which, m_livepatch_proxy, m_snap_proxy, m_validate_proxy, entitlement, capsys, caplog_text, ): m_which.side_effect = [True, False] m_installed_pkgs.return_value = ["snapd"] stderr_msg = ( "error: Unknown command `wait'. Please specify one command of: " "abort, ack, buy, change, changes, connect, create-user, disable," " disconnect, download, enable, find, help, install, interfaces, " "known, list, login, logout, refresh, remove, run or try") m_subp.side_effect = [ exceptions.ProcessExecutionError( cmd="snapd wait system seed.loaded", exit_code=-1, stdout="", stderr=stderr_msg, ), True, ] fake_stdout = io.StringIO() with mock.patch.object(entitlement, "can_enable") as m_can_enable: m_can_enable.return_value = (True, None) with mock.patch.object( entitlement, "setup_livepatch_config") as m_setup_livepatch: with contextlib.redirect_stdout(fake_stdout): entitlement.enable() assert 1 == m_can_enable.call_count assert 1 == m_setup_livepatch.call_count assert ("Installing canonical-livepatch snap" in fake_stdout.getvalue().strip()) for msg in messages.SNAPD_DOES_NOT_HAVE_WAIT_CMD.split("\n"): assert msg in caplog_text() assert m_validate_proxy.call_count == 2 assert m_snap_proxy.call_count == 1 assert m_livepatch_proxy.call_count == 1
def test_run_apt_command_with_invalid_repositories(self, m_subp, error_list, output_list): error_msg = "\n".join(error_list) m_subp.side_effect = exceptions.ProcessExecutionError(cmd="apt update", stderr=error_msg) with pytest.raises(exceptions.UserFacingError) as excinfo: run_apt_update_command() expected_message = "\n".join(output_list) + "." assert expected_message == excinfo.value.msg
class TestGetCloudType: @mock.patch(M_PATH + "util.which", return_value="/usr/bin/cloud-id") @mock.patch(M_PATH + "util.subp", return_value=("somecloud\n", "")) def test_use_cloud_id_when_available(self, m_subp, m_which): """Use cloud-id utility to discover cloud type.""" assert ("somecloud", None) == get_cloud_type.__wrapped__() assert [mock.call("cloud-id")] == m_which.call_args_list @mock.patch(M_PATH + "util.which", return_value="/usr/bin/cloud-id") @mock.patch( M_PATH + "util.subp", side_effect=exceptions.ProcessExecutionError("cloud-id"), ) def test_error_when_cloud_id_fails(self, m_subp, m_which): assert ( None, NoCloudTypeReason.CLOUD_ID_ERROR, ) == get_cloud_type.__wrapped__() @pytest.mark.parametrize( "settings_overrides", ( (""" settings_overrides: cloud_type: "azure" """), (""" settings_overrides: other_setting: "blah" """), ), ) @mock.patch("uaclient.util.load_file") @mock.patch(M_PATH + "util.which", return_value="/usr/bin/cloud-id") @mock.patch(M_PATH + "util.subp", return_value=("test", "")) def test_cloud_type_when_using_settings_override(self, m_subp, m_which, m_load_file, settings_overrides): if "azure" in settings_overrides: expected_value = "azure" else: expected_value = "test" m_load_file.return_value = settings_overrides assert get_cloud_type.__wrapped__() == (expected_value, None)
class TestGetInstanceID: @mock.patch(M_PATH + "util.subp", return_value=("my-iid\n", "")) def test_use_cloud_init_query(self, m_subp): """Get instance_id from cloud-init query.""" assert "my-iid" == get_instance_id() assert [mock.call(["cloud-init", "query", "instance_id"])] == m_subp.call_args_list @mock.patch( M_PATH + "util.subp", side_effect=exceptions.ProcessExecutionError( "cloud-init query instance_id"), ) def test_none_when_cloud_init_query_fails(self, m_subp): """Return None when cloud-init query fails.""" assert None is get_instance_id() assert [mock.call(["cloud-init", "query", "instance_id"])] == m_subp.call_args_list
def test_application_status(self, m_subp, m_which, subp_raise_exception, which_result, entitlement): m_which.return_value = which_result if subp_raise_exception: m_subp.side_effect = exceptions.ProcessExecutionError("error msg") status, details = entitlement.application_status() if not which_result: assert status == ApplicationStatus.DISABLED assert "canonical-livepatch snap is not installed." in details.msg elif subp_raise_exception: assert status == ApplicationStatus.DISABLED assert "error msg" in details.msg else: assert status == ApplicationStatus.ENABLED assert details is None
def test_errors_on_process_execution_errors( self, m_exists, m_subp, m_temporary_directory, exit_code, stderr, error_msg, ): """Raise the appropriate user facing error from apt-helper failure.""" m_temporary_directory.return_value.__enter__.return_value = ( "/does/not/exist") # Failure apt-helper response m_subp.side_effect = exceptions.ProcessExecutionError( cmd="apt-helper ", exit_code=exit_code, stdout="Err:1...", stderr=stderr, ) with pytest.raises(exceptions.UserFacingError) as excinfo: assert_valid_apt_credentials(repo_url="http://fakerepo", username="******", password="******") assert error_msg == str(excinfo.value) exists_calls = [mock.call("/usr/lib/apt/apt-helper")] assert exists_calls == m_exists.call_args_list expected_path = os.path.join( m_temporary_directory.return_value.__enter__.return_value, "apt-helper-output", ) apt_helper_call = mock.call( [ "/usr/lib/apt/apt-helper", "download-file", "http://*****:*****@fakerepo/ubuntu/pool/", expected_path, ], timeout=60, retry_sleeps=APT_RETRIES, ) assert [apt_helper_call] == m_subp.call_args_list
def test_enable_raise_exception_for_unexpected_error_on_snapd_wait( self, m_subp, m_installed_pkgs, m_which, m_livepatch_proxy, m_snap_proxy, m_validate_proxy, entitlement, ): m_which.side_effect = [False, True] m_installed_pkgs.return_value = ["snapd"] stderr_msg = "test error" m_subp.side_effect = exceptions.ProcessExecutionError( cmd="snapd wait system seed.loaded", exit_code=-1, stdout="", stderr=stderr_msg, ) with mock.patch.object(entitlement, "can_enable") as m_can_enable: m_can_enable.return_value = (True, None) with mock.patch.object( entitlement, "setup_livepatch_config") as m_setup_livepatch: with pytest.raises( exceptions.ProcessExecutionError) as excinfo: entitlement.enable() assert 1 == m_can_enable.call_count assert 0 == m_setup_livepatch.call_count expected_msg = "test error" assert expected_msg in str(excinfo) assert m_validate_proxy.call_count == 0 assert m_snap_proxy.call_count == 0 assert m_livepatch_proxy.call_count == 0
def fake_subp(cmd, capture=None, retry_sleeps=None, env={}): if cmd == ["apt-get", "update"]: raise exceptions.ProcessExecutionError( "Failure", stderr="Could not get lock /var/lib/dpkg/lock") return "", ""
def fake_subp(args, *other_args, **kwargs): if "install" in args: raise exceptions.ProcessExecutionError(args)
def _subp( args: Sequence[str], rcs: Optional[List[int]] = None, capture: bool = False, timeout: Optional[float] = None, env: Optional[Dict[str, str]] = None, ) -> Tuple[str, str]: """Run a command and return a tuple of decoded stdout, stderr. @param args: A list of arguments to feed to subprocess.Popen @param rcs: A list of allowed return_codes. If returncode not in rcs raise a ProcessExecutionError. @param capture: Boolean set True to log the command and response. @param timeout: Optional float indicating number of seconds to wait for subp to return. @param env: Optional dictionary of environment variable to pass to Popen. @return: Tuple of utf-8 decoded stdout, stderr @raises ProcessExecutionError on invalid command or returncode not in rcs. @raises subprocess.TimeoutError when timeout specified and the command exceeds that number of seconds. """ bytes_args = [ x if isinstance(x, bytes) else x.encode("utf-8") for x in args ] if env: env.update(os.environ) if rcs is None: rcs = [0] redacted_cmd = redact_sensitive_logs(" ".join(args)) try: proc = subprocess.Popen(bytes_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) (out, err) = proc.communicate(timeout=timeout) except OSError: try: raise exceptions.ProcessExecutionError( cmd=redacted_cmd, exit_code=proc.returncode, stdout=out.decode("utf-8"), stderr=err.decode("utf-8"), ) except UnboundLocalError: raise exceptions.ProcessExecutionError(cmd=redacted_cmd) if proc.returncode not in rcs: raise exceptions.ProcessExecutionError( cmd=redacted_cmd, exit_code=proc.returncode, stdout=out.decode("utf-8"), stderr=err.decode("utf-8"), ) if capture: logging.debug( "Ran cmd: %s, rc: %s stderr: %s", redacted_cmd, proc.returncode, err, ) return out.decode("utf-8"), err.decode("utf-8")
def fake_subp(cmd, *args, **kwargs): if "install" in cmd: raise exceptions.ProcessExecutionError(cmd) return ("", "")
class TestConfigureSnapProxy: @pytest.mark.parametrize( "http_proxy,https_proxy,retry_sleeps", ( ("http_proxy", "https_proxy", [1, 2]), ("http_proxy", "", None), ("", "https_proxy", [1, 2]), ("http_proxy", None, [1, 2]), (None, "https_proxy", None), (None, None, [1, 2]), ), ) @mock.patch("uaclient.util.subp") @mock.patch("uaclient.util.which", return_value=True) def test_configure_snap_proxy(self, m_which, m_subp, http_proxy, https_proxy, retry_sleeps, capsys): configure_snap_proxy(http_proxy, https_proxy, retry_sleeps) expected_calls = [] if http_proxy: expected_calls.append( mock.call( [ "snap", "set", "system", "proxy.http={}".format(http_proxy), ], retry_sleeps=retry_sleeps, )) if https_proxy: expected_calls.append( mock.call( [ "snap", "set", "system", "proxy.https={}".format(https_proxy), ], retry_sleeps=retry_sleeps, )) assert m_subp.call_args_list == expected_calls out, _ = capsys.readouterr() if http_proxy or https_proxy: assert out.strip() == messages.SETTING_SERVICE_PROXY.format( service="snap") @pytest.mark.parametrize( "key, subp_side_effect, expected_ret", [ ( "proxy.http", exceptions.ProcessExecutionError("doesn't matter"), None, ), ("proxy.https", ("value", ""), "value"), ], ) @mock.patch("uaclient.util.subp") def test_get_config_option_value(self, m_util_subp, key, subp_side_effect, expected_ret): m_util_subp.side_effect = [subp_side_effect] ret = get_config_option_value(key) assert ret == expected_ret assert [mock.call(["snap", "get", "system", key])] == m_util_subp.call_args_list