Skip to content

Commit

Permalink
api: u.pro.services.disable.v1
Browse files Browse the repository at this point in the history
  • Loading branch information
orndorffgrant committed Apr 3, 2024
1 parent 3f16ee6 commit 14d1c5c
Show file tree
Hide file tree
Showing 11 changed files with 511 additions and 7 deletions.
189 changes: 189 additions & 0 deletions features/api_disable.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
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)."
}
]
"""
# 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 |
4 changes: 2 additions & 2 deletions features/api_enable.feature
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ Feature: u.pro.services.enable
When I run `pro api u.pro.services.enable.v1 --show-progress --data '{"service": "realtime-kernel", "variant": "intel-iotg"}'` with sudo
Then stdout contains substring:
"""
{"total_steps": 4, "done_steps": 0, "previous_step_message": null, "current_step_message": "Disabling incompatible service: Livepatch"}
{"total_steps": 4, "done_steps": 1, "previous_step_message": "Disabling incompatible service: Livepatch", "current_step_message": "Configuring APT access to Real-time Intel IOTG Kernel"}
{"total_steps": 4, "done_steps": 0, "previous_step_message": null, "current_step_message": "Executing `/snap/bin/canonical-livepatch disable`"}
{"total_steps": 4, "done_steps": 1, "previous_step_message": "Executing `/snap/bin/canonical-livepatch disable`", "current_step_message": "Configuring APT access to Real-time Intel IOTG Kernel"}
{"total_steps": 4, "done_steps": 2, "previous_step_message": "Configuring APT access to Real-time Intel IOTG Kernel", "current_step_message": "Updating Real-time Intel IOTG Kernel package lists"}
{"total_steps": 4, "done_steps": 3, "previous_step_message": "Updating Real-time Intel IOTG Kernel package lists", "current_step_message": "Installing Real-time Intel IOTG Kernel packages"}
{"total_steps": 4, "done_steps": 4, "previous_step_message": "Installing Real-time Intel IOTG Kernel packages", "current_step_message": null}
Expand Down
1 change: 1 addition & 0 deletions uaclient/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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
164 changes: 164 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,164 @@
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 + "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,
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

0 comments on commit 14d1c5c

Please sign in to comment.