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-factory: Method to Associate needed entities, then Register the Host #14974

Closed
wants to merge 1 commit into from
Closed
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
229 changes: 229 additions & 0 deletions robottelo/host_helpers/api_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,235 @@ def wait_for_errata_applicability_task(
f'No task was found using query " {search_query} " for host id: {host_id}'
)

def register_host_and_needed_setup(
self,
client,
organization,
activation_key,
environment,
content_view,
enable_repos=False,
rex_key=False,
force=False,
):
"""
* Helper will setup desired entities to host content. Then, register the
host client to the entities, using associated activation-key.

* Attempt to make needed associations between detached entities.

* Add desired repos to the content-view prior to calling this helper.
Or, add them to content-view after calling, then publish/promote.

* The host will be registered to location: None (visible to all locations).

param satellite: sat instance where needed entities exist.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
param satellite: sat instance where needed entities exist.


param client: instance of a rhel contenthost to register.

param enable_repos (bool): enable all available repos on the client, after registration?
default: False, be sure to enable any repo(s) for client, after calling this method.

param rex_key (bool): add a Remote Execution Key to client for satellite?
default: False

param force (bool): force registration of the client to bypass?
default: False, a reused fixture contenthost will fail if already registered.

type: Below arguments can be any of the following:
* (int): pass id of entity to be read
* (str): pass name of entity to be searched
* (entity): pass an entity instance
param organization: pass an Organization instance, name, or id to use.

param activation_key: pass an Activation-Key instance, name, or id.

param environment: pass a Lifecycle-Environment instance, name, or id.
for example: can pass string name 'Library'.

param content_view: pass a Content-View instance, name, or id.
for example: can pass string name 'Default Organization View'.

Notes:
1. Will fail if passed entities do not exist in the same organization and satellite.
2. Use param `enable_repos`, to try enabling any repositories on client,
that were added to content-view prior. But if there are no
repositories added/made available, this will fail.
3. The Default Organization View cannot be published, promoted, edited, or deleted.
but you can register the client to it.

Steps:
1. get needed entities from arguments, id, name, or instance. Read all as instance.
2. publish the content-view if no versions exist, or needs_publish.
3. promote the newest content-view-version if not in environment already. Skip for 'Library'.
4. assign environment and content-view to the activation-key, if not associated.
5. Register the host, using the activation-key associated with content.
6. Add a rex_key to the client if desired, enable all available repositories if desired.

return: if Succeeded: dict containing the updated entities:
{
'result', 'client', 'organization', 'activation_key',
'environment', 'content_view',
}

return: if Failed: dict containing the result and reason
{
'result': 'error'
'client': None, unless registration was successful
'message': Details of failure encountered
}
"""
method_error = {
'result': 'error',
'client': None,
'message': None,
}
entities = {
'Organization': organization,
'ActivationKey': activation_key,
'LifecycleEnvironment': environment,
'ContentView': content_view,
}
if not hasattr(client, 'hostname'):
method_error['message'] = (
'Argument "client" must be instance, with attribute "hostname".'
)
return method_error
# for entity arguments matched to above params:
# fetch entity instance on satellite,
# from given id or name, else read passed argument as an instance.
for entity, value in entities.items():
param = None
# passed int for entity, try to read by id
if isinstance(value, int):
# equivalent: _satellite_.api.{KEY}(id=VALUE).read()
param = getattr(self._satellite.api, entity)(id=value).read()
# passed str, search for entity by name
elif isinstance(value, str):
search_query = f'name="{value}"'
if entity == 'Organization':
# search for org name itself, will be just scoped to satellite
# equivalent: _satellite_.api.{KEY}().search(...name={VALUE})
result = getattr(self._satellite.api, entity)().search(
query={'search': search_query}
)
else:
# search of non-org entity by name, will be scoped to organization
result = getattr(self._satellite.api, entity)(
organization=entities['Organization']
).search(query={'search': search_query})
if not len(result) > 0:
method_error['message'] = (
f'Could not find {entity} name: {value}, by search query: "{search_query}"'
)
return method_error
param = result[0]
# did not pass int (id) or str (name), must be readable entity instance
else:
if not hasattr(value, 'id'):
method_error['message'] = (
f'Passed entity {entity}, has no attribute id:\n{value}'
)
return method_error
param = value
# updated param, should now be only an entity isntance
if not hasattr(param, 'id'):
method_error['message'] = (
f'Did not get readable instance from parameter on {self._satellite.hostname}:'
f' Param:{entity}:\n{value}'
)
return method_error
# entity found, read updated instance into dictionary
entities[entity] = param.read()

