def test_can_enable_when_incompatible_service_found( self, concrete_entitlement_factory ): base_ent = concrete_entitlement_factory( entitled=True, applicability_status=(ApplicabilityStatus.APPLICABLE, ""), application_status=(ApplicationStatus.DISABLED, ""), ) m_entitlement_cls = mock.MagicMock() m_entitlement_obj = m_entitlement_cls.return_value m_entitlement_obj.application_status.return_value = [ ApplicationStatus.ENABLED, "", ] base_ent._incompatible_services = ( base.IncompatibleService( m_entitlement_cls, messages.NamedMessage("test", "test") ), ) ret, reason = base_ent.can_enable() assert ret is False assert reason.reason == CanEnableFailureReason.INCOMPATIBLE_SERVICE assert reason.message is None
def test_proc_file_is_used_to_determine_application_status_message( self, proc_content, path_exists, entitlement): orig_load_file = util.load_file def fake_load_file(path): if path == "/proc/sys/crypto/fips_enabled": return proc_content return orig_load_file(path) orig_exists = os.path.exists def fake_exists(path): if path == "/proc/sys/crypto/fips_enabled": return path_exists return orig_exists(path) msg = messages.NamedMessage("test-code", "sure is some status here") entitlement.cfg.add_notice("", messages.FIPS_SYSTEM_REBOOT_REQUIRED.msg) if path_exists: entitlement.cfg.add_notice("", messages.FIPS_REBOOT_REQUIRED_MSG) if proc_content == "0": entitlement.cfg.add_notice("", messages.FIPS_DISABLE_REBOOT_REQUIRED) with mock.patch( M_PATH + "repo.RepoEntitlement.application_status", return_value=(ApplicationStatus.ENABLED, msg), ): with mock.patch("uaclient.util.load_file") as m_load_file: m_load_file.side_effect = fake_load_file with mock.patch("os.path.exists") as m_path_exists: m_path_exists.side_effect = fake_exists ( actual_status, actual_msg, ) = entitlement.application_status() expected_status = ApplicationStatus.ENABLED if path_exists and proc_content == "1": expected_msg = msg assert entitlement.cfg.read_cache("notices") is None elif path_exists and proc_content == "0": expected_msg = messages.FIPS_PROC_FILE_ERROR.format( file_name=entitlement.FIPS_PROC_FILE) expected_status = ApplicationStatus.DISABLED assert [["", messages.NOTICE_FIPS_MANUAL_DISABLE_URL] ] == entitlement.cfg.read_cache("notices") else: expected_msg = messages.FIPS_REBOOT_REQUIRED assert [["", messages.FIPS_SYSTEM_REBOOT_REQUIRED.msg] ] == entitlement.cfg.read_cache("notices") assert actual_status == expected_status assert expected_msg.msg == actual_msg.msg assert expected_msg.name == actual_msg.name
def test_enable_false_when_fails_to_enable_required_service( self, m_handle_msg, m_ent_factory, m_prompt_for_confirmation, enable_fail_message, concrete_entitlement_factory, ): m_handle_msg.return_value = True fail_reason = CanEnableFailure( CanEnableFailureReason.INACTIVE_REQUIRED_SERVICES ) if enable_fail_message: msg = messages.NamedMessage("test-code", enable_fail_message) else: msg = None enable_fail_reason = CanEnableFailure( CanEnableFailureReason.NOT_ENTITLED, message=msg ) m_ent_cls = mock.Mock() m_ent_obj = m_ent_cls.return_value m_ent_obj.enable.return_value = (False, enable_fail_reason) m_ent_obj.application_status.return_value = ( ApplicationStatus.DISABLED, None, ) type(m_ent_obj).title = mock.PropertyMock(return_value="Test") m_ent_factory.return_value = m_ent_cls m_prompt_for_confirmation.return_vale = True entitlement = concrete_entitlement_factory( entitled=True, application_status=(ApplicationStatus.DISABLED, ""), ) entitlement._required_services = "test" with mock.patch.object(entitlement, "can_enable") as m_can_enable: m_can_enable.return_value = (False, fail_reason) ret, fail = entitlement.enable() assert not ret expected_msg = "Cannot enable required service: Test" if enable_fail_reason.message: expected_msg += "\n" + enable_fail_reason.message.msg assert expected_msg == fail.message.msg assert 1 == m_can_enable.call_count assert 1 == m_ent_factory.call_count
def test_disable_false_when_fails_to_disable_dependent_service( self, m_handle_msg, m_ent_factory, m_prompt_for_confirmation, disable_fail_message, concrete_entitlement_factory, ): m_handle_msg.return_value = True fail_reason = CanDisableFailure( CanDisableFailureReason.ACTIVE_DEPENDENT_SERVICES ) if disable_fail_message: msg = messages.NamedMessage("test-code", disable_fail_message) else: msg = None disable_fail_reason = CanDisableFailure( CanDisableFailureReason.ALREADY_DISABLED, message=msg ) m_ent_cls = mock.Mock() m_ent_obj = m_ent_cls.return_value m_ent_obj.disable.return_value = (False, disable_fail_reason) m_ent_obj.application_status.return_value = ( ApplicationStatus.ENABLED, None, ) type(m_ent_obj).title = mock.PropertyMock(return_value="Test") m_ent_factory.return_value = m_ent_cls m_prompt_for_confirmation.return_vale = True entitlement = concrete_entitlement_factory( entitled=True, application_status=(ApplicationStatus.DISABLED, ""), dependent_services=("test"), ) with mock.patch.object(entitlement, "can_disable") as m_can_disable: m_can_disable.return_value = (False, fail_reason) ret, fail = entitlement.disable() assert not ret expected_msg = "Cannot disable dependent service: Test" if disable_fail_reason.message: expected_msg += "\n" + disable_fail_reason.message.msg assert expected_msg == fail.message.msg assert 1 == m_can_disable.call_count assert 1 == m_ent_factory.call_count
def test_enable_when_incompatible_service_found( self, m_prompt, m_is_config_value_true, block_disable_on_enable, assume_yes, concrete_entitlement_factory, ): m_prompt.return_value = assume_yes m_is_config_value_true.return_value = block_disable_on_enable base_ent = concrete_entitlement_factory( entitled=True, enable=True, applicability_status=(ApplicabilityStatus.APPLICABLE, ""), application_status=(ApplicationStatus.DISABLED, ""), ) m_entitlement_cls = mock.MagicMock() m_entitlement_obj = m_entitlement_cls.return_value m_entitlement_obj.application_status.return_value = [ ApplicationStatus.ENABLED, "", ] base_ent._incompatible_services = ( base.IncompatibleService( m_entitlement_cls, messages.NamedMessage("test", "test") ), ) ret, reason = base_ent.enable() expected_prompt_call = 1 if block_disable_on_enable: expected_prompt_call = 0 expected_ret = False expected_reason = CanEnableFailureReason.INCOMPATIBLE_SERVICE if assume_yes and not block_disable_on_enable: expected_ret = True expected_reason = None expected_disable_call = 1 if expected_ret else 0 assert ret == expected_ret if expected_reason is None: assert reason is None else: assert reason.reason == expected_reason assert m_prompt.call_count == expected_prompt_call assert m_is_config_value_true.call_count == 1 assert m_entitlement_obj.disable.call_count == expected_disable_call
def application_status( self, ) -> Tuple[ApplicationStatus, Optional[messages.NamedMessage]]: status = (ApplicationStatus.ENABLED, None) if not util.which(LIVEPATCH_CMD): return (ApplicationStatus.DISABLED, messages.LIVEPATCH_NOT_ENABLED) try: util.subp( [LIVEPATCH_CMD, "status"], retry_sleeps=LIVEPATCH_RETRIES ) except exceptions.ProcessExecutionError as e: # TODO(May want to parse INACTIVE/failure assessment) logging.debug("Livepatch not enabled. %s", str(e)) return ( ApplicationStatus.DISABLED, messages.NamedMessage(name="", msg=str(e)), ) return status
def test_status( self, contract_status, uf_status, in_inapplicable_resources, expected_status, FakeConfig, ): ent = mock.MagicMock() ent.name = "test_entitlement" ent.contract_status.return_value = contract_status ent.user_facing_status.return_value = ( uf_status, messages.NamedMessage("test-code", ""), ) unavailable_resources = ({ ent.name: "" } if in_inapplicable_resources else {}) ret = status._attached_service_status(ent, unavailable_resources) assert expected_status == ret["status"]
def test_entitlement_instantiated_and_disabled( self, m_status, m_valid_services, m_entitlement_factory, _m_getuid, disable_return, return_code, assume_yes, service, tmpdir, capsys, event, FakeConfig, ): entitlements_cls = [] entitlements_obj = [] ent_dict = {} m_valid_services.return_value = [] if not disable_return: fail = CanDisableFailure( CanDisableFailureReason.ALREADY_DISABLED, message=messages.NamedMessage("test-code", "test"), ) else: fail = None for entitlement_name in service: m_entitlement_cls = mock.Mock() m_entitlement = m_entitlement_cls.return_value m_entitlement.disable.return_value = (disable_return, fail) entitlements_obj.append(m_entitlement) entitlements_cls.append(m_entitlement_cls) m_valid_services.return_value.append(entitlement_name) ent_dict[entitlement_name] = m_entitlement_cls type(m_entitlement).name = mock.PropertyMock( return_value=entitlement_name) def factory_side_effect(cfg, name, ent_dict=ent_dict): return ent_dict.get(name) m_entitlement_factory.side_effect = factory_side_effect cfg = FakeConfig.for_attached_machine() args_mock = mock.Mock() args_mock.service = service args_mock.assume_yes = assume_yes with mock.patch.object(cfg, "check_lock_info", return_value=(-1, "")): ret = action_disable(args_mock, cfg=cfg) for m_entitlement_cls in entitlements_cls: assert [mock.call(cfg, assume_yes=assume_yes) ] == m_entitlement_cls.call_args_list expected_disable_call = mock.call() for m_entitlement in entitlements_obj: assert [expected_disable_call ] == m_entitlement.disable.call_args_list assert return_code == ret assert len(entitlements_cls) == m_status.call_count cfg = FakeConfig.for_attached_machine() args_mock.assume_yes = True args_mock.format = "json" with mock.patch.object(event, "_event_logger_mode", event_logger.EventLoggerMode.JSON): with mock.patch.object(event, "set_event_mode"): with mock.patch.object(cfg, "check_lock_info", return_value=(-1, "")): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): ret = action_disable(args_mock, cfg=cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "success" if disable_return else "failure", "errors": [], "failed_services": [] if disable_return else service, "needs_reboot": False, "processed_services": service if disable_return else [], "warnings": [], } if not disable_return: expected["errors"] = [{ "message": "test", "message_code": "test-code", "service": ent_name, "type": "service", } for ent_name in service] assert return_code == ret assert expected == json.loads(fake_stdout.getvalue())
def test_entitlements_not_found_disabled_and_enabled( self, m_status, m_valid_services, m_entitlement_factory, _m_getuid, assume_yes, tmpdir, event, FakeConfig, ): expected_error_tmpl = messages.INVALID_SERVICE_OP_FAILURE num_calls = 2 m_ent1_cls = mock.Mock() m_ent1_obj = m_ent1_cls.return_value m_ent1_obj.disable.return_value = ( False, CanDisableFailure( CanDisableFailureReason.ALREADY_DISABLED, message=messages.NamedMessage("test-code", "test"), ), ) type(m_ent1_obj).name = mock.PropertyMock(return_value="ent1") m_ent2_cls = mock.Mock() m_ent2_obj = m_ent2_cls.return_value m_ent2_obj.disable.return_value = ( False, CanDisableFailure( CanDisableFailureReason.ALREADY_DISABLED, message=messages.NamedMessage("test-code2", "test2"), ), ) type(m_ent2_obj).name = mock.PropertyMock(return_value="ent2") m_ent3_cls = mock.Mock() m_ent3_obj = m_ent3_cls.return_value m_ent3_obj.disable.return_value = (True, None) type(m_ent3_obj).name = mock.PropertyMock(return_value="ent3") def factory_side_effect(cfg, name): if name == "ent2": return m_ent2_cls if name == "ent3": return m_ent3_cls return None m_entitlement_factory.side_effect = factory_side_effect m_valid_services.return_value = ["ent2", "ent3"] cfg = FakeConfig.for_attached_machine() args_mock = mock.Mock() args_mock.service = ["ent1", "ent2", "ent3"] args_mock.assume_yes = assume_yes with pytest.raises(exceptions.UserFacingError) as err: with mock.patch.object(cfg, "check_lock_info", return_value=(-1, "")): action_disable(args_mock, cfg=cfg) assert (expected_error_tmpl.format( operation="disable", invalid_service="ent1", service_msg="Try ent2, ent3.", ).msg == err.value.msg) for m_ent_cls in [m_ent2_cls, m_ent3_cls]: assert [mock.call(cfg, assume_yes=assume_yes) ] == m_ent_cls.call_args_list expected_disable_call = mock.call() for m_ent in [m_ent2_obj, m_ent3_obj]: assert [expected_disable_call] == m_ent.disable.call_args_list assert 0 == m_ent1_obj.call_count assert num_calls == m_status.call_count cfg = FakeConfig.for_attached_machine() args_mock.assume_yes = True args_mock.format = "json" with pytest.raises(SystemExit): with mock.patch.object(event, "_event_logger_mode", event_logger.EventLoggerMode.JSON): with mock.patch.object(event, "set_event_mode"): with mock.patch.object(cfg, "check_lock_info", return_value=(-1, "")): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): main_error_handler(action_disable)(args_mock, cfg=cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": "test2", "message_code": "test-code2", "service": "ent2", "type": "service", }, { "message": ("Cannot disable unknown service 'ent1'.\n" "Try ent2, ent3."), "message_code": "invalid-service-or-failure", "service": None, "type": "system", }, ], "failed_services": ["ent2"], "needs_reboot": False, "processed_services": ["ent3"], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
def test_print_message_when_can_enable_fails( self, _m_get_available_resources, _m_request_updated_contract, m_getuid, event, FakeConfig, ): m_getuid.return_value = 0 m_entitlement_cls = mock.Mock() type(m_entitlement_cls).is_beta = mock.PropertyMock(return_value=False) m_entitlement_obj = m_entitlement_cls.return_value m_entitlement_obj.enable.return_value = ( False, CanEnableFailure( CanEnableFailureReason.ALREADY_ENABLED, message=messages.NamedMessage("test-code", "msg"), ), ) cfg = FakeConfig.for_attached_machine() args_mock = mock.Mock() args_mock.service = ["ent1"] args_mock.assume_yes = False args_mock.beta = False with mock.patch( "uaclient.entitlements.entitlement_factory", return_value=m_entitlement_cls, ), mock.patch( "uaclient.entitlements.valid_services", return_value=["ent1"] ): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): action_enable(args_mock, cfg) assert ( "One moment, checking your subscription first\nmsg\n" == fake_stdout.getvalue() ) with mock.patch( "uaclient.entitlements.entitlement_factory", return_value=m_entitlement_cls, ), mock.patch( "uaclient.entitlements.valid_services", return_value=["ent1"] ), mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): ret = action_enable(args_mock, cfg=cfg) expected_ret = 1 expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": "msg", "message_code": "test-code", "service": "ent1", "type": "service", } ], "failed_services": ["ent1"], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue()) assert expected_ret == ret
class TestAttachedServiceStatus: @pytest.mark.parametrize( "contract_status,uf_status,in_inapplicable_resources,expected_status", ATTACHED_SERVICE_STATUS_PARAMETERS, ) def test_status( self, contract_status, uf_status, in_inapplicable_resources, expected_status, FakeConfig, ): ent = mock.MagicMock() ent.name = "test_entitlement" ent.contract_status.return_value = contract_status ent.user_facing_status.return_value = ( uf_status, messages.NamedMessage("test-code", ""), ) unavailable_resources = ({ ent.name: "" } if in_inapplicable_resources else {}) ret = status._attached_service_status(ent, unavailable_resources) assert expected_status == ret["status"] @pytest.mark.parametrize( "blocking_incompatible_services, expected_blocked_by", ( ([], []), ( [ IncompatibleService(FIPSEntitlement, messages.NamedMessage("code", "msg")) ], [{ "name": "fips", "reason": "msg", "reason_code": "code" }], ), ( [ IncompatibleService(FIPSEntitlement, messages.NamedMessage("code", "msg")), IncompatibleService(ROSEntitlement, messages.NamedMessage("code2", "msg2")), ], [ { "name": "fips", "reason": "msg", "reason_code": "code" }, { "name": "ros", "reason": "msg2", "reason_code": "code2" }, ], ), ), ) def test_blocked_by( self, blocking_incompatible_services, expected_blocked_by, tmpdir, FakeConfig, ): ent = ConcreteTestEntitlement( blocking_incompatible_services=blocking_incompatible_services) service_status = status._attached_service_status(ent, []) assert service_status["blocked_by"] == expected_blocked_by
def test_attached_reports_contract_and_service_status( self, m_repo_contract_status, m_repo_uf_status, m_esm_contract_status, m_esm_uf_status, m_livepatch_contract_status, m_livepatch_uf_status, _m_livepatch_status, _m_fips_status, _m_getuid, _m_should_reboot, m_remove_notice, entitlements, features_override, show_beta, FakeConfig, ): """When attached, return contract and service user-facing status.""" m_repo_contract_status.return_value = ContractStatus.ENTITLED m_repo_uf_status.return_value = ( UserFacingStatus.INAPPLICABLE, messages.NamedMessage("test-code", "repo details"), ) m_livepatch_contract_status.return_value = ContractStatus.ENTITLED m_livepatch_uf_status.return_value = ( UserFacingStatus.ACTIVE, messages.NamedMessage("test-code", "livepatch details"), ) m_esm_contract_status.return_value = ContractStatus.ENTITLED m_esm_uf_status.return_value = ( UserFacingStatus.ACTIVE, messages.NamedMessage("test-code", "esm-apps details"), ) token = { "availableResources": ALL_RESOURCES_AVAILABLE, "machineTokenInfo": { "machineId": "test_machine_id", "accountInfo": { "id": "1", "name": "accountname", "createdAt": "2019-06-14T06:45:50Z", "externalAccountIDs": [{ "IDs": ["id1"], "Origin": "AWS" }], }, "contractInfo": { "id": "contract-1", "name": "contractname", "createdAt": "2020-05-08T19:02:26Z", "resourceEntitlements": entitlements, "products": ["free"], }, }, } cfg = FakeConfig.for_attached_machine(account_name="accountname", machine_token=token) if features_override: cfg.override_features(features_override) if not entitlements: support_level = UserFacingStatus.INAPPLICABLE.value else: support_level = entitlements[0]["affordances"]["supportLevel"] expected = copy.deepcopy(status.DEFAULT_STATUS) expected.update({ "version": version.get_version(features=cfg.features), "attached": True, "machine_id": "test_machine_id", "contract": { "name": "contractname", "id": "contract-1", "created_at": datetime.datetime(2020, 5, 8, 19, 2, 26, tzinfo=datetime.timezone.utc), "products": ["free"], "tech_support_level": support_level, }, "account": { "name": "accountname", "id": "1", "created_at": datetime.datetime(2019, 6, 14, 6, 45, 50, tzinfo=datetime.timezone.utc), "external_account_ids": [{ "IDs": ["id1"], "Origin": "AWS" }], }, }) for cls in ENTITLEMENT_CLASSES: if cls.name == "livepatch": expected_status = UserFacingStatus.ACTIVE.value details = "livepatch details" elif cls.name == "esm-apps": expected_status = UserFacingStatus.ACTIVE.value details = "esm-apps details" else: expected_status = UserFacingStatus.INAPPLICABLE.value details = "repo details" if self.check_beta(cls, show_beta, cfg, expected_status): continue expected["services"].append({ "name": cls.name, "description": cls.description, "entitled": ContractStatus.ENTITLED.value, "status": expected_status, "status_details": details, "description_override": None, "available": mock.ANY, "blocked_by": [], }) with mock.patch( "uaclient.status._get_config_status") as m_get_cfg_status: m_get_cfg_status.return_value = DEFAULT_CFG_STATUS assert expected == status.status(cfg=cfg, show_beta=show_beta) assert len(ENTITLEMENT_CLASSES) - 2 == m_repo_uf_status.call_count assert 1 == m_livepatch_uf_status.call_count expected_calls = [ mock.call( "", messages.NOTICE_DAEMON_AUTO_ATTACH_LOCK_HELD.format( operation=".*"), ), mock.call("", messages.NOTICE_DAEMON_AUTO_ATTACH_FAILED), mock.call( "", messages.ENABLE_REBOOT_REQUIRED_TMPL.format( operation="fix operation"), ), ] assert expected_calls == m_remove_notice.call_args_list
def test_help_command_when_attached(self, m_attached, m_available_resources, ent_status, ent_msg, is_beta): """Test help command for a valid service in an attached ua client.""" m_args = mock.MagicMock() m_service_name = mock.PropertyMock(return_value="test") type(m_args).service = m_service_name m_all = mock.PropertyMock(return_value=True) type(m_args).all = m_all m_entitlement_cls = mock.MagicMock() m_ent_help_info = mock.PropertyMock( return_value="Test service\nService is being tested") m_is_beta = mock.PropertyMock(return_value=is_beta) type(m_entitlement_cls).is_beta = m_is_beta m_entitlement_obj = m_entitlement_cls.return_value type(m_entitlement_obj).help_info = m_ent_help_info m_entitlement_obj.contract_status.return_value = ent_status m_entitlement_obj.user_facing_status.return_value = ( status.UserFacingStatus.ACTIVE, messages.NamedMessage("test-code", "active"), ) m_ent_name = mock.PropertyMock(return_value="test") type(m_entitlement_obj).name = m_ent_name m_ent_desc = mock.PropertyMock(return_value="description") type(m_entitlement_obj).description = m_ent_desc m_attached.return_value = True m_available_resources.return_value = [{ "name": "test", "available": True }] status_msg = "enabled" if ent_msg == "yes" else "—" ufs_call_count = 1 if ent_msg == "yes" else 0 ent_name_call_count = 2 if ent_msg == "yes" else 1 is_beta_call_count = 1 if status_msg == "enabled" else 0 expected_msgs = [ "Name:\ntest", "Entitled:\n{}".format(ent_msg), "Status:\n{}".format(status_msg), ] if is_beta and status_msg == "enabled": expected_msgs.append("Beta:\nTrue") expected_msgs.append( "Help:\nTest service\nService is being tested\n\n") expected_msg = "\n\n".join(expected_msgs) fake_stdout = io.StringIO() with mock.patch( "uaclient.status.entitlement_factory", return_value=m_entitlement_cls, ): with contextlib.redirect_stdout(fake_stdout): action_help(m_args, cfg=None) assert expected_msg.strip() == fake_stdout.getvalue().strip() assert 1 == m_service_name.call_count assert 1 == m_ent_help_info.call_count assert 1 == m_available_resources.call_count assert 1 == m_attached.call_count assert 1 == m_ent_desc.call_count assert is_beta_call_count == m_is_beta.call_count assert ent_name_call_count == m_ent_name.call_count assert 1 == m_entitlement_obj.contract_status.call_count assert ( ufs_call_count == m_entitlement_obj.user_facing_status.call_count)