Exemple #1
0
    def test_root_followed_by_nonroot(
        self,
        m_getuid,
        m_get_available_resources,
        _m_should_reboot,
        _m_remove_notice,
        tmpdir,
        FakeConfig,
    ):
        """Ensure that non-root run after root returns data"""
        cfg = UAConfig({"data_dir": tmpdir.strpath})

        # Run as root
        m_getuid.return_value = 0
        before = copy.deepcopy(status.status(cfg=cfg))

        # Replicate an attach by modifying the underlying config and confirm
        # that we see different status
        other_cfg = FakeConfig.for_attached_machine()
        cfg.write_cache("accounts", {"accounts": other_cfg.accounts})
        cfg.write_cache("machine-token", other_cfg.machine_token)
        cfg.delete_cache_key("status-cache")
        assert status._attached_status(cfg=cfg) != before

        # Run as regular user and confirm that we see the result from
        # last time we called .status()
        m_getuid.return_value = 1000
        after = status.status(cfg=cfg)

        assert before == after
Exemple #2
0
def attach_with_token(
    cfg: config.UAConfig, token: str, allow_enable: bool
) -> None:
    """
    Common functionality to take a token and attach via contract backend
    :raise UrlError: On unexpected connectivity issues to contract
        server or inability to access identity doc from metadata service.
    :raise ContractAPIError: On unexpected errors when talking to the contract
        server.
    """
    from uaclient.jobs.update_messaging import update_apt_and_motd_messages

    try:
        contract.request_updated_contract(
            cfg, token, allow_enable=allow_enable
        )
    except exceptions.UrlError as exc:
        # Persist updated status in the event of partial attach
        ua_status.status(cfg=cfg)
        update_apt_and_motd_messages(cfg)
        raise exc
    except exceptions.UserFacingError as exc:
        # Persist updated status in the event of partial attach
        ua_status.status(cfg=cfg)
        update_apt_and_motd_messages(cfg)
        raise exc

    current_iid = identity.get_instance_id()
    if current_iid:
        cfg.write_cache("instance-id", current_iid)

    update_apt_and_motd_messages(cfg)
Exemple #3
0
    def test_expires_handled_appropriately(
        self,
        m_getuid,
        _m_get_available_resources,
        _m_should_reboot,
        _m_remove_notice,
        FakeConfig,
    ):
        token = {
            "availableResources": ALL_RESOURCES_AVAILABLE,
            "machineTokenInfo": {
                "machineId": "test_machine_id",
                "accountInfo": {
                    "id": "1",
                    "name": "accountname"
                },
                "contractInfo": {
                    "name": "contractname",
                    "id": "contract-1",
                    "effectiveTo": "2020-07-18T00:00:00Z",
                    "createdAt": "2020-05-08T19:02:26Z",
                    "resourceEntitlements": [],
                    "products": ["free"],
                },
            },
        }
        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,
                                        tzinfo=datetime.timezone.utc)
        assert expected_dt == status.status(cfg=cfg)["expires"]

        # Test that the read from the status cache work properly for non-root
        # users
        m_getuid.return_value = 1000
        assert expected_dt == status.status(cfg=cfg)["expires"]
Exemple #4
0
    def test_nonroot_unattached_is_same_as_unattached_root(
        self,
        m_getuid,
        m_get_available_resources,
        _m_should_reboot,
        _m_remove_notice,
        FakeConfig,
    ):
        m_get_available_resources.return_value = [{
            "name": "esm-infra",
            "available": True
        }]
        m_getuid.return_value = 1000
        cfg = FakeConfig()
        nonroot_status = status.status(cfg=cfg)

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

        assert root_unattached_status == nonroot_status
Exemple #5
0
    def test_cache_file_is_written_world_readable(
        self,
        _m_getuid,
        _m_get_available_resources,
        _m_should_reboot,
        m_remove_notice,
        tmpdir,
    ):
        cfg = UAConfig({"data_dir": tmpdir.strpath})
        status.status(cfg=cfg)

        assert 0o644 == stat.S_IMODE(
            os.lstat(cfg.data_path("status-cache")).st_mode)

        expected_calls = [
            mock.call(
                "",
                messages.ENABLE_REBOOT_REQUIRED_TMPL.format(
                    operation="fix operation"),
            )
        ]

        assert expected_calls == m_remove_notice.call_args_list
