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

[6.13.z] [POC] New-Style Upgrade Tests #16766

Open
wants to merge 3 commits into
base: 6.13.z
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions conf/upgrade.yaml.template
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ UPGRADE:
TO_VERSION: "6.9"
# Satellite, Capsule hosts RHEL operating system version.
OS: "rhel7"
# The job template Broker should use to upgrade a Satellite
SATELLITE_UPGRADE_JOB_TEMPLATE: satellite-upgrade
# Capsule's activation key will only be available when we spawn the VM using upgrade template.
CAPSULE_AK:
RHEL6:
Expand Down
4 changes: 2 additions & 2 deletions robottelo/host_helpers/satellite_mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def is_sca_mode_enabled(self, org_id):
"""
return self.api.Organization(id=org_id).read().simple_content_access

def publish_content_view(self, org, repo_list):
def publish_content_view(self, org, repo_list, name):
"""This method publishes the content view for a given organization and repository list.

:param str org: The name of the organization to which the content view belongs
Expand All @@ -190,7 +190,7 @@ def publish_content_view(self, org, repo_list):
:return: A dictionary containing the details of the published content view.
"""
repo = repo_list if isinstance(repo_list, list) else [repo_list]
content_view = self.api.ContentView(organization=org, repository=repo).create()
content_view = self.api.ContentView(organization=org, repository=repo, name=name).create()
content_view.publish()
return content_view.read()

Expand Down
2 changes: 1 addition & 1 deletion robottelo/hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,7 @@ def put(self, local_path, remote_path=None):
content_file.flush()
self.session.sftp_write(source=content_file.name, destination=remote_path)
else:
self.session.sftp_write(source=local_path, destination=remote_path)
self.session.sftp_write(source=str(local_path), destination=str(remote_path))

def put_ssh_key(self, source_key_path, destination_key_name):
"""Copy ssh key to virtual machine ssh path and ensure proper permission is set
Expand Down
30 changes: 26 additions & 4 deletions robottelo/utils/shared_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
from broker.helpers import FileLock


class SharedResourceError(Exception):
"""An exception class for SharedResource errors."""


class SharedResource:
"""A class representing a shared resource.

Expand All @@ -44,19 +48,21 @@ class SharedResource:
is_recovering (bool): Whether the current instance is recovering from an error or not.
"""

def __init__(self, resource_name, action, *action_args, **action_kwargs):
def __init__(self, resource_name, action, *action_args, action_validator=None, **action_kwargs):
"""Initializes a new instance of the SharedResource class.

Args:
resource_name (str): The name of the shared resource.
action (function): The function to be executed when the resource is ready.
action_args (tuple): The arguments to be passed to the action function.
action_validator (function): The function to validate the action results.
action_kwargs (dict): The keyword arguments to be passed to the action function.
"""
self.resource_file = Path(f"/tmp/{resource_name}.shared")
self.lock_file = FileLock(self.resource_file)
self.id = str(uuid4().fields[-1])
self.action = action
self.action_validator = action_validator
self.action_is_recoverable = action_kwargs.pop("action_is_recoverable", False)
self.action_args = action_args
self.action_kwargs = action_kwargs
Expand Down Expand Up @@ -152,6 +158,14 @@ def register(self):
curr_data["statuses"][self.id] = "pending"
self.resource_file.write_text(json.dumps(curr_data, indent=4))

def unregister(self):
"""Unregisters the current process as a watcher."""
with self.lock_file:
curr_data = json.loads(self.resource_file.read_text())
curr_data["watchers"].remove(self.id)
del curr_data["statuses"][self.id]
self.resource_file.write_text(json.dumps(curr_data, indent=4))

def ready(self):
"""Marks the current process as ready to perform the action."""
self._update_status("ready")
Expand All @@ -164,10 +178,13 @@ def done(self):
def act(self):
"""Attempt to perform the action."""
try:
self.action(*self.action_args, **self.action_kwargs)
result = self.action(*self.action_args, **self.action_kwargs)
except Exception as err:
self._update_main_status("error")
raise err
raise SharedResourceError("Main worker failed during action") from err
# If the action_validator is a callable, use it to validate the result
if callable(self.action_validator) and not self.action_validator(result):
raise SharedResourceError(f"Action validation failed for {self.action} with {result=}")

