コード例 #1
0
    def test_already_attached(self, _m_getuid, capsys, FakeConfig, event):
        """Check that an already-attached machine emits message and exits 0"""
        account_name = "test_account"
        cfg = FakeConfig.for_attached_machine(account_name=account_name)

        with pytest.raises(AlreadyAttachedError):
            action_attach(mock.MagicMock(), cfg=cfg)

        with pytest.raises(SystemExit):
            with mock.patch.object(
                event, "_event_logger_mode", event_logger.EventLoggerMode.JSON
            ):
                main_error_handler(action_attach)(mock.MagicMock(), cfg)

        msg = messages.ALREADY_ATTACHED.format(account_name=account_name)
        expected = {
            "_schema_version": event_logger.JSON_SCHEMA_VERSION,
            "result": "failure",
            "errors": [
                {
                    "message": msg.msg,
                    "message_code": msg.name,
                    "service": None,
                    "type": "system",
                }
            ],
            "failed_services": [],
            "needs_reboot": False,
            "processed_services": [],
            "warnings": [],
        }
        assert expected == json.loads(capsys.readouterr()[0])
コード例 #2
0
def test_non_root_users_are_rejected(getuid, FakeConfig, capsys, event):
    """Check that a UID != 0 will receive a message and exit non-zero"""
    getuid.return_value = 1

    cfg = FakeConfig()
    with pytest.raises(NonRootUserError):
        action_attach(mock.MagicMock(), cfg)

    with pytest.raises(SystemExit):
        with mock.patch.object(
            event, "_event_logger_mode", event_logger.EventLoggerMode.JSON
        ):
            main_error_handler(action_attach)(mock.MagicMock(), cfg)

    expected = {
        "_schema_version": event_logger.JSON_SCHEMA_VERSION,
        "result": "failure",
        "errors": [
            {
                "message": messages.NONROOT_USER.msg,
                "message_code": messages.NONROOT_USER.name,
                "service": None,
                "type": "system",
            }
        ],
        "failed_services": [],
        "needs_reboot": False,
        "processed_services": [],
        "warnings": [],
    }
    assert expected == json.loads(capsys.readouterr()[0])
コード例 #3
0
    def test_already_attached(self, _m_getuid, capsys, FakeConfig):
        """Check that an already-attached machine emits message and exits 0"""
        account_name = "test_account"
        cfg = FakeConfig.for_attached_machine(account_name=account_name)

        with pytest.raises(AlreadyAttachedError):
            action_attach(mock.MagicMock(), cfg)
コード例 #4
0
def test_non_root_users_are_rejected(getuid, FakeConfig):
    """Check that a UID != 0 will receive a message and exit non-zero"""
    getuid.return_value = 1

    cfg = FakeConfig()
    with pytest.raises(NonRootUserError):
        action_attach(mock.MagicMock(), cfg)
コード例 #5
0
 def test_token_is_a_required_argument(self, _m_getuid, FakeConfig):
     """When missing the required token argument, raise a UserFacingError"""
     args = mock.MagicMock()
     args.token = None
     with pytest.raises(UserFacingError) as e:
         action_attach(args, FakeConfig())
     assert status.MESSAGE_ATTACH_REQUIRES_TOKEN == str(e.value)
コード例 #6
0
    def test_attach_config_enable_services(
        self,
        _m_daemon_stop,
        m_status,
        m_format_tabular,
        m_handle_unicode,
        m_attach_with_token,
        m_enable,
        _m_getuid,
        auto_enable,
        FakeConfig,
        event,
    ):
        m_status.return_value = ("status", 0)
        m_format_tabular.return_value = "status"
        m_handle_unicode.return_value = "status"

        cfg = FakeConfig()
        args = mock.MagicMock(
            token=None,
            attach_config=FakeFile(
                yaml.dump({"token": "faketoken", "enable_services": ["cis"]})
            ),
            auto_enable=auto_enable,
        )
        action_attach(args, cfg=cfg)
        assert [
            mock.call(mock.ANY, token="faketoken", allow_enable=False)
        ] == m_attach_with_token.call_args_list
        if auto_enable:
            assert [
                mock.call(cfg, "cis", assume_yes=True, allow_beta=True)
            ] == m_enable.call_args_list
        else:
            assert [] == m_enable.call_args_list

        args.attach_config = FakeFile(
            yaml.dump({"token": "faketoken", "enable_services": ["cis"]})
        )

        fake_stdout = io.StringIO()
        with contextlib.redirect_stdout(fake_stdout):
            with mock.patch.object(
                event, "_event_logger_mode", event_logger.EventLoggerMode.JSON
            ):
                main_error_handler(action_attach)(args, cfg)

        expected = {
            "_schema_version": event_logger.JSON_SCHEMA_VERSION,
            "result": "success",
            "errors": [],
            "failed_services": [],
            "needs_reboot": False,
            "processed_services": ["cis"] if auto_enable else [],
            "warnings": [],
        }
        assert expected == json.loads(fake_stdout.getvalue())