Exemple #6
0
    def test_status_updated_when_auto_enable_fails(
        self,
        request_updated_contract,
        m_update_apt_and_motd_msgs,
        _m_get_available_resources,
        _m_should_reboot,
        _m_remove_notice,
        _m_get_uid,
        error_class,
        error_str,
        FakeConfig,
        event,
    ):
        """If auto-enable of a service fails, attach status is updated."""
        token = "contract-token"
        args = mock.MagicMock(token=token, attach_config=None)
        cfg = FakeConfig()
        status.status(cfg=cfg)  # 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
        with pytest.raises(SystemExit) as excinfo:
            main_error_handler(action_attach)(args, cfg)

        assert 1 == excinfo.value.code
        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"
        assert [mock.call(cfg)] == m_update_apt_and_motd_msgs.call_args_list
Exemple #7
0
def status(
    cfg: config.UAConfig,
    *,
    simulate_with_token: Optional[str] = None,
    show_beta: bool = False
):
    """
    Construct the current UA status dictionary.
    """
    if simulate_with_token:
        status, ret = ua_status.simulate_status(
            cfg=cfg, token=simulate_with_token, show_beta=show_beta
        )
    else:
        status = ua_status.status(cfg=cfg, show_beta=show_beta)
        ret = 0

    return status, ret
Exemple #8
0
def get_ua_info(cfg: UAConfig) -> Dict[str, Any]:
    """Returns the UA information based on the config object."""
    ua_info = {
        "attached": False,
        "enabled_services": [],
        "entitled_services": [],
    }  # type: Dict[str, Any]

    status_dict = status(cfg=cfg, show_beta=True)
    if status_dict["attached"]:
        ua_info["attached"] = True
        for service in status_dict["services"]:
            if service["name"] in ESM_SERVICES:
                if service["entitled"] == "yes":
                    ua_info["entitled_services"].append(service["name"])
                if service["status"] == "enabled":
                    ua_info["enabled_services"].append(service["name"])

    return ua_info
Exemple #9
0
    def test_root_unattached(
        self,
        _m_getuid,
        m_get_available_resources,
        _m_should_reboot,
        m_remove_notice,
        show_beta,
        expected_services,
        FakeConfig,
    ):
        """Test we get the correct status dict when unattached"""
        cfg = FakeConfig()
        m_get_available_resources.return_value = [
            {
                "name": "esm-infra",
                "available": True
            },
            {
                "name": "ros",
                "available": False
            },
        ]
        expected = copy.deepcopy(DEFAULT_STATUS)
        expected["version"] = mock.ANY
        expected["services"] = expected_services
        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)

            expected_calls = [
                mock.call(
                    "",
                    messages.ENABLE_REBOOT_REQUIRED_TMPL.format(
                        operation="fix operation"),
                )
            ]

            assert expected_calls == m_remove_notice.call_args_list
Exemple #10
0
    def test_nonroot_user_uses_cache_and_updates_if_available(
            self, m_getuid, _m_should_reboot, m_remove_notice, tmpdir):
        m_getuid.return_value = 1000

        expected_status = {"pass": True}
        cfg = UAConfig({"data_dir": tmpdir.strpath})
        cfg.write_cache("marker-reboot-cmds", "")  # To indicate a reboot reqd
        cfg.write_cache("status-cache", expected_status)

        # Even non-root users can update execution_status details
        details = messages.ENABLE_REBOOT_REQUIRED_TMPL.format(
            operation="configuration changes")
        reboot_required = UserFacingConfigStatus.REBOOTREQUIRED.value
        expected_status.update({
            "execution_status": reboot_required,
            "execution_details": details,
            "notices": [],
            "config_path": None,
            "config": {
                "data_dir": mock.ANY
            },
        })

        assert expected_status == status.status(cfg=cfg)
