Skip to content

Commit

Permalink
Merge pull request pulp#25 from pulp/jdob-puppet
Browse files Browse the repository at this point in the history
CLI refactoring of sync related commands and Puppet implementation of the progress reporting
  • Loading branch information
mhrivnak committed Aug 29, 2012
2 parents 2cc7f0e + a32e7fb commit 4175908
Show file tree
Hide file tree
Showing 32 changed files with 2,109 additions and 88 deletions.
10 changes: 7 additions & 3 deletions platform/src/pulp/client/commands/repo/cudl.py
Expand Up @@ -166,8 +166,8 @@ def run(self, **kwargs):
self.prompt.render_title(_('Repositories'))

# Default flags to render_document_list
filters = None
order = ['id', 'display_name', 'description', 'content_unit_count']
filters = ['id', 'display_name', 'description', 'content_unit_count']
order = filters

if kwargs['summary'] is True:
filters = ['id', 'display_name']
Expand All @@ -185,5 +185,9 @@ def run(self, **kwargs):
query_params[param] = True
filters.append(param)

repo_list = self.context.server.repo.repositories(query_params).response_body
repo_list = self.get_repositories(query_params, **kwargs)
self.prompt.render_document_list(repo_list, filters=filters, order=order)

def get_repositories(self, query_params, **kwargs):
repo_list = self.context.server.repo.repositories(query_params).response_body
return repo_list
12 changes: 12 additions & 0 deletions platform/src/pulp/client/commands/repo/status/__init__.py
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2012 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public
# License as published by the Free Software Foundation; either version
# 2 of the License (GPLv2) or (at your option) any later version.
# There is NO WARRANTY for this software, express or implied,
# including the implied warranties of MERCHANTABILITY,
# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
# have received a copy of GPLv2 along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
131 changes: 131 additions & 0 deletions platform/src/pulp/client/commands/repo/status/status.py
@@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2012 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public
# License as published by the Free Software Foundation; either version
# 2 of the License (GPLv2) or (at your option) any later version.
# There is NO WARRANTY for this software, express or implied,
# including the implied warranties of MERCHANTABILITY,
# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
# have received a copy of GPLv2 along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.

"""
Contains functions for rendering the sync/publish progress reports.
"""

from gettext import gettext as _
import time

# -- public -------------------------------------------------------------------

def display_task_status(context, renderer, task_id):
"""
Displays and continues to poll and update the sync or publish operation
taking place in the given task.
:param context: CLI context
:type context: pulp.client.extensions.core.ClientContext
:param renderer: StatusRenderer subclass that will interpret the sync or
publish progress report
:type renderer: StatusRenderer
:param task_id: task to display
:type task_id: str
"""

# Retrieve the task and wrap into a list for consistent rendering
response = context.server.tasks.get_task(task_id)
task_list = [response.response_body]

_display_status(context, renderer, task_list)

def display_group_status(context, renderer, task_group_id):
"""
Displays and continues to poll and update the sync or publish operation
taking place in the given task group. This call is used for tracking
sync operations with one or more distributors configured for auto
publish.
:param context: CLI context
:type context: pulp.client.extensions.core.ClientContext
:param renderer: StatusRenderer subclass that will interpret the sync or
publish progress report
:type renderer: StatusRenderer
:param task_group_id: task group to display
:type task_group_id: str
"""

# Get the list of tasks relevant to the group
response = context.server.task_groups.get_task_group(task_group_id)
task_list = response.response_body

_display_status(context, renderer, task_list)

# -- private ------------------------------------------------------------------

def _display_status(context, renderer, task_list):

m = _('This command may be exited by pressing ctrl+c without affecting the actual operation on the server.')
context.prompt.render_paragraph(m, tag='ctrl-c')

# Handle the cases where we don't want to honor the foreground request
if task_list[0].is_rejected():
announce = _('The request to synchronize repository was rejected')
description = _('This is likely due to an impending delete request for the repository.')