def wait(self):
"""Top-level wait function, separating behavior between main and non-main watchers."""
Expand All @@ -190,11 +207,16 @@ def __exit__(self, exc_type, exc_value, traceback):
raise exc_value
if exc_type is None:
self.done()
self.unregister()
if self.is_main:
self._wait_for_status("done")
self.resource_file.unlink()
else:
self._update_status("error")
if self.is_main:
self._update_main_status("error")
if self._check_all_status("error"):
# All have failed, delete the file
self.resource_file.unlink()
else:
self._update_main_status("error")
raise exc_value
Empty file added tests/new_upgrades/__init__.py
Empty file.
100 changes: 100 additions & 0 deletions tests/new_upgrades/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""
This module is intended to be used for upgrade tests that have a single run stage.
"""

import datetime

from broker import Broker
import pytest

from robottelo.config import settings
from robottelo.hosts import Satellite
from robottelo.utils.shared_resource import SharedResource

pre_upgrade_failed_tests = []


PRE_UPGRADE_TESTS_FILE_OPTION = 'pre_upgrade_tests_file'
PRE_UPGRADE_TESTS_FILE_PATH = '/var/tmp/robottelo_pre_upgrade_failed_tests.json'
PRE_UPGRADE = False
POST_UPGRADE = False
PRE_UPGRADE_MARK = 'pre_upgrade'
POST_UPGRADE_MARK = 'post_upgrade'
TEST_NODE_ID_NAME = '__pytest_node_id'


def log(message, level="DEBUG"):
"""Pytest has a limitation to use logging.logger from conftest.py
so we need to emulate the logger by std-out the output
"""
now = datetime.datetime.now()
full_message = "{date} - conftest - {level} - {message}\n".format(
date=now.strftime("%Y-%m-%d %H:%M:%S"), level=level, message=message
)
print(full_message) # noqa
with open('robottelo.log', 'a') as log_file:
log_file.write(full_message)


def pytest_configure(config):
"""Register custom markers to avoid warnings."""
markers = [
"content_upgrades: Upgrade tests that run under .",
]
for marker in markers:
config.addinivalue_line("markers", marker)


def shared_checkout(shared_name):
Satellite(hostname="blank")._swap_nailgun(f"{settings.UPGRADE.FROM_VERSION}.z")
bx_inst = Broker(
workflow=settings.SERVER.deploy_workflows.product,
deploy_sat_version=settings.UPGRADE.FROM_VERSION,
host_class=Satellite,
upgrade_group=f"{shared_name}_shared_checkout",
)
with SharedResource(
resource_name=f"{shared_name}_sat_checkout",
action=bx_inst.checkout,
action_validator=lambda result: isinstance(result, Satellite),
) as sat_checkout:
sat_checkout.ready()
sat_instance = bx_inst.from_inventory(
filter=f'@inv._broker_args.upgrade_group == "{shared_name}_shared_checkout"'
)[0]
sat_instance.setup()
return sat_instance


def shared_checkin(sat_instance):
sat_instance.teardown()
with SharedResource(
resource_name=sat_instance.hostname + "_checkin",
action=Broker(hosts=[sat_instance]).checkin,
) as sat_checkin:
sat_checkin.ready()


@pytest.fixture(scope='session')
def upgrade_action():
def _upgrade_action(target_sat):
Broker(
job_template=settings.UPGRADE.SATELLITE_UPGRADE_JOB_TEMPLATE,
target_vm=target_sat.name,
sat_version=settings.UPGRADE.TO_VERSION,
upgrade_path="ystream",
tower_inventory=target_sat.tower_inventory,
).execute()

return _upgrade_action


@pytest.fixture
def content_upgrade_shared_satellite():
"""Mark tests using this fixture with pytest.mark.content_upgrades."""
sat_instance = shared_checkout("content_upgrade")
with SharedResource(
"content_upgrade_tests", shared_checkin, sat_instance=sat_instance
) as test_duration:
yield sat_instance
test_duration.ready()
133 changes: 133 additions & 0 deletions tests/new_upgrades/test_contentview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""Test for Content View related Upgrade Scenario's

:Requirement: UpgradedSatellite

:CaseAutomation: Automated

:CaseComponent: ContentViews

:Team: Phoenix-content

:CaseImportance: High

"""

from box import Box
from fauxfactory import gen_alpha
import pytest

