Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: u.pro.services.disable.v1 #3033

Merged
merged 3 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 69 additions & 64 deletions debian/po/pt_BR.po

Large diffs are not rendered by default.

133 changes: 69 additions & 64 deletions debian/po/ubuntu-pro.pot

Large diffs are not rendered by default.

203 changes: 203 additions & 0 deletions features/api_disable.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
Feature: u.pro.services.disable

Scenario Outline: u.pro.services.disable.v1 container services
Given a `<release>` `<machine_type>` machine with ubuntu-advantage-tools installed
When I apt update
# Requires attach
When I verify that running `pro api u.pro.services.disable.v1 --args service=esm-infra` `with sudo` exits `1`
Then API errors field output is:
"""
[
{
"code": "unattached",
"meta": {},
"title": "This machine is not attached to an Ubuntu Pro subscription.\nSee https://ubuntu.com/pro"
}
]
"""
When I attach `contract_token` with sudo
# Requires root
When I verify that running `pro api u.pro.services.disable.v1 --args service=esm-infra` `as non-root` exits `1`
Then API errors field output is:
"""
[
{
"code": "nonroot-user",
"meta": {},
"title": "This command must be run as root (try using sudo)."
}
]
"""
# Invalid service name
When I verify that running `pro api u.pro.services.disable.v1 --args service=invalid` `with sudo` exits `1`
Then API errors field output is:
"""
[
{
"code": "entitlement-not-found",
"meta": {
"entitlement_name": "invalid"
},
"title": "could not find entitlement named \"invalid\""
}
]
"""
# Basic disable
When I run `pro api u.pro.services.disable.v1 --args service=esm-infra` with sudo
Then API data field output is:
"""
{
"attributes": {
"disabled": [
"esm-infra"
]
},
"meta": {
"environment_vars": []
},
"type": "DisableService"
}
"""
Then I verify that `esm-infra` is disabled
# Disable already disabled service succeeds
When I run `pro api u.pro.services.disable.v1 --args service=esm-infra` with sudo
Then API data field output is:
"""
{
"attributes": {
"disabled": []
},
"meta": {
"environment_vars": []
},
"type": "DisableService"
}
"""
# disables dependent services
When I run `pro enable ros-updates --assume-yes` with sudo
When I run `pro api u.pro.services.disable.v1 --args service=esm-apps` with sudo
Then API data field output is:
"""
{
"attributes": {
"disabled": [
"esm-apps",
"ros",
"ros-updates"
]
},
"meta": {
"environment_vars": []
},
"type": "DisableService"
}
"""
# purge works and post enable messages work
When I apt install `curl`
When I run `apt-cache policy curl` as non-root
Then stdout matches regexp:
"""
\*\*\* <curl_version>\+esm.* 510
"""
When I run `pro api u.pro.services.disable.v1 --data '{"service": "esm-infra", "purge": true}'` with sudo
Then API data field output is:
"""
{
"attributes": {
"disabled": [
"esm-infra"
]
},
"meta": {
"environment_vars": []
},
"type": "DisableService"
}
"""
When I run `apt-cache policy curl` as non-root
Then stdout contains substring:
"""
*** <curl_version> 500
"""

Examples:
| release | machine_type | curl_version |
| xenial | lxd-container | 7.47.0-1ubuntu2.19 |
| bionic | lxd-container | 7.58.0-2ubuntu3.24 |

Scenario Outline: u.pro.services.disable.v1 vm services
Given a `<release>` `<machine_type>` machine with ubuntu-advantage-tools installed
When I apt update
And I attach `contract_token` with sudo
# Basic disable
And I run `pro api u.pro.services.disable.v1 --args service=livepatch` with sudo
Then API data field output is:
"""
{
"attributes": {
"disabled": [
"livepatch"
]
},
"meta": {
"environment_vars": []
},
"type": "DisableService"
}
"""
# fails when purge not supported
When I run `pro enable realtime-kernel --access-only` with sudo
When I verify that running `pro api u.pro.services.disable.v1 --data '{"service": "realtime-kernel", "purge": true}'` `with sudo` exits `1`
Then API errors field output is:
"""
[
{
"code": "entitlement-not-disabled",
"meta": {
"reason": {
"additional_info": null,
"code": "disable-purge-not-supported",
"title": "Real-time kernel does not support being disabled with --purge"
}
},
"title": "failed to disable realtime-kernel"
}
]
"""

