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

Introduce "topology" addresses for guests #2670

Merged
merged 2 commits into from
Feb 20, 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
6 changes: 3 additions & 3 deletions tests/unit/test_guest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ def test_multihost_name(root_logger: Logger) -> None:
assert Guest(
logger=root_logger,
name='foo',
data=GuestData(guest='bar')).multihost_name == 'foo'
data=GuestData(primary_address='bar')).multihost_name == 'foo'

assert Guest(
logger=root_logger,
name='foo',
data=GuestData(guest='bar', role='client')).multihost_name == 'foo (client)'
data=GuestData(primary_address='bar', role='client')).multihost_name == 'foo (client)'


@pytest.mark.parametrize(('stdout', 'expected'), [
Expand Down Expand Up @@ -57,7 +57,7 @@ def test_execute_no_connection_closed(
guest = GuestSsh(
logger=root_logger,
name='foo',
data=GuestSshData(guest='bar')
data=GuestSshData(primary_address='bar')
)

monkeypatch.setattr(
Expand Down
2 changes: 1 addition & 1 deletion tmt/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3238,7 +3238,7 @@ def runner(self) -> 'tmt.steps.provision.local.GuestLocal':

return tmt.steps.provision.local.GuestLocal(
data=tmt.steps.provision.GuestData(
guest='localhost',
primary_address='localhost',
role=None),
name='tmt runner',
logger=self._logger)
Expand Down
2 changes: 1 addition & 1 deletion tmt/steps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1911,7 +1911,7 @@ class GuestTopology(SerializableContainer):
def __init__(self, guest: 'Guest') -> None:
self.name = guest.name
self.role = guest.role
self.hostname = guest.guest
self.hostname = guest.topology_address


@dataclasses.dataclass(init=False)
Expand Down
48 changes: 30 additions & 18 deletions tmt/steps/provision/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,10 +475,13 @@ class GuestData(SerializableContainer):
# fields are not created by `field()` - not sure why, but we can fix that
# later.
#: List of fields that are not allowed to be set via fmf keys/CLI options.
_OPTIONLESS_FIELDS: tuple[str, ...] = ('guest', 'facts')
_OPTIONLESS_FIELDS: tuple[str, ...] = ('primary_address', 'topology_address', 'facts')

# hostname or ip address
guest: Optional[str] = None
#: Primary hostname or IP address for tmt/guest communication.
primary_address: Optional[str] = None

#: Guest topology hostname or IP address for guest/guest communication.
topology_address: Optional[str] = None

role: Optional[str] = field(
default=None,
Expand Down Expand Up @@ -589,7 +592,10 @@ def show(
else:
printable_value = str(value)

logger.info(tmt.utils.key_to_option(key), printable_value, color='green')
logger.info(
tmt.utils.key_to_option(key).replace('-', ' '),
printable_value,
color='green')


class Guest(tmt.utils.Common):
Expand All @@ -615,7 +621,13 @@ class Guest(tmt.utils.Common):
_data_class: type[GuestData] = GuestData

role: Optional[str]
guest: Optional[str]

#: Primary hostname or IP address for tmt/guest communication.
primary_address: Optional[str] = None

#: Guest topology hostname or IP address for guest/guest communication.
topology_address: Optional[str] = None

become: bool

hardware: Optional[tmt.hardware.Hardware]
Expand Down Expand Up @@ -710,7 +722,7 @@ def wake(self) -> None:
attach to a running guest instance and execute commands. Called
after load() is completed so all guest data should be prepared.
"""
self.debug(f"Doing nothing to wake up guest '{self.guest}'.")
self.debug(f"Doing nothing to wake up guest '{self.primary_address}'.")

def start(self) -> None:
"""
Expand All @@ -720,7 +732,7 @@ def start(self) -> None:
any configuration necessary to get it started. Called after
load() is completed so all guest data should be available.
"""
self.debug(f"Doing nothing to start guest '{self.guest}'.")
self.debug(f"Doing nothing to start guest '{self.primary_address}'.")

# A couple of requiremens for this field:
#
Expand Down Expand Up @@ -804,7 +816,7 @@ def _ansible_summary(self, output: Optional[str]) -> None:

def _ansible_playbook_path(self, playbook: Path) -> Path:
""" Prepare full ansible playbook path """
self.debug(f"Applying playbook '{playbook}' on guest '{self.guest}'.")
self.debug(f"Applying playbook '{playbook}' on guest '{self.primary_address}'.")
# FIXME: cast() - https://github.com/teemtee/tmt/issues/1372
parent = cast(Provision, self.parent)
assert parent.plan.my_run is not None # narrow type
Expand Down Expand Up @@ -1101,7 +1113,7 @@ def remove(self) -> None:
Completely remove all guest instance data so that it does not
consume any disk resources.
"""
self.debug(f"Doing nothing to remove guest '{self.guest}'.")
self.debug(f"Doing nothing to remove guest '{self.primary_address}'.")

def _check_rsync(self) -> CheckRsyncOutcome:
"""
Expand Down Expand Up @@ -1233,7 +1245,7 @@ class GuestSsh(Guest):

def _ssh_guest(self) -> str:
""" Return user@guest """
return f'{self.user}@{self.guest}'
return f'{self.user}@{self.primary_address}'

def _ssh_socket(self) -> Path:
""" Prepare path to the master connection socket """
Expand Down Expand Up @@ -1351,7 +1363,7 @@ def is_ready(self) -> bool:
""" Detect guest is ready or not """

# Enough for now, ssh connection can be created later
return self.guest is not None
return self.primary_address is not None

def execute(self,
command: Union[tmt.utils.Command, tmt.utils.ShellScript],
Expand All @@ -1375,7 +1387,7 @@ def execute(self,
"""

# Abort if guest is unavailable
if self.guest is None and not self.is_dry_run:
if self.primary_address is None and not self.is_dry_run:
raise tmt.utils.GeneralError('The guest is not available.')

ssh_command: tmt.utils.Command = self._ssh_command()
Expand Down Expand Up @@ -1416,7 +1428,7 @@ def execute(self,
remote_command
]

self.debug(f"Execute command '{remote_command}' on guest '{self.guest}'.")
self.debug(f"Execute command '{remote_command}' on guest '{self.primary_address}'.")

output = self._run_guest_command(
ssh_command,
Expand Down Expand Up @@ -1457,7 +1469,7 @@ def push(self,
sudo on the Guest (e.g. pushing to r/o destination)
"""
# Abort if guest is unavailable
if self.guest is None and not self.is_dry_run:
if self.primary_address is None and not self.is_dry_run:
raise tmt.utils.GeneralError('The guest is not available.')

# Prepare options and the push command
Expand All @@ -1471,7 +1483,7 @@ def push(self,
assert parent.plan.workdir is not None

source = parent.plan.workdir
self.debug(f"Push workdir to guest '{self.guest}'.")
self.debug(f"Push workdir to guest '{self.primary_address}'.")
else:
self.debug(f"Copy '{source}' to '{destination}' on the guest.")

Expand Down Expand Up @@ -1524,7 +1536,7 @@ def pull(self,
and 'extend_options' to extend them (e.g. by exclude).
"""
# Abort if guest is unavailable
if self.guest is None and not self.is_dry_run:
if self.primary_address is None and not self.is_dry_run:
raise tmt.utils.GeneralError('The guest is not available.')

# Prepare options and the pull command
Expand All @@ -1540,7 +1552,7 @@ def pull(self,
assert parent.plan.workdir is not None

source = parent.plan.workdir
self.debug(f"Pull workdir from guest '{self.guest}'.")
self.debug(f"Pull workdir from guest '{self.primary_address}'.")
else:
self.debug(f"Copy '{source}' from the guest to '{destination}'.")

Expand Down Expand Up @@ -1724,7 +1736,7 @@ def remove(self) -> None:
Completely remove all guest instance data so that it does not
consume any disk resources.
"""
self.debug(f"Doing nothing to remove guest '{self.guest}'.")
self.debug(f"Doing nothing to remove guest '{self.primary_address}'.")

def _check_rsync(self) -> CheckRsyncOutcome:
"""
Expand Down
10 changes: 6 additions & 4 deletions tmt/steps/provision/artemis.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ def is_ready(self) -> bool:

# FIXME: A more robust solution should be provided. Currently just
# return True if self.guest is not None
return self.guest is not None
return self.primary_address is not None

def _create(self) -> None:
environment: dict[str, Any] = {
Expand Down Expand Up @@ -620,8 +620,7 @@ def get_new_state() -> GuestInspectType:
f'Failed to provision in the given amount '
f'of time (--provision-timeout={self.provision_timeout}).')

self.guest = guest_info['address']
self.info('address', self.guest, 'green')
self.primary_address = self.topology_address = guest_info['address']

def start(self) -> None:
"""
Expand All @@ -632,9 +631,12 @@ def start(self) -> None:
load() is completed so all guest data should be available.
"""

if self.guestname is None or self.guest is None:
if self.guestname is None or self.primary_address is None:
self._create()

self.verbose('primary address', self.primary_address, 'green')
self.verbose('topology address', self.topology_address, 'green')

def remove(self) -> None:
""" Remove the guest """

Expand Down
26 changes: 25 additions & 1 deletion tmt/steps/provision/connect.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import dataclasses
from typing import Optional, Union
from typing import Any, Optional, Union

import tmt
import tmt.steps
Expand Down Expand Up @@ -57,6 +57,22 @@ class ConnectGuestData(tmt.steps.provision.GuestSshData):
unserialize=lambda serialized: None if serialized is None else ShellScript(serialized)
)

@classmethod
def from_plugin(
cls: type['ConnectGuestData'],
container: 'ProvisionConnect') -> 'ConnectGuestData': # type: ignore[override]

options: dict[str, Any] = {
key: container.get(option)
# SIM118: Use `{key} in {dict}` instead of `{key} in {dict}.keys()`.
# "Type[ArtemisGuestData]" has no attribute "__iter__" (not iterable)
for key, option in cls.options()
}

options['primary_address'] = options['topology_address'] = options.pop('guest')

return ConnectGuestData(**options)


@dataclasses.dataclass
class ProvisionConnectData(ConnectGuestData, tmt.steps.provision.ProvisionStepData):
Expand Down Expand Up @@ -128,6 +144,14 @@ def reboot(
tick=tick,
tick_increase=tick_increase)

def start(self) -> None:
""" Start the guest """

self.debug(f"Doing nothing to start guest '{self.primary_address}'.")

self.verbose('primary address', self.primary_address, 'green')
self.verbose('topology address', self.topology_address, 'green')


@tmt.steps.provides_method('connect')
class ProvisionConnect(tmt.steps.provision.ProvisionPlugin[ProvisionConnectData]):
Expand Down
14 changes: 11 additions & 3 deletions tmt/steps/provision/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,26 @@ def execute(self,
on_process_start=on_process_start,
**kwargs)

def start(self) -> None:
""" Start the guest """

self.debug(f"Doing nothing to start guest '{self.primary_address}'.")

self.verbose('primary address', self.primary_address, 'green')
self.verbose('topology address', self.topology_address, 'green')

def stop(self) -> None:
""" Stop the guest """

self.debug(f"Doing nothing to stop guest '{self.guest}'.")
self.debug(f"Doing nothing to stop guest '{self.primary_address}'.")

def reboot(self,
hard: bool = False,
command: Optional[Union[Command, ShellScript]] = None,
timeout: Optional[int] = None) -> bool:
""" Reboot the guest, return True if successful """

self.debug(f"Doing nothing to reboot guest '{self.guest}'.")
self.debug(f"Doing nothing to reboot guest '{self.primary_address}'.")

return False

Expand Down Expand Up @@ -171,7 +179,7 @@ def go(self) -> None:

# Create a GuestLocal instance
data = tmt.steps.provision.GuestData.from_plugin(self)
data.guest = 'localhost'
data.primary_address = 'localhost'

data.show(verbose=self.verbosity_level, logger=self._logger)

Expand Down
13 changes: 8 additions & 5 deletions tmt/steps/provision/mrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,8 +680,7 @@ def get_new_state() -> GuestInspectType:
f'of time (--provision-timeout={self.provision_timeout}).'
)

self.guest = guest_info['system']
self.info('address', self.guest, 'green')
self.primary_address = self.topology_address = guest_info['system']

def start(self) -> None:
"""
Expand All @@ -692,9 +691,12 @@ def start(self) -> None:
load() is completed so all guest data should be available.
"""

if self.job_id is None or self.guest is None:
if self.job_id is None or self.primary_address is None:
self._create(self._tmt_name())

self.verbose('primary address', self.primary_address, 'green')
self.verbose('topology address', self.topology_address, 'green')

def stop(self) -> None:
""" Stop the guest """
# do nothing
Expand Down Expand Up @@ -736,9 +738,10 @@ def reboot(
if not command and hard:
self.debug("Reboot using the reboot command 'bkr system-power --action reboot'.")

reboot_script = ShellScript(f'bkr system-power --action reboot {self.primary_address}')

return self.perform_reboot(
lambda: self._run_guest_command(ShellScript(
f'bkr system-power --action reboot {self.guest}').to_shell_command()),
lambda: self._run_guest_command(reboot_script.to_shell_command()),
timeout=timeout,
tick=tick,
tick_increase=tick_increase)
Expand Down
5 changes: 3 additions & 2 deletions tmt/steps/provision/podman.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,9 @@ def start(self) -> None:
# Mount the whole plan directory in the container
workdir = self.parent.plan.workdir

self.container = self.guest = self._tmt_name()
self.verbose('name', self.container, 'green')
self.container = self.primary_address = self.topology_address = self._tmt_name()
self.verbose('primary address', self.primary_address, 'green')
self.verbose('topology address', self.topology_address, 'green')

additional_args = []

Expand Down
9 changes: 5 additions & 4 deletions tmt/steps/provision/testcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,11 +759,12 @@ def start(self) -> None:
libvirt.libvirtError) as error:
raise ProvisionError(
f'Failed to boot testcloud instance ({error}).')
self.guest = self._instance.get_ip()
self.primary_address = self.topology_address = self._instance.get_ip()
self.port = int(self._instance.get_instance_port())
self.verbose('ip', self.guest, 'green')
self.verbose('primary address', self.primary_address, 'green')
self.verbose('topology address', self.topology_address, 'green')
self.verbose('port', self.port, 'green')
self._instance.create_ip_file(self.guest)
self._instance.create_ip_file(self.primary_address)

# Wait a bit until the box is up
if not self.reconnect(
Expand All @@ -783,7 +784,7 @@ def stop(self) -> None:
""" Stop provisioned guest """
super().stop()
# Stop only if the instance successfully booted
if self._instance and self.guest:
if self._instance and self.primary_address:
self.debug(f"Stopping testcloud instance '{self.instance_name}'.")
assert testcloud is not None
try:
Expand Down
Loading
Loading