context.prompt.render_failure_message(announce, tag='rejected-msg')
context.prompt.render_paragraph(description, tag='rejected-desc')
return

if task_list[0].is_postponed():
a = _('The request to synchronize the repository was accepted but postponed '
'due to one or more previous requests against the repository. The sync will '
'take place at the earliest possible time.')
context.prompt.render_paragraph(a, tag='postponed')
return

try:
for task_num, task in enumerate(task_list):
quiet_waiting = task_num > 0

_display_task_status(context, renderer, task.task_id, quiet_waiting=quiet_waiting)

except KeyboardInterrupt:
# If the user presses ctrl+c, don't let the error bubble up, just
# exit gracefully
return

def _display_task_status(context, renderer, task_id, quiet_waiting=False):
"""
Poll an individual task and display the progress for it.
:return: the completed task
:rtype: Task
"""

begin_spinner = context.prompt.create_spinner()
poll_frequency_in_seconds = float(context.config['output']['poll_frequency_in_seconds'])

response = context.server.tasks.get_task(task_id)

while not response.response_body.is_completed():

if response.response_body.is_waiting() and not quiet_waiting:
begin_spinner.next(_('Waiting to begin'))
else:
renderer.display_report(response.response_body.progress)

time.sleep(poll_frequency_in_seconds)

response = context.server.tasks.get_task(response.response_body.task_id)

# Even after completion, we still want to display the report one last
# time in case there was no poll between, say, the middle of the
# package download and when the task itself reports as finished. We
# don't want to leave the UI in that half-finished state so this final
# call is to clean up and render the completed report.
renderer.display_report(response.response_body.progress)

return response.response_body

105 changes: 105 additions & 0 deletions platform/src/pulp/client/commands/repo/status/tasks.py
@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2012 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public
# License as published by the Free Software Foundation; either version
# 2 of the License (GPLv2) or (at your option) any later version.
# There is NO WARRANTY for this software, express or implied,
# including the implied warranties of MERCHANTABILITY,
# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
# have received a copy of GPLv2 along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.

"""
Functions for working with sync/publish tasks in the server.
"""

from pulp.common import tags


def relevant_existing_task_id(existing_sync_tasks):
"""
Analyzes the list of existing sync tasks to determine which should be
tracked by the CLI.
:param existing_sync_tasks: list of task instances retrieved from the server
:type existing_sync_tasks: list
:return: ID of the task that should be displayed
:rtype: str
"""

# At this point, we have at least one sync task but that doesn't
# mean it's running yet. It shouldn't, however, be completed as
# it wouldn't come back in the lookup. That should leave two
# possibilities: waiting or running.
#
# There will only be one running, so that case is easy: if we find
# a running task start displaying it.
#
# If there are no running tasks, the waiting ones are ordered such
# that the first one will execute next, so use that task ID and
# start the display process (it will handle waiting accordingly.

running_tasks = [t for t in existing_sync_tasks if t.is_running()]
waiting_tasks = [t for t in existing_sync_tasks if t.is_waiting()]

if running_tasks:
return running_tasks[0].task_id
elif waiting_tasks:
return waiting_tasks[0].task_id

return None


def relevant_existing_task_group_id(existing_sync_tasks):
"""
Grok through a list of existing sync tasks and look for the task_group_id
for the highest priority sync.
"""
running_tasks = [t for t in existing_sync_tasks if t.is_running()]
waiting_tasks = [t for t in existing_sync_tasks if t.is_waiting()]

if running_tasks:
return running_tasks[0].task_group_id
elif waiting_tasks:
return waiting_tasks[0].task_group_id

return None


def sync_task_in_sync_task_group(task_list):
"""
Grok through the tasks returned from the server's repo sync call and find
the task that pertains to the sync itself.
:param task_list: list of tasks
:type task_list: list
:return: task for the sync
:rtype: Task
"""
sync_tag = tags.action_tag(tags.ACTION_SYNC_TYPE)
for t in task_list:
if sync_tag in t.tags:
return t
return None