Examples:
| release | machine_type |
| jammy | lxd-vm |

Scenario Outline: u.pro.services.disable.v1 with progress
Given a `<release>` `<machine_type>` machine with ubuntu-advantage-tools installed
When I run `apt-get update` with sudo
And I attach `contract_token` with sudo
# Basic disable
And I run shell command `pro api u.pro.services.disable.v1 --show-progress --args service=esm-infra` with sudo
Then stdout contains substring:
"""
{"total_steps": 2, "done_steps": 0, "previous_step_message": null, "current_step_message": "Removing APT access to Ubuntu Pro: ESM Infra"}
{"total_steps": 2, "done_steps": 1, "previous_step_message": "Removing APT access to Ubuntu Pro: ESM Infra", "current_step_message": "Updating package lists"}
{"total_steps": 2, "done_steps": 2, "previous_step_message": "Updating package lists", "current_step_message": null}
{"_schema_version": "v1", "data": {"attributes": {"disabled": ["esm-infra"]}, "meta": {"environment_vars": []}, "type": "DisableService"}, "errors": [], "result": "success"
"""
# Disabling multiple services shows steps correctly
When I run `pro enable ros-updates --assume-yes` with sudo
When I run `pro api u.pro.services.disable.v1 --show-progress --args service=esm-apps` with sudo
Then stdout contains substring:
"""
{"total_steps": 6, "done_steps": 0, "previous_step_message": null, "current_step_message": "Removing APT access to ROS ESM All Updates"}
{"total_steps": 6, "done_steps": 1, "previous_step_message": "Removing APT access to ROS ESM All Updates", "current_step_message": "Updating package lists"}
{"total_steps": 6, "done_steps": 2, "previous_step_message": "Updating package lists", "current_step_message": "Removing APT access to ROS ESM Security Updates"}
{"total_steps": 6, "done_steps": 3, "previous_step_message": "Removing APT access to ROS ESM Security Updates", "current_step_message": "Updating package lists"}
{"total_steps": 6, "done_steps": 4, "previous_step_message": "Updating package lists", "current_step_message": "Removing APT access to Ubuntu Pro: ESM Apps"}
{"total_steps": 6, "done_steps": 5, "previous_step_message": "Removing APT access to Ubuntu Pro: ESM Apps", "current_step_message": "Updating package lists"}
{"total_steps": 6, "done_steps": 6, "previous_step_message": "Updating package lists", "current_step_message": null}
{"_schema_version": "v1", "data": {"attributes": {"disabled": ["esm-apps", "ros", "ros-updates"]}, "meta": {"environment_vars": []}, "type": "DisableService"}, "errors": [], "result": "success"
"""

Examples:
| release | machine_type |
| xenial | lxd-container |
| bionic | lxd-container |
1 change: 1 addition & 0 deletions uaclient/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"u.pro.security.status.livepatch_cves.v1",
"u.pro.security.status.reboot_required.v1",
"u.pro.services.dependencies.v1",
"u.pro.services.disable.v1",
"u.pro.services.enable.v1",
"u.pro.status.enabled_services.v1",
"u.pro.status.is_attached.v1",
Expand Down
2 changes: 2 additions & 0 deletions uaclient/api/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
AlreadyAttachedError,
ConnectivityError,
ContractAPIError,
EntitlementNotDisabledError,
EntitlementNotEnabledError,
EntitlementNotFoundError,
EntitlementsNotEnabledError,
Expand Down Expand Up @@ -34,6 +35,7 @@
"UserFacingError",
"EntitlementsNotEnabledError",
"EntitlementNotEnabledError",
"EntitlementNotDisabledError",
"IncompatibleServiceStopsEnable",
"RequiredServiceStopsEnable",
]
Expand Down
166 changes: 166 additions & 0 deletions uaclient/api/tests/test_api_u_pro_services_disable_v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import mock
import pytest

