-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ff75677
commit b5f5e7c
Showing
9 changed files
with
427 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
Feature: u.pro.services.enable | ||
|
||
Scenario Outline: u.pro.services.enable.v1 container services | ||
Given a `<release>` `<machine_type>` machine with ubuntu-advantage-tools installed | ||
When I run `apt-get update` with sudo | ||
And I apt install `jq` | ||
And I attach `contract_token` with sudo and options `--no-auto-enable` | ||
|
||
# Basic enable | ||
And I run shell command `pro api u.pro.services.enable.v1 --args service=esm-infra | jq .data.attributes` with sudo | ||
Then I will see the following on stdout: | ||
""" | ||
{ | ||
"disabled": [], | ||
"enabled": [ | ||
"esm-infra" | ||
], | ||
"messages": [], | ||
"reboot_required": false | ||
} | ||
""" | ||
When I run shell command `pro api u.pro.status.enabled_services.v1 | jq .data.attributes.enabled_services ` with sudo | ||
Then stdout contains substring: | ||
""" | ||
esm-infra | ||
""" | ||
# Enable already enabled service succeeds | ||
When I run shell command `pro api u.pro.services.enable.v1 --args service=esm-infra | jq .data.attributes` with sudo | ||
Then I will see the following on stdout: | ||
""" | ||
{ | ||
"disabled": [], | ||
"enabled": [], | ||
"messages": [], | ||
"reboot_required": false | ||
} | ||
""" | ||
# Disallowing required services when required causes error | ||
When I verify that running `pro api u.pro.services.enable.v1 --data '{"service": "ros", "enable_required_services": false}'` `with sudo` exits `1` | ||
When I run shell command `pro api u.pro.services.enable.v1 --data \"{\\\"service\\\": \\\"ros\\\", \\\"enable_required_services\\\": false}\" | jq .errors[0]` with sudo | ||
Then I will see the following on stdout: | ||
""" | ||
{ | ||
"code": "enable-blocked-required-service", | ||
"meta": { | ||
"required": "esm-apps", | ||
"target": "ros" | ||
}, | ||
"title": "Could not enable ros because esm-apps is not enabled" | ||
} | ||
""" | ||
# Default allows enabling required services | ||
When I run shell command `pro api u.pro.services.enable.v1 --args service=ros | jq .data.attributes` with sudo | ||
Then I will see the following on stdout: | ||
""" | ||
{ | ||
"disabled": [], | ||
"enabled": [ | ||
"esm-apps", | ||
"ros" | ||
], | ||
"messages": [], | ||
"reboot_required": false | ||
} | ||
""" | ||
# Access only works and post enable messages work | ||
When I run shell command `pro api u.pro.services.enable.v1 --data \"{\\\"service\\\": \\\"cis\\\", \\\"access_only\\\": true}\" | jq .data.attributes` with sudo | ||
Then I will see the following on stdout: | ||
""" | ||
{ | ||
"disabled": [], | ||
"enabled": [ | ||
"cis" | ||
], | ||
"messages": [ | ||
"Visit https://ubuntu.com/security/cis to learn how to use CIS" | ||
], | ||
"reboot_required": false | ||
} | ||
""" | ||
When I run `apt-cache policy usg-common` as non-root | ||
Then stdout contains substring: | ||
""" | ||
Installed: (none) | ||
""" | ||
# Access only on service that doesn't support it fails | ||
When I verify that running `pro api u.pro.services.enable.v1 --data '{"service": "ros-updates", "access_only": true}'` `with sudo` exits `1` | ||
When I run shell command `pro api u.pro.services.enable.v1 --data \"{\\\"service\\\": \\\"ros-updates\\\", \\\"access_only\\\": true}\" | jq .errors[0]` with sudo | ||
Then I will see the following on stdout: | ||
""" | ||
{ | ||
"code": "entitlement-not-enabled", | ||
"meta": { | ||
"reason": { | ||
"code": "enable-access-only-not-supported", | ||
"title": "ROS ESM All Updates does not support being enabled with --access-only" | ||
} | ||
}, | ||
"title": "failed to enable ros-updates" | ||
} | ||
""" | ||
Examples: | ||
| release | machine_type | | ||
| xenial | lxd-container | | ||
| bionic | lxd-container | | ||
| focal | lxd-container | | ||
| jammy | lxd-container | | ||
Scenario Outline: u.pro.services.enable.v1 vm services | ||
Given a `<release>` `<machine_type>` machine with ubuntu-advantage-tools installed | ||
When I run `apt-get update` with sudo | ||
And I apt install `jq` | ||
And I attach `contract_token` with sudo and options `--no-auto-enable` | ||
# Basic enable | ||
And I run shell command `pro api u.pro.services.enable.v1 --args service=livepatch | jq .data.attributes` with sudo | ||
Then I will see the following on stdout: | ||
""" | ||
{ | ||
"disabled": [], | ||
"enabled": [ | ||
"livepatch" | ||
], | ||
"messages": [], | ||
"reboot_required": false | ||
} | ||
""" | ||
# Enable without disable_incompatible fails | ||
When I verify that running `pro api u.pro.services.enable.v1 --data '{"service": "realtime-kernel", "disable_incompatible_services": false}'` `with sudo` exits `1` | ||
When I run shell command `pro api u.pro.services.enable.v1 --data \"{\\\"service\\\": \\\"realtime-kernel\\\", \\\"disable_incompatible_services\\\": false}\" | jq .errors[0]` with sudo | ||
Then I will see the following on stdout: | ||
""" | ||
{ | ||
"code": "enable-blocked-incompatible-service", | ||
"meta": { | ||
"incompatible": "livepatch", | ||
"target": "realtime-kernel" | ||
}, | ||
"title": "Could not enable realtime-kernel because livepatch is enabled" | ||
} | ||
""" | ||
# Enable with disable_incompatible works and variant works | ||
When I run shell command `pro api u.pro.services.enable.v1 --data \"{\\\"service\\\": \\\"realtime-kernel\\\", \\\"variant\\\": "intel-iotg"}\" | jq .data.attributes` with sudo | ||
Then I will see the following on stdout: | ||
""" | ||
{ | ||
"disabled": [ | ||
"livepatch" | ||
], | ||
"enabled": [ | ||
"realtime-kernel" | ||
], | ||
"messages": [], | ||
"reboot_required": false | ||
} | ||
""" | ||
When I run shell command `pro api u.pro.status.enabled_services.v1 | jq ".data.attributes.enabled_services | select(.name==\"realtime-kernel\")" ` with sudo | ||
Then stdout contains substring: | ||
""" | ||
{ | ||
"name": "realtime-kernel", | ||
"variant_enabled": true, | ||
"variant_name": "intel-iotg" | ||
} | ||
""" | ||
Examples: | ||
| release | machine_type | | ||
| jammy | lxd-vm | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import logging | ||
from typing import List, Optional | ||
|
||
from uaclient import actions, entitlements, event_logger, lock, messages, util | ||
from uaclient.api import exceptions | ||
from uaclient.api.api import APIEndpoint | ||
from uaclient.api.data_types import AdditionalInfo | ||
from uaclient.api.u.pro.status.enabled_services.v1 import _enabled_services | ||
from uaclient.api.u.pro.status.is_attached.v1 import _is_attached | ||
from uaclient.config import UAConfig | ||
from uaclient.data_types import ( | ||
BoolDataValue, | ||
DataObject, | ||
Field, | ||
StringDataValue, | ||
data_list, | ||
) | ||
|
||
event = event_logger.get_event_logger() | ||
LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) | ||
|
||
|
||
class EnableOptions(DataObject): | ||
fields = [ | ||
Field("service", StringDataValue), | ||
Field("variant", StringDataValue, False), | ||
Field("enable_required_services", BoolDataValue, False), | ||
Field("disable_incompatible_services", BoolDataValue, False), | ||
Field("access_only", BoolDataValue, False), | ||
] | ||
|
||
def __init__( | ||
self, | ||
*, | ||
service: str, | ||
variant: Optional[str] = None, | ||
enable_required_services: bool = True, | ||
disable_incompatible_services: bool = True, | ||
access_only: bool = False | ||
): | ||
self.service = service | ||
self.variant = variant | ||
self.enable_required_services = enable_required_services | ||
self.disable_incompatible_services = disable_incompatible_services | ||
self.access_only = access_only | ||
|
||
|
||
class EnableResult(DataObject, AdditionalInfo): | ||
fields = [ | ||
Field("enabled", data_list(StringDataValue)), | ||
Field("disabled", data_list(StringDataValue)), | ||
Field("reboot_required", BoolDataValue), | ||
Field("messages", data_list(StringDataValue)), | ||
] | ||
|
||
def __init__( | ||
self, | ||
*, | ||
enabled: List[str], | ||
disabled: List[str], | ||
reboot_required: bool, | ||
messages: List[str] | ||
): | ||
self.enabled = enabled | ||
self.disabled = disabled | ||
self.reboot_required = reboot_required | ||
self.messages = messages | ||
|
||
|
||
def _enabled_services_names(cfg: UAConfig) -> List[str]: | ||
return [s.name for s in _enabled_services(cfg).enabled_services] | ||
|
||
|
||
def enable(options: EnableOptions) -> EnableResult: | ||
return _enable(options, UAConfig()) | ||
|
||
|
||
def _enable( | ||
options: EnableOptions, | ||
cfg: UAConfig, | ||
) -> EnableResult: | ||
event.set_event_mode(event_logger.EventLoggerMode.JSON) | ||
|
||
if not _is_attached(cfg).is_attached: | ||
raise exceptions.UnattachedError() | ||
|
||
enabled_services_before = _enabled_services_names(cfg) | ||
if options.service in enabled_services_before: | ||
# nothing to do | ||
return EnableResult( | ||
enabled=[], | ||
disabled=[], | ||
reboot_required=False, | ||
messages=[], | ||
) | ||
|
||
ent_cls = entitlements.entitlement_factory( | ||
cfg=cfg, name=options.service, variant=options.variant or "" | ||
) | ||
entitlement = ent_cls( | ||
cfg, | ||
assume_yes=True, | ||
allow_beta=True, | ||
called_name=options.service, | ||
access_only=options.access_only, | ||
) | ||
|
||
success = False | ||
fail_reason = None | ||
|
||
try: | ||
with lock.SpinLock( | ||
cfg=cfg, | ||
lock_holder="u.pro.services.enable.v1", | ||
): | ||
success, fail_reason = entitlement.enable( | ||
enable_required_services=options.enable_required_services, | ||
disable_incompatible_services=options.disable_incompatible_services, | ||
api=True, | ||
) | ||
except Exception as e: | ||
lock.clear_lock_file_if_present() | ||
raise e | ||
|
||
if not success: | ||
if fail_reason is not None: | ||
reason = fail_reason.message | ||
else: | ||
reason = messages.GENERIC_UNKNOWN_ISSUE | ||
raise exceptions.EntitlementNotEnabledError( | ||
service=options.service, reason=reason | ||
) | ||
|
||
enabled_services_after = _enabled_services_names(cfg) | ||
|
||
post_enable_messages = [ | ||
msg | ||
for msg in entitlement.messaging.get("post_enable", []) | ||
if isinstance(msg, str) | ||
] | ||
|
||
return EnableResult( | ||
enabled=sorted( | ||
list( | ||
set(enabled_services_after).difference( | ||
set(enabled_services_before) | ||
) | ||
) | ||
), | ||
disabled=sorted( | ||
list( | ||
set(enabled_services_before).difference( | ||
set(enabled_services_after) | ||
) | ||
) | ||
), | ||
reboot_required=entitlement._check_for_reboot(), | ||
messages=post_enable_messages, | ||
) | ||
|
||
|
||
endpoint = APIEndpoint( | ||
version="v1", | ||
name="EnableService", | ||
fn=_enable, | ||
options_cls=EnableOptions, | ||
) |
Oops, something went wrong.