Exemple #11
0
    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
Exemple #12
0
    def test_root_attached(
        self,
        _m_getuid,
        m_get_avail_resources,
        _m_livepatch_status,
        _m_should_reboot,
        _m_remove_notice,
        avail_res,
        entitled_res,
        uf_entitled,
        uf_status,
        features_override,
        show_beta,
        FakeConfig,
    ):
        """Test we get the correct status dict when attached with basic conf"""
        resource_names = [resource["name"] for resource in avail_res]
        default_entitled = ContractStatus.UNENTITLED.value
        default_status = UserFacingStatus.UNAVAILABLE.value
        token = {
            "availableResources": [],
            "machineTokenInfo": {
                "machineId": "test_machine_id",
                "accountInfo": {
                    "id": "acct-1",
                    "name": "test_account",
                    "createdAt": "2019-06-14T06:45:50Z",
                    "externalAccountIDs": [{
                        "IDs": ["id1"],
                        "Origin": "AWS"
                    }],
                },
                "contractInfo": {
                    "id": "cid",
                    "name": "test_contract",
                    "createdAt": "2020-05-08T19:02:26Z",
                    "effectiveFrom": "2000-05-08T19:02:26Z",
                    "effectiveTo": "2040-05-08T19:02:26Z",
                    "resourceEntitlements": entitled_res,
                    "products": ["free"],
                },
            },
        }

        available_resource_response = [{
            "name":
            cls.name,
            "available":
            bool({
                "name": cls.name,
                "available": True
            } in avail_res),
        } for cls in ENTITLEMENT_CLASSES]
        if avail_res:
            token["availableResources"] = available_resource_response
        else:
            m_get_avail_resources.return_value = available_resource_response

        cfg = FakeConfig.for_attached_machine(machine_token=token)
        if features_override:
            cfg.override_features(features_override)

        expected_services = [{
            "description":
            cls.description,
            "entitled":
            uf_entitled if cls.name in resource_names else default_entitled,
            "name":
            cls.name,
            "status":
            uf_status if cls.name in resource_names else default_status,
            "status_details":
            mock.ANY,
            "description_override":
            None,
            "available":
            mock.ANY,
            "blocked_by": [],
        } for cls in ENTITLEMENT_CLASSES
                             if not self.check_beta(cls, show_beta, cfg)]
        expected = copy.deepcopy(DEFAULT_STATUS)
        expected.update({
            "version":
            version.get_version(features=cfg.features),
            "attached":
            True,
            "machine_id":
            "test_machine_id",
            "services":
            expected_services,
            "effective":
            datetime.datetime(2000,
                              5,
                              8,
                              19,
                              2,
                              26,
                              tzinfo=datetime.timezone.utc),
            "expires":
            datetime.datetime(2040,
                              5,
                              8,
                              19,
                              2,
                              26,
                              tzinfo=datetime.timezone.utc),
            "contract": {
                "name":
                "test_contract",
                "id":
                "cid",
                "created_at":
                datetime.datetime(2020,
                                  5,
                                  8,
                                  19,
                                  2,
                                  26,
                                  tzinfo=datetime.timezone.utc),
                "products": ["free"],
                "tech_support_level":
                "n/a",
            },
            "account": {
                "name":
                "test_account",
                "id":
                "acct-1",
                "created_at":
                datetime.datetime(2019,
                                  6,
                                  14,
                                  6,
                                  45,
                                  50,
                                  tzinfo=datetime.timezone.utc),
                "external_account_ids": [{
                    "IDs": ["id1"],
                    "Origin": "AWS"
                }],
            },
        })
        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)
        if avail_res:
            assert m_get_avail_resources.call_count == 0
        else:
            assert m_get_avail_resources.call_count == 1
        # status() idempotent
        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)
def update_status(cfg: UAConfig) -> bool:
    if cfg.is_attached:
        status(cfg=cfg)
    return True