def test_root_unattached(self, _m_getuid, m_get_available_resources):
     """Test we get the correct status dict when unattached"""
     cfg = FakeConfig({})
     m_get_available_resources.return_value = [
         {
             "name": "esm-infra",
             "available": True
         },
         {
             "name": "fips",
             "available": False
         },
     ]
     esm_desc = ENTITLEMENT_CLASS_BY_NAME["esm-infra"].description
     fips_desc = ENTITLEMENT_CLASS_BY_NAME["fips"].description
     expected = copy.deepcopy(DEFAULT_STATUS)
     expected["services"] = [
         {
             "available": "yes",
             "name": "esm-infra",
             "description": esm_desc
         },
         {
             "available": "no",
             "name": "fips",
             "description": fips_desc
         },
     ]
     assert expected == cfg.status()
示例#2
0
    def test_nonroot_without_cache_is_same_as_unattached_root(self, m_getuid):
        m_getuid.return_value = 1000
        cfg = FakeConfig()

        nonroot_status = cfg.status()

        m_getuid.return_value = 0
        root_unattached_status = cfg.status()

        assert root_unattached_status == nonroot_status
示例#3
0
 def test_root_unattached(self, _m_getuid):
     """Test we get the correct status dict when unattached"""
     cfg = FakeConfig({})
     expected = {
         "attached": False,
         "expires": status.UserFacingStatus.INAPPLICABLE.value,
         "origin": None,
         "services": [],
         "techSupportLevel": status.UserFacingStatus.INAPPLICABLE.value,
     }
     assert expected == cfg.status()
示例#4
0
    def test_assert_attached_root_exceptions(self, attached, uid,
                                             expected_exception):
        @assert_attached_root
        def test_function(args, cfg):
            return mock.sentinel.success

        if attached:
            cfg = FakeConfig.for_attached_machine()
        else:
            cfg = FakeConfig()

        with pytest.raises(expected_exception):
            with mock.patch("uaclient.cli.os.getuid", return_value=uid):
                test_function(mock.Mock(), cfg)
    def test_nonroot_unattached_is_same_as_unattached_root(
            self, m_getuid, m_get_available_resources):
        m_get_available_resources.return_value = [{
            "name": "esm-infra",
            "available": True
        }]
        m_getuid.return_value = 1000
        cfg = FakeConfig()

        nonroot_status = cfg.status()

        m_getuid.return_value = 0
        root_unattached_status = cfg.status()

        assert root_unattached_status == nonroot_status
示例#6
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
示例#7
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']
    def test_already_attached(self, _m_getuid):
        """Check that an attached machine raises AlreadyAttachedError."""
        account_name = "test_account"
        cfg = FakeConfig.for_attached_machine(account_name=account_name)

        with pytest.raises(AlreadyAttachedError):
            action_auto_attach(mock.MagicMock(), cfg)
示例#9
0
    def test_invalid_token_user_facing_error_on_invalid_token_refresh_failure(
        self, client, get_machine_id
    ):
        """When attaching, invalid token errors result in proper user error."""

        def fake_contract_client(cfg):
            fake_client = FakeContractClient(cfg)
            fake_client._responses = {
                API_V1_CONTEXT_MACHINE_TOKEN: ContractAPIError(
                    util.UrlError(
                        "Server error", code=500, url="http://me", headers={}
                    ),
                    error_response={
                        "message": "invalid token: checksum error"
                    },
                )
            }
            return fake_client

        client.side_effect = fake_contract_client
        cfg = FakeConfig()
        with pytest.raises(exceptions.UserFacingError) as exc:
            request_updated_contract(cfg, contract_token="yep")

        assert MESSAGE_ATTACH_INVALID_TOKEN == str(exc.value)