コード例 #7
0
 def test_attach_config_and_token_mutually_exclusive(
     self, _m_getuid, FakeConfig
 ):
     args = mock.MagicMock(
         token="something", attach_config=FakeFile("something")
     )
     cfg = FakeConfig()
     with pytest.raises(UserFacingError) as e:
         action_attach(args, cfg=cfg)
     assert e.value.msg == messages.ATTACH_TOKEN_ARG_XOR_CONFIG.msg
コード例 #8
0
    def test_lock_file_exists(self, m_subp, _m_getuid, capsys, FakeConfig):
        """Check when an operation holds a lock file, attach cannot run."""
        cfg = FakeConfig()

        cfg.write_cache("lock", "123:ua disable")
        with pytest.raises(LockHeldError) as exc_info:
            action_attach(mock.MagicMock(), cfg)
        assert [mock.call(["ps", "123"])] == m_subp.call_args_list
        assert ("Unable to perform: ua attach.\n"
                "Operation in progress: ua disable (pid:123)"
                ) == exc_info.value.msg
コード例 #9
0
 def test_token_from_attach_config(
     self, m_attach_with_token, _m_post_cli_attach, _m_getuid, FakeConfig
 ):
     args = mock.MagicMock(
         token=None,
         attach_config=FakeFile(yaml.dump({"token": "faketoken"})),
     )
     cfg = FakeConfig()
     action_attach(args, cfg=cfg)
     assert [
         mock.call(mock.ANY, token="faketoken", allow_enable=True)
     ] == m_attach_with_token.call_args_list
コード例 #10
0
    def test_attach_config_invalid_config(
        self, _m_getuid, FakeConfig, capsys, event
    ):
        args = mock.MagicMock(
            token=None,
            attach_config=FakeFile(
                yaml.dump({"token": "something", "enable_services": "cis"}),
                name="fakename",
            ),
        )
        cfg = FakeConfig()
        with pytest.raises(UserFacingError) as e:
            action_attach(args, cfg=cfg)
        assert "Error while reading fakename: " in e.value.msg

        args.attach_config = FakeFile(
            yaml.dump({"token": "something", "enable_services": "cis"}),
            name="fakename",
        )
        with pytest.raises(SystemExit):
            with mock.patch.object(
                event, "_event_logger_mode", event_logger.EventLoggerMode.JSON
            ):
                main_error_handler(action_attach)(args, cfg)

        expected_message = messages.ATTACH_CONFIG_READ_ERROR.format(
            config_name="fakename",
            error=(
                "Got value with "
                'incorrect type for field\n"enable_services": '
                "Expected value with type list but got value: 'cis'"
            ),
        )

        expected = {
            "_schema_version": event_logger.JSON_SCHEMA_VERSION,
            "result": "failure",
            "errors": [
                {
                    "message": expected_message.msg,
                    "message_code": expected_message.name,
                    "service": None,
                    "type": "system",
                }
            ],
            "failed_services": [],
            "needs_reboot": False,
            "processed_services": [],
            "warnings": [],
        }
        assert expected == json.loads(capsys.readouterr()[0])
コード例 #11
0
    def test_auto_enable_passed_through_to_request_updated_contract(
            self, _m_getuid, auto_enable):
        args = mock.MagicMock(auto_enable=auto_enable)

        def fake_contract_updates(cfg, contract_token, allow_enable):
            cfg.write_cache("machine-token", BASIC_MACHINE_TOKEN)
            return True

        with mock.patch(M_PATH + "contract.request_updated_contract") as m_ruc:
            m_ruc.side_effect = fake_contract_updates
            action_attach(args, FakeConfig())

        expected_call = mock.call(mock.ANY, mock.ANY, allow_enable=auto_enable)
        assert [expected_call] == m_ruc.call_args_list