def publish_task_in_sync_task_group(task_list):
"""
Grok through the tasks returned from the server's repo sync call and find
the task that pertains to the auto publish.
:param task_list: list of tasks
:type task_list: list
:return: task for the publish
:rtype: Task
"""
publish_tag = tags.action_tag(tags.ACTION_AUTO_PUBLISH_TYPE)
for t in task_list:
if publish_tag in t.tags:
return t
return None


108 changes: 108 additions & 0 deletions platform/src/pulp/client/commands/repo/sync_publish.py
@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2012 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public
# License as published by the Free Software Foundation; either version
# 2 of the License (GPLv2) or (at your option) any later version.
# There is NO WARRANTY for this software, express or implied,
# including the implied warranties of MERCHANTABILITY,
# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
# have received a copy of GPLv2 along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.

"""
Commands and hooks for creating and using sync, publish, and progress status
commands.
"""

from gettext import gettext as _

from pulp.client.commands import options
from pulp.client.commands.repo.status import status, tasks
from pulp.client.extensions.extensions import PulpCliCommand

# -- constants ----------------------------------------------------------------

# Command Descriptions

DESC_SYNC_RUN = _('triggers an immediate sync of a repository')
DESC_STATUS = _('displays the status of a repository\'s sync tasks')

NAME_BACKGROUND = 'bg'
DESC_BACKGROUND = _('if specified, the CLI process will end but the sync will continue on '
'the server; the progress can be later displayed using the status command')

# -- hooks --------------------------------------------------------------------

class StatusRenderer(object):

def __init__(self, context):
self.context = context
self.prompt = context.prompt

def display_report(self, progress_report):
raise NotImplementedError()

# -- commands -----------------------------------------------------------------

class RunSyncRepositoryCommand(PulpCliCommand):
"""
Requests an immediate sync for a repository. If the sync begins (it is not
postponed or rejected), the provided renderer will be used to track its
progress. The user has the option to exit the progress polling or skip it
entirely through a flag on the run command.
"""

def __init__(self, context, renderer):
super(RunSyncRepositoryCommand, self).__init__('run', DESC_SYNC_RUN, self.run)

self.context = context
self.prompt = context.prompt
self.renderer = renderer

self.add_option(options.OPTION_REPO_ID)
self.create_flag('--' + NAME_BACKGROUND, DESC_BACKGROUND)

def run(self, **kwargs):
repo_id = kwargs[options.OPTION_REPO_ID.keyword]
background = kwargs[NAME_BACKGROUND]

self.prompt.render_title(_('Synchronizing Repository [%(r)s]') % {'r' : repo_id})

# See if an existing sync is running for the repo. If it is, resume
# progress tracking.
existing_sync_tasks = self.context.server.tasks.get_repo_sync_tasks(repo_id).response_body
task_group_id = tasks.relevant_existing_task_group_id(existing_sync_tasks)

if task_group_id is not None:
msg = _('A sync task is already in progress for this repository. ')
if not background:
msg += _('Its progress will be tracked below.')
self.context.prompt.render_paragraph(msg, tag='in-progress')

else:
# Trigger the actual sync
response = self.context.server.repo_actions.sync(repo_id, None)
sync_task = tasks.sync_task_in_sync_task_group(response.response_body)
task_group_id = sync_task.task_group_id

if not background:
status.display_group_status(self.context, self.renderer, task_group_id)
else:
msg = _('The status of this sync can be displayed using the status command.')
self.context.prompt.render_paragraph(msg, 'background')


class SyncStatusCommand(PulpCliCommand):
def __init__(self, context, renderer):
super(SyncStatusCommand, self).__init__('status', DESC_STATUS, self.run)

self.context = context
self.prompt = context.prompt
self.renderer = renderer

self.add_option(options.OPTION_REPO_ID)

def run(self, **kwargs):
pass

0 comments on commit 4175908

Please sign in to comment.