from uaclient.api import exceptions
from uaclient.api.u.pro.services.disable.v1 import (
DisableOptions,
DisableResult,
_disable,
)
from uaclient.testing.helpers import does_not_raise

M_PATH = "uaclient.api.u.pro.services.disable.v1."


class TestDisable:
@pytest.mark.parametrize(
[
"options",
"we_are_currently_root",
"is_attached",
"enabled_services_names_before",
"enabled_services_names_after",
"disable_result",
"expected_raises",
"expected_result",
],
[
# not root
(
DisableOptions(service="s1"),
False,
False,
None,
None,
None,
pytest.raises(exceptions.NonRootUserError),
None,
),
# not attached
(
DisableOptions(service="s1"),
True,
False,
None,
None,
None,
pytest.raises(exceptions.UnattachedError),
None,
),
# generic disable failure
(
DisableOptions(service="s1"),
True,
True,
["s1"],
None,
(False, None),
pytest.raises(exceptions.EntitlementNotDisabledError),
None,
),
# success
(
DisableOptions(service="s1"),
True,
True,
["s1"],
[],
(True, None),
does_not_raise(),
DisableResult(
disabled=["s1"],
),
),
# success already disabled
(
DisableOptions(service="s1"),
True,
True,
[],
None,
None,
does_not_raise(),
DisableResult(
disabled=[],
),
),
# success with additional disablements
(
DisableOptions(service="s1"),
True,
True,
["s1", "s2", "s3"],
["s2"],
(True, None),
does_not_raise(),
DisableResult(
disabled=["s1", "s3"],
),
),
],
)
@mock.patch(M_PATH + "status.status")
@mock.patch(M_PATH + "lock.clear_lock_file_if_present")
@mock.patch(M_PATH + "lock.RetryLock")
@mock.patch(M_PATH + "entitlements.entitlement_factory")
@mock.patch(M_PATH + "_enabled_services_names")
@mock.patch(M_PATH + "_is_attached")
@mock.patch(M_PATH + "util.we_are_currently_root")
def test_disable(
self,
m_we_are_currently_root,
m_is_attached,
m_enabled_services_names,
m_entitlement_factory,
m_spin_lock,
m_clear_lock_file_if_present,
m_status,
options,
we_are_currently_root,
is_attached,
enabled_services_names_before,
enabled_services_names_after,
disable_result,
expected_raises,
expected_result,
FakeConfig,
):
m_we_are_currently_root.return_value = we_are_currently_root
m_is_attached.return_value = mock.MagicMock(is_attached=is_attached)
m_enabled_services_names.side_effect = [
enabled_services_names_before,
enabled_services_names_after,
]
m_ent_class = m_entitlement_factory.return_value
m_ent = m_ent_class.return_value
m_ent_variant = m_ent.enabled_variant
m_ent_variant.disable.return_value = disable_result

cfg = FakeConfig()

actual_result = None
with expected_raises:
actual_result = _disable(
options, cfg, progress_object=mock.MagicMock()
)

assert actual_result == expected_result

if expected_result is not None and len(expected_result.disabled) > 0:
assert m_entitlement_factory.call_args_list == [
mock.call(
cfg=cfg,
name=options.service,
)
]
assert m_ent_class.call_args_list == [
mock.call(
cfg,
assume_yes=True,
called_name=options.service,
purge=options.purge,
)
]
assert m_ent_variant.disable.call_args_list == [
mock.call(mock.ANY)
]
Empty file.
Loading
Loading