コード例 #12
0
    def test_happy_path_without_token_arg(self, action_status, contract_client,
                                          discharge_root_macaroon,
                                          request_updated_contract):
        """A mock-heavy test for the happy path without an argument"""
        # TODO: Improve this test with less general mocking and more
        # post-conditions
        bound_macaroon = b'bound_bytes_macaroon'
        discharge_root_macaroon.return_value = bound_macaroon
        args = mock.MagicMock(token=None)
        cfg = FakeConfig.with_account()
        machine_token = {
            'machineTokenInfo': {
                'contractInfo': {
                    'name': 'mycontract',
                    'resourceEntitlements': []
                }
            }
        }

        def fake_contract_updates(cfg, contract_token, allow_enable):
            cfg.write_cache('machine-token', machine_token)
            return True

        request_updated_contract.side_effect = fake_contract_updates
        ret = action_attach(args, cfg)

        assert 0 == ret
        assert 1 == action_status.call_count
        expected_macaroon = bound_macaroon.decode('utf-8')
        assert expected_macaroon == cfg._cache_contents['bound-macaroon']
コード例 #13
0
    def test_status_updated_when_auto_enable_fails(
        self,
        request_updated_contract,
        _m_get_available_resources,
        _m_get_uid,
        error_class,
        error_str,
        expected_log,
        caplog_text,
        FakeConfig,
    ):
        """If auto-enable of a service fails, attach status is updated."""
        token = "contract-token"
        args = mock.MagicMock(token=token)
        cfg = FakeConfig()
        cfg.status()  # persist unattached status
        # read persisted status cache from disk
        orig_unattached_status = cfg.read_cache("status-cache")

        def fake_request_updated_contract(cfg, contract_token, allow_enable):
            cfg.write_cache("machine-token", ENTITLED_MACHINE_TOKEN)
            raise error_class(error_str)

        request_updated_contract.side_effect = fake_request_updated_contract
        ret = action_attach(args, cfg)
        assert 1 == ret
        assert cfg.is_attached
        # Assert updated status cache is written to disk
        assert orig_unattached_status != cfg.read_cache(
            "status-cache"
        ), "Did not persist on disk status during attach failure"
        logs = caplog_text()
        assert expected_log in logs
コード例 #14
0
    def test_happy_path_with_token_arg(self, action_status,
                                       contract_machine_attach,
                                       discharge_root_macaroon):
        """A mock-heavy test for the happy path with the contract token arg"""
        # TODO: Improve this test with less general mocking and more
        # post-conditions
        token = 'contract-token'
        args = mock.MagicMock(token=token)
        cfg = FakeConfig.with_account()
        machine_token = {
            'machineTokenInfo': {
                'contractInfo': {
                    'name': 'mycontract',
                    'resourceEntitlements': []
                }
            }
        }

        def fake_contract_attach(contract_token):
            cfg.write_cache('machine-token', machine_token)
            return machine_token

        contract_machine_attach.side_effect = fake_contract_attach

        ret = action_attach(args, cfg)

        assert 0 == ret
        assert 1 == action_status.call_count
        expected_calls = [mock.call(contract_token=token)]
        assert expected_calls == contract_machine_attach.call_args_list
        assert 0 == discharge_root_macaroon.call_count
コード例 #15
0
    def test_lock_file_exists(
        self, m_subp, _m_getuid, capsys, FakeConfig, event
    ):
        """Check when an operation holds a lock file, attach cannot run."""
        cfg = FakeConfig()
        cfg.write_cache("lock", "123:ua disable")
        with pytest.raises(LockHeldError) as exc_info:
            action_attach(mock.MagicMock(), cfg=cfg)
        assert [mock.call(["ps", "123"])] == m_subp.call_args_list
        assert (
            "Unable to perform: ua attach.\n"
            "Operation in progress: ua disable (pid:123)"
        ) == exc_info.value.msg

        with pytest.raises(SystemExit):
            with mock.patch.object(
                event, "_event_logger_mode", event_logger.EventLoggerMode.JSON
            ):
                with mock.patch.object(
                    cfg, "check_lock_info"
                ) as m_check_lock_info:
                    m_check_lock_info.return_value = (1, "lock_holder")
                    main_error_handler(action_attach)(mock.MagicMock(), cfg)

        expected_msg = messages.LOCK_HELD_ERROR.format(
            lock_request="ua attach", lock_holder="lock_holder", pid=1
        )
        expected = {
            "_schema_version": event_logger.JSON_SCHEMA_VERSION,
            "result": "failure",
            "errors": [
                {
                    "message": expected_msg.msg,
                    "message_code": expected_msg.name,
                    "service": None,
                    "type": "system",
                }
            ],
            "failed_services": [],
            "needs_reboot": False,
            "processed_services": [],
            "warnings": [],
        }
        assert expected == json.loads(capsys.readouterr()[0])