from robottelo.config import settings
from robottelo.constants import RPM_TO_UPLOAD, DataFile
from robottelo.utils.shared_resource import SharedResource


@pytest.fixture
def cv_upgrade_setup(content_upgrade_shared_satellite, upgrade_action):
"""Pre-upgrade scenario that creates content-view with various repositories.

:id: preupgrade-a4ebbfa1-106a-4962-9c7c-082833879ae8

:steps:
1. Create custom repositories of yum and file type.
2. Create content-view.
3. Add yum and file repositories in the content view.
4. Publish the content-view.

:expectedresults: Content-view created with various repositories.
"""
target_sat = content_upgrade_shared_satellite
with SharedResource(target_sat.hostname, upgrade_action, target_sat=target_sat) as sat_upgrade:
test_data = Box(
{
'target_sat': target_sat,
'cv': None,
'org': None,
'product': None,
'yum_repo': None,
'file_repo': None,
}
)
test_name = f'cv_upgrade_{gen_alpha()}' # unique name for the test
org = target_sat.api.Organization(name=f'{test_name}_org').create()
test_data.org = org
product = target_sat.api.Product(organization=org, name=f'{test_name}_prod').create()
test_data.product = product
yum_repository = target_sat.api.Repository(
product=product,
name=f'{test_name}_yum_repo',
url=settings.repos.yum_1.url,
content_type='yum',
).create()
test_data.yum_repo = yum_repository
target_sat.api.Repository.sync(yum_repository)
file_repository = target_sat.api.Repository(
product=product, name=f'{test_name}_file_repo', content_type='file'
).create()
test_data.file_repo = file_repository
remote_file_path = f'/tmp/{RPM_TO_UPLOAD}'
target_sat.put(DataFile.RPM_TO_UPLOAD, remote_file_path)
file_repository.upload_content(files={'content': DataFile.RPM_TO_UPLOAD.read_bytes()})
assert 'content' in file_repository.files()['results'][0]['name']
cv = target_sat.publish_content_view(org, [yum_repository, file_repository], test_name)
assert len(cv.read_json()['versions']) == 1
test_data.cv = cv
sat_upgrade.ready()
target_sat._session = None
yield test_data


@pytest.mark.content_upgrades
def test_cv_upgrade_scenario(cv_upgrade_setup):
"""After upgrade, the existing content-view(created before upgrade) should be updated.

:id: postupgrade-a4ebbfa1-106a-4962-9c7c-082833879ae8

:steps:
1. Check yum and file repository which was added in CV before upgrade.
2. Check the content view which was was created before upgrade.
3. Remove yum repository from existing CV.
4. Create new yum repository in existing CV.
5. Publish content-view

:expectedresults: After upgrade,
1. All the repositories should be intact.
2. Content view created before upgrade should be intact.
3. The new repository should be added/updated to the CV.

"""
target_sat = cv_upgrade_setup.target_sat
org = target_sat.api.Organization().search(
query={'search': f'name="{cv_upgrade_setup.org.name}"'}
)[0]
product = target_sat.api.Product(organization=org.id).search(
query={'search': f'name="{cv_upgrade_setup.product.name}"'}
)[0]
cv = target_sat.api.ContentView(organization=org.id).search(
query={'search': f'name="{cv_upgrade_setup.cv.name}"'}
)[0]
target_sat.api.Repository(organization=org.id).search(
query={'search': f'name="{cv_upgrade_setup.yum_repo.name}"'}
)[0]
target_sat.api.Repository(organization=org.id).search(
query={'search': f'name="{cv_upgrade_setup.file_repo.name}"'}
)[0]
cv.repository = []
cv.update(['repository'])
assert len(cv.read_json()['repositories']) == 0

yum_repository2 = target_sat.api.Repository(
product=product,
name='cv_upgrade_yum_repos2',
url=settings.repos.yum_2.url,
content_type='yum',
).create()
yum_repository2.sync()
cv.repository = [yum_repository2]
cv.update(['repository'])
assert cv.read_json()['repositories'][0]['name'] == yum_repository2.name

cv.publish()
assert len(cv.read_json()['versions']) == 2
content_view_json = cv.read_json()['environments'][0]
cv.delete_from_environment(content_view_json['id'])
assert len(cv.read_json()['environments']) == 0
Loading
Loading