示例#10
0
 def test_token_is_a_required_argument(self, _m_getuid):
     """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)
示例#11
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)

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

        cfg = FakeConfig.for_attached_machine()
        with pytest.raises(exceptions.NonRootUserError):
            action_refresh(mock.MagicMock(), cfg)
    def test_expires_handled_appropriately(self, m_getuid,
                                           _m_get_available_resources):
        token = {
            "machineTokenInfo": {
                "accountInfo": {
                    "id": "1",
                    "name": "accountname"
                },
                "contractInfo": {
                    "name": "contractname",
                    "id": "contract-1",
                    "effectiveTo": "2020-07-18T00:00:00Z",
                    "resourceEntitlements": [],
                },
            }
        }
        cfg = FakeConfig.for_attached_machine(account_name="accountname",
                                              machine_token=token)

        # Test that root's status works as expected (including the cache write)
        m_getuid.return_value = 0
        expected_dt = datetime.datetime(2020, 7, 18, 0, 0, 0)
        assert expected_dt == cfg.status()["expires"]

        # Test that the read from the status cache work properly for non-root
        # users
        m_getuid.return_value = 1000
        assert expected_dt == cfg.status()["expires"]
def test_non_root_users_are_rejected(getuid):
    """Check that a UID != 0 will receive a message and exit non-zero"""
    getuid.return_value = 1

    cfg = FakeConfig()
    with pytest.raises(NonRootUserError):
        action_auto_attach(mock.MagicMock(), cfg)
示例#15
0
 def test_attached_reports_contract_and_service_status(
         self, m_repo_uf_status, m_livepatch_uf_status, _m_getuid,
         entitlements):
     """When attached, return contract and service user-facing status."""
     m_repo_uf_status.return_value = (
         status.UserFacingStatus.INAPPLICABLE,
         "repo details",
     )
     m_livepatch_uf_status.return_value = (
         status.UserFacingStatus.ACTIVE,
         "livepatch details",
     )
     token = {
         "machineTokenInfo": {
             "accountInfo": {
                 "id": "1",
                 "name": "accountname"
             },
             "contractInfo": {
                 "name": "contractname",
                 "resourceEntitlements": entitlements,
             },
         }
     }
     cfg = FakeConfig.for_attached_machine(account_name="accountname",
                                           machine_token=token)
     if not entitlements:
         support_level = status.UserFacingStatus.INAPPLICABLE.value
     else:
         support_level = entitlements[0]["affordances"]["supportLevel"]
     expected = {
         "attached": True,
         "account": "accountname",
         "expires": status.UserFacingStatus.INAPPLICABLE.value,
         "origin": None,
         "subscription": "contractname",
         "techSupportLevel": support_level,
         "services": [],
     }
     for cls in ENTITLEMENT_CLASSES:
         if cls.name == "livepatch":
             expected_status = status.UserFacingStatus.ACTIVE.value
             details = "livepatch details"
         else:
             expected_status = status.UserFacingStatus.INAPPLICABLE.value
             details = "repo details"
         expected["services"].append({
             "name":
             cls.name,
             "entitled":
             status.ContractStatus.UNENTITLED.value,
             "status":
             expected_status,
             "statusDetails":
             details,
         })
     assert expected == cfg.status()
     assert len(ENTITLEMENT_CLASSES) - 1 == m_repo_uf_status.call_count
     assert 1 == m_livepatch_uf_status.call_count
    def test_unattached_error_message(self, m_getuid, _m_prompt):
        """Check that root user gets unattached message."""

        m_getuid.return_value = 0
        cfg = FakeConfig()
        with pytest.raises(exceptions.UnattachedError) as err:
            action_detach(mock.MagicMock(), cfg)
        assert status.MESSAGE_UNATTACHED == err.value.msg
 def test_non_aws_cloud_type_raises_error(self, m_get_cloud_type,
                                          cloud_type):
     """Non-aws clouds will error."""
     m_get_cloud_type.return_value = cloud_type
     with pytest.raises(NonAutoAttachImageError) as excinfo:
         _get_contract_token_from_cloud_identity(FakeConfig())
     assert status.MESSAGE_UNSUPPORTED_AUTO_ATTACH_CLOUD_TYPE.format(
         cloud_type=cloud_type) == str(excinfo.value)
    def test_not_attached_errors(self, getuid, stdout):
        """Check that an unattached machine emits message and exits 1"""
        cfg = FakeConfig()

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

        assert 1 == ret
        expected_msg = status.MESSAGE_UNATTACHED
        assert mock.call(expected_msg) in stdout.write.call_args_list
示例#19
0
    def test_assert_attached_when_unattached(self, uid):
        @assert_attached()
        def test_function(args, cfg):
            pass

        cfg = FakeConfig()

        with mock.patch("uaclient.cli.os.getuid", return_value=uid):
            with pytest.raises(UnattachedError):
                test_function(mock.Mock(), cfg)
    def test_error_on_connectivity_errors(self, m_getuid,
                                          m_get_avail_resources, capsys):
        """Raise UrlError on connectivity issues"""
        m_get_avail_resources.side_effect = util.UrlError(
            socket.gaierror(-2, "Name or service not known"))

        cfg = FakeConfig()

        with pytest.raises(util.UrlError):
            action_status(mock.MagicMock(), cfg)
    def test_non_root_users_are_rejected(self, getuid, stdout):
        """Check that a UID != 0 will receive a message and exit non-zero"""
        getuid.return_value = 1

        cfg = FakeConfig.for_attached_machine()
        ret = action_refresh(mock.MagicMock(), cfg)

        assert 1 == ret
        assert (mock.call(status.MESSAGE_NONROOT_USER)
                in stdout.write.call_args_list)
示例#22
0
    def test_when_attached(self, uid):
        @assert_not_attached
        def test_function(args, cfg):
            pass

        cfg = FakeConfig.for_attached_machine()

        with mock.patch("uaclient.cli.os.getuid", return_value=uid):
            with pytest.raises(AlreadyAttachedError):
                test_function(mock.Mock(), cfg)
示例#23
0
    def test_already_attached(self, stdout):
        """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 mock.call(expected_msg) in stdout.write.call_args_list
    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]
    def test_refresh_contract_happy_path(self, request_updated_contract,
                                         getuid, capsys):
        """On success from request_updates_contract root user can refresh."""
        request_updated_contract.return_value = True

        cfg = FakeConfig.for_attached_machine()
        ret = action_refresh(mock.MagicMock(), cfg)

        assert 0 == ret
        assert status.MESSAGE_REFRESH_SUCCESS in capsys.readouterr()[0]
        assert [mock.call(cfg)] == request_updated_contract.call_args_list
    def test_refresh_contract_error_on_failure_to_update_contract(
            self, request_updated_contract, logging_error, getuid, stdout):
        """On failure in request_updates_contract emit an error."""
        request_updated_contract.return_value = False  # failure to refresh

        cfg = FakeConfig.for_attached_machine()
        ret = action_refresh(mock.MagicMock(), cfg)

        assert 1 == ret
        assert (mock.call(status.MESSAGE_REFRESH_FAILURE)
                in logging_error.call_args_list)