コード例 #16
0
def test_non_root_users_are_rejected(stdout, getuid):
    """Check that a UID != 0 will receive a message and exit non-zero"""
    getuid.return_value = 1

    cfg = TestConfig()
    ret = action_attach(mock.MagicMock(), cfg)

    assert 1 == ret
    assert (mock.call(status.MESSAGE_NONROOT_USER)
            in stdout.write.call_args_list)
コード例 #17
0
    def test_already_attached(self, stdout):
        """Check that an already-attached machine emits message and exits 0"""
        account_name = 'test_account'
        cfg = TestConfig.for_attached_machine(account_name=account_name)

        ret = action_attach(mock.MagicMock(), cfg)

        assert 0 == ret
        expected_msg = "This machine is already attached to '{}'.".format(
            account_name)
        assert mock.call(expected_msg) in stdout.write.call_args_list
コード例 #18
0
    def test_already_attached(self, _m_getuid, capsys):
        """Check that an already-attached machine emits message and exits 0"""
        account_name = "test_account"
        cfg = FakeConfig.for_attached_machine(account_name=account_name)

        ret = action_attach(mock.MagicMock(), cfg)

        assert 0 == ret
        expected_msg = "This machine is already attached to '{}'.".format(
            account_name)
        assert expected_msg in capsys.readouterr()[0]
コード例 #19
0
    def test_token_is_a_required_argument(
        self, _m_getuid, FakeConfig, capsys, event
    ):
        """When missing the required token argument, raise a UserFacingError"""
        args = mock.MagicMock(token=None, attach_config=None)
        cfg = FakeConfig()
        with pytest.raises(UserFacingError) as e:
            action_attach(args, cfg=cfg)
        assert messages.ATTACH_REQUIRES_TOKEN.msg == str(e.value.msg)

        args = mock.MagicMock()
        args.token = None
        args.attach_config = None
        with pytest.raises(SystemExit):
            with mock.patch.object(
                event, "_event_logger_mode", event_logger.EventLoggerMode.JSON
            ):
                with mock.patch.object(
                    cfg, "check_lock_info"
                ) as m_check_lock_info:
                    m_check_lock_info.return_value = (0, "lock_holder")
                    main_error_handler(action_attach)(args, cfg)

        expected_msg = messages.ATTACH_REQUIRES_TOKEN
        expected = {
            "_schema_version": event_logger.JSON_SCHEMA_VERSION,
            "result": "failure",
            "errors": [
                {
                    "message": expected_msg.msg,
                    "message_code": expected_msg.name,
                    "service": None,
                    "type": "system",
                }
            ],
            "failed_services": [],
            "needs_reboot": False,
            "processed_services": [],
            "warnings": [],
        }
        assert expected == json.loads(capsys.readouterr()[0])
コード例 #20
0
    def test_no_discharged_macaroon(self, action_status, contract_client,
                                    discharge_root_macaroon, stdout):
        """If we can't discharge the root macaroon, fail gracefully."""
        discharge_root_macaroon.return_value = None
        args = mock.MagicMock(token=None)
        cfg = TestConfig.with_account()

        ret = action_attach(args, cfg)

        assert 1 == ret
        expected_msg = ('Could not attach machine. Unable to obtain'
                        ' authenticated user token')
        assert mock.call(expected_msg) in stdout.write.call_args_list
コード例 #21
0
    def test_auto_enable_passed_through_to_request_updated_contract(
        self,
        m_update_apt_and_motd_msgs,
        _m_get_available_resources,
        _m_should_reboot,
        _m_remove_notice,
        _m_get_uid,
        auto_enable,
        FakeConfig,
    ):
        args = mock.MagicMock(auto_enable=auto_enable, attach_config=None)

        def fake_contract_updates(cfg, contract_token, allow_enable):
            cfg.write_cache("machine-token", BASIC_MACHINE_TOKEN)
            return True

        cfg = FakeConfig()
        with mock.patch(M_PATH + "contract.request_updated_contract") as m_ruc:
            m_ruc.side_effect = fake_contract_updates
            action_attach(args, cfg)

        expected_call = mock.call(mock.ANY, mock.ANY, allow_enable=auto_enable)
        assert [expected_call] == m_ruc.call_args_list
        assert [mock.call(cfg)] == m_update_apt_and_motd_msgs.call_args_list
コード例 #22
0
    def test_happy_path_without_token_arg(self, action_status, contract_client,
                                          discharge_root_macaroon):
        """A mock-heavy test for the happy path without an argument"""
        # TODO: Improve this test with less general mocking and more
        # post-conditions
        bound_macaroon = b'bound_bytes_macaroon'
        discharge_root_macaroon.return_value = bound_macaroon
        args = mock.MagicMock(token=None)
        cfg = TestConfig.with_account()

        ret = action_attach(args, cfg)

        assert 0 == ret
        assert 1 == action_status.call_count
        expected_macaroon = bound_macaroon.decode('utf-8')
        assert expected_macaroon == cfg._cache_contents['bound-macaroon']