if ( # publish a content-view-version if none exist, or needs_publish is True
len(entities['ContentView'].version) == 0
or entities['ContentView'].needs_publish is True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting use of the feature.

):
entities['ContentView'].publish()
# read updated entitites after modifying CV
entities = {k: v.read() for k, v in entities.items()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, do we need to read/update all the entities? Wouldn't be update of entities['ContentView'] enough?
Nice syntax anyway.


# promote to non-Library env if not already present:
# skip for 'Library' env selected or passed arg,
# any published version(s) will already be in Library.
if all(
[
environment != 'Library',
entities['LifecycleEnvironment'].name != 'Library',
entities['LifecycleEnvironment'] not in entities['ContentView'].environment,
]
):
# promote newest version by id
entities['ContentView'].version.sort(key=lambda version: version.id)
entities['ContentView'].version[-1].promote(
data={'environment_ids': entities['LifecycleEnvironment'].id}
)
# updated entities after promoting
entities = {k: v.read() for k, v in entities.items()}

if ( # assign env to ak if not present
entities['ActivationKey'].environment is None
or entities['ActivationKey'].environment.id != entities['LifecycleEnvironment'].id
):
entities['ActivationKey'].environment = entities['LifecycleEnvironment']
entities['ActivationKey'].update(['environment'])
entities = {k: v.read() for k, v in entities.items()}
if ( # assign cv to ak if not present
entities['ActivationKey'].content_view is None
or entities['ActivationKey'].content_view.id != entities['ContentView'].id
):
entities['ActivationKey'].content_view = entities['ContentView']
entities['ActivationKey'].update(['content_view'])

# register with now setup entities, using ak
entities = {k: v.read() for k, v in entities.items()}
result = client.register(
activation_keys=entities['ActivationKey'].name,
target=self._satellite,
org=entities['Organization'],
loc=None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could pass the loc as parameter too (for future uses).

force=force,
)
if result.status != 0:
method_error['message'] = (
f'Failed to register the host: {client.hostname}.\n{result.stderr}'
)
return method_error
if not client.subscribed:
method_error['client'] = client
method_error['message'] = (
f'Failed to subscribe the host: {client.hostname},'
f' to content-view: {entities["ContentView"].name}'
)
return method_error
if rex_key:
client.add_rex_key(self._satellite)

# enable all repositories available to client, using subscription manager,
# ie: any repos added to content-view prior to calling this method.
# note: this will fail if no repos are available to client from CV/AK
if enable_repos:
output = client.execute(r'subscription-manager repos --enable \*')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we use this at several places already. I just wonder if it wouldn't be better to do content-override at the AK entity (based on the enable_repos param) prior to the client registration, so the repos would get enabled through the AK already.

if output.status != 0:
method_error['client'] = client
method_error['message'] = (
'Failed to enable all available repositories using subscription-manager.'
f' For client: {client.hostname}.\n{output.stdout}'
)
return method_error
entities = {k: v.read() for k, v in entities.items()}
return ( # dict containing registered host client, and updated entities
{
'result': 'success',
'client': client,
'organization': entities['Organization'],
'activation_key': entities['ActivationKey'],
'environment': entities['LifecycleEnvironment'],
'content_view': entities['ContentView'],
}
)

def wait_for_syncplan_tasks(self, repo_backend_id=None, timeout=10, repo_name=None):
"""Search the pulp tasks and identify repositories sync tasks with
specified name or backend_identifier
Expand Down
Loading
Loading