示例#27
0
    def test_attached_config_refresh_machine_token_and_services(
            self, client, get_machine_id, process_entitlement_delta):
        """When attached, refresh machine token and entitled services.

        Processing service deltas are processed in a sorted order based on
        name to ensure operations occur the same regardless of dict ordering.
        """

        refresh_route = API_V1_TMPL_CONTEXT_MACHINE_TOKEN_REFRESH.format(
            contract='cid', machine='mid')
        access_route_ent1 = API_V1_TMPL_RESOURCE_MACHINE_ACCESS.format(
            resource='ent1', machine='mid')

        # resourceEntitlements specifically ordered reverse alphabetically
        # to ensure proper sorting for process_contract_delta calls below
        machine_token = {
            'machineToken': 'mToken',
            'machineTokenInfo': {'contractInfo': {
                'id': 'cid', 'resourceEntitlements': [
                    {'entitled': False, 'type': 'ent2'},
                    {'entitled': True, 'type': 'ent1'}]}}}

        def fake_contract_client(cfg):
            client = FakeContractClient(cfg)
            # Note ent2 access route is not called
            client._responses = {
                refresh_route: machine_token,
                access_route_ent1: {
                    'entitlement': {
                        'entitled': True, 'type': 'ent1', 'new': 'newval'}}}
            return client

        client.side_effect = fake_contract_client
        cfg = FakeConfig.for_attached_machine(machine_token=machine_token)
        assert True is request_updated_contract(cfg)
        assert machine_token == cfg.read_cache('machine-token')
        # Redact public content
        assert (
            '<REDACTED>' == cfg.read_cache(
                'public-machine-token')['machineToken'])

        # Deltas are processed in a sorted fashion so that if enableByDefault
        # is true, the order of enablement operations is the same regardless
        # of dict key ordering.
        process_calls = [
            mock.call({'entitlement': {'entitled': True, 'type': 'ent1'}},
                      {'entitlement': {'entitled': True, 'type': 'ent1',
                                       'new': 'newval'}},
                      allow_enable=False),
            mock.call({'entitlement': {'entitled': False, 'type': 'ent2'}},
                      {'entitlement': {'entitled': False, 'type': 'ent2'}},
                      allow_enable=False)]
        assert process_calls == process_entitlement_delta.call_args_list
    def test_refresh_contract_error_on_failure_to_update_contract(
            self, request_updated_contract, logging_error, getuid):
        """On failure in request_updates_contract emit an error."""
        request_updated_contract.side_effect = exceptions.UserFacingError(
            "Failure to refresh")

        cfg = FakeConfig.for_attached_machine()

        with pytest.raises(exceptions.UserFacingError) as excinfo:
            action_refresh(mock.MagicMock(), cfg)

        assert "Failure to refresh" == excinfo.value.msg
    def test_request_resources_error_on_network_disconnected(
            self, m_request_resources):
        """Raise error get_available_resources can't contact backend"""
        cfg = FakeConfig()

        urlerror = util.UrlError(
            socket.gaierror(-2, "Name or service not known"))
        m_request_resources.side_effect = urlerror

        with pytest.raises(util.UrlError) as exc:
            get_available_resources(cfg)
        assert urlerror == exc.value
    def test_unattached_error_message(self, m_getuid, uid,
                                      expected_error_template):
        """Check that root user gets unattached message."""
        m_getuid.return_value = uid

        cfg = FakeConfig()
        with pytest.raises(exceptions.UserFacingError) as err:
            args = mock.MagicMock()
            args.name = "esm-infra"
            action_disable(args, cfg)
        assert (expected_error_template.format(
            name="esm-infra") == err.value.msg)