コード例 #23
0
    def test_happy_path_with_token_arg(self, action_status,
                                       contract_machine_attach,
                                       discharge_root_macaroon):
        """A mock-heavy test for the happy path with the contract token arg"""
        # TODO: Improve this test with less general mocking and more
        # post-conditions
        token = 'contract-token'
        args = mock.MagicMock(token=token)
        cfg = TestConfig.with_account()

        ret = action_attach(args, cfg)

        assert 0 == ret
        assert 1 == action_status.call_count
        expected_calls = [mock.call(contract_token=token)]
        assert expected_calls == contract_machine_attach.call_args_list
        assert 0 == discharge_root_macaroon.call_count
コード例 #24
0
    def test_happy_path_with_token_arg(self, action_status,
                                       contract_machine_attach, _m_getuid):
        """A mock-heavy test for the happy path with the contract token arg"""
        # TODO: Improve this test with less general mocking and more
        # post-conditions
        token = "contract-token"
        args = mock.MagicMock(token=token)
        cfg = FakeConfig()

        def fake_contract_attach(contract_token):
            cfg.write_cache("machine-token", BASIC_MACHINE_TOKEN)
            return BASIC_MACHINE_TOKEN

        contract_machine_attach.side_effect = fake_contract_attach

        ret = action_attach(args, cfg)

        assert 0 == ret
        assert 1 == action_status.call_count
        expected_calls = [mock.call(contract_token=token)]
        assert expected_calls == contract_machine_attach.call_args_list
コード例 #25
0
def _run_ua_attach(cfg: UAConfig, token: str) -> bool:
    """Attach to a UA subscription with a given token.

    :return: True if attach performed without errors.
    """
    import argparse

    from uaclient import cli

    print(colorize_commands([["ua", "attach", token]]))
    try:
        ret_code = cli.action_attach(
            argparse.Namespace(
                token=token, auto_enable=True, format="cli", attach_config=None
            ),
            cfg,
        )
        return ret_code == 0
    except exceptions.UserFacingError as err:
        print(err.msg)
        return False
コード例 #26
0
    def test_happy_path_with_token_arg(
        self,
        m_format_tabular,
        m_status,
        contract_machine_attach,
        m_update_apt_and_motd_msgs,
        _m_should_reboot,
        _m_remove_notice,
        _m_getuid,
        FakeConfig,
        event,
    ):
        """A mock-heavy test for the happy path with the contract token arg"""
        # TODO: Improve this test with less general mocking and more
        # post-conditions
        token = "contract-token"
        args = mock.MagicMock(token=token, attach_config=None)
        cfg = FakeConfig()

        def fake_contract_attach(contract_token):
            cfg.write_cache("machine-token", BASIC_MACHINE_TOKEN)
            return BASIC_MACHINE_TOKEN

        contract_machine_attach.side_effect = fake_contract_attach

        ret = action_attach(args, cfg)

        assert 0 == ret
        assert 1 == m_status.call_count
        assert 1 == m_format_tabular.call_count
        expected_calls = [mock.call(contract_token=token)]
        assert expected_calls == contract_machine_attach.call_args_list
        assert [mock.call(cfg)] == m_update_apt_and_motd_msgs.call_args_list

        # We need to do that since all config objects in this
        # test will share the same data dir. Since this will
        # test a successful attach, in the end we write a machine token
        # file, which will make all other cfg objects here to report
        # as attached
        cfg.delete_cache()

        cfg = FakeConfig()
        args = mock.MagicMock(token=token, attach_config=None)
        with mock.patch.object(
            event, "_event_logger_mode", event_logger.EventLoggerMode.JSON
        ):
            with mock.patch.object(
                cfg, "check_lock_info"
            ) as m_check_lock_info:
                m_check_lock_info.return_value = (0, "lock_holder")
                fake_stdout = io.StringIO()
                with contextlib.redirect_stdout(fake_stdout):
                    main_error_handler(action_attach)(args, cfg)

        expected = {
            "_schema_version": event_logger.JSON_SCHEMA_VERSION,
            "result": "success",
            "errors": [],
            "failed_services": [],
            "needs_reboot": False,
            "processed_services": [],
            "warnings": [],
        }
        assert expected == json.loads(fake_stdout.getvalue())