Merge "Support pagination and filtering for job list operations"

This commit is contained in:
Zuul 2017-10-19 02:27:30 +00:00 committed by Gerrit Code Review
commit 4bf13ec7e2
9 changed files with 257 additions and 100 deletions

View File

@ -44,9 +44,9 @@ job_resource_map = {
# listed by alphabet order.
COLUMNS = ('id', 'project_id', 'status', 'timestamp', 'type')
# column headers that show in command line.
COLUMNS_REMAP = {'id': 'Id',
'project_id': 'Project id',
# column headers about job that show in command line.
COLUMNS_REMAP = {'id': 'ID',
'project_id': 'Project',
'timestamp': 'Timestamp',
'status': 'Status',
'type': 'Type'}

View File

@ -67,7 +67,7 @@ class TestShowJob(_TestJobCommand, utils.TestCommandWithoutOptions):
_job['job']['id'],
]
verifylist = [
('id', _job['job']['id']),
('job', _job['job']['id']),
]
self.job_manager.get = mock.Mock(return_value=_job)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -88,7 +88,6 @@ class TestListJob(_TestJobCommand):
self.job_manager.list = mock.Mock(return_value={'jobs': _jobs})
parsed_args = self.check_parser(self.cmd)
columns, data = (self.cmd.take_action(parsed_args))
self.assertEqual(sorted(constants.COLUMNS_REMAP.values()),
sorted(columns))
self.assertEqual(len(_jobs), len(data))
@ -96,6 +95,96 @@ class TestListJob(_TestJobCommand):
sorted([tuple(o[k] for k in constants.COLUMNS) for o in _jobs]),
sorted(data))
def test_list_with_filters(self):
_job = utils.FakeJob.create_single_job()
_job = _job['job']
# we filter the jobs by the following fields: project ID, type, status.
# given values of _job, then only single item _job is retrieved.
arglist = [
'--project-id', _job['project_id'],
'--type', _job['type'],
'--status', _job['status'],
]
verifylist = [
('project_id', _job['project_id']),
('type', _job['type']),
('status', _job['status'].lower()),
]
self.job_manager.list = mock.Mock(return_value={'jobs': [_job]})
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = (self.cmd.take_action(parsed_args))
self.assertEqual(1, len(data))
self.assertEqual(sorted(constants.COLUMNS_REMAP.values()),
sorted(columns))
# lower case of job status
arglist = [
'--status', _job['status'].lower(),
]
verifylist = [
('status', _job['status'].lower()),
]
self.job_manager.list = mock.Mock(return_value={'jobs': [_job]})
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = (self.cmd.take_action(parsed_args))
self.assertEqual(1, len(data))
self.assertEqual(sorted(constants.COLUMNS_REMAP.values()),
sorted(columns))
def test_invalid_job_status_filter(self):
# unrecognizable job status filter
arglist = [
'--status', 'new_1',
]
verifylist = []
self.assertRaises(utils.ParserException, self.check_parser,
self.cmd, arglist, verifylist)
def test_list_with_pagination(self):
number_of_jobs = 4
limit = number_of_jobs - 2
_jobs = utils.FakeJob.create_multiple_jobs(count=number_of_jobs)
# test list operation with pagination
arglist = [
'--limit', str(limit),
]
verifylist = [
('limit', limit),
]
self.job_manager.list = mock.Mock(return_value={"jobs": _jobs[:limit]})
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = (self.cmd.take_action(parsed_args))
self.assertEqual(limit, len(data))
self.assertEqual(sorted(constants.COLUMNS_REMAP.values()),
sorted(columns))
# test list operation with pagination and marker
arglist = [
'--limit', str(limit),
'--marker', _jobs[0]['id'],
]
verifylist = [
('limit', limit),
('marker', _jobs[0]['id']),
]
self.job_manager.list = mock.Mock(
return_value={"jobs": _jobs[1:limit+1]})
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = (self.cmd.take_action(parsed_args))
self.assertEqual(limit, len(data))
self.assertEqual(sorted(constants.COLUMNS_REMAP.values()),
sorted(columns))
class TestDeleteJob(_TestJobCommand, utils.TestCommandWithoutOptions):
@ -109,7 +198,7 @@ class TestDeleteJob(_TestJobCommand, utils.TestCommandWithoutOptions):
_job['job']['id'],
]
verifylist = [
('id', _job['job']['id']),
('job', [_job['job']['id']]),
]
self.job_manager.delete = mock.Mock(return_value=None)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -130,7 +219,7 @@ class TestRedoJob(_TestJobCommand, utils.TestCommandWithoutOptions):
_job['job']['id'],
]
verifylist = [
('id', _job['job']['id']),
('job', _job['job']['id']),
]
self.job_manager.update = mock.Mock(return_value=None)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)

View File

@ -126,8 +126,8 @@ class TestShowPod(_TestPodCommand, utils.TestCommandWithoutOptions):
class TestListPod(_TestPodCommand):
columns = [
'Id',
'Region name',
'ID',
'Region Name',
]
def setUp(self):

View File

@ -79,10 +79,10 @@ class TestShowRouting(_TestRoutingCommand, utils.TestCommandWithoutOptions):
class TestListRouting(_TestRoutingCommand):
columns = [
'Id',
'Pod id',
'Resource type',
'Top id'
'ID',
'Pod ID',
'Resource Type',
'Top ID'
]
def setUp(self):
@ -100,8 +100,7 @@ class TestListRouting(_TestRoutingCommand):
return_value={'routings': _routings})
parsed_args = self.check_parser(self.cmd)
columns, data = (self.cmd.take_action(parsed_args))
self.assertEqual(self.columns, sorted(columns))
self.assertEqual(sorted(self.columns), sorted(columns))
self.assertEqual(len(_routings), len(data))
def test_list_with_filters(self):
@ -151,7 +150,7 @@ class TestListRouting(_TestRoutingCommand):
def test_list_with_pagination(self):
_routings = utils.FakeRouting.create_multiple_routings(count=3)
arglist = [
'--page-size', '2',
'--limit', '2',
'--marker', _routings[0]['id'],
]
verifylist = [
@ -167,7 +166,7 @@ class TestListRouting(_TestRoutingCommand):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = (self.cmd.take_action(parsed_args))
self.assertEqual(self.columns, sorted(columns))
self.assertEqual(sorted(self.columns), sorted(columns))
self.assertEqual(2, len(data))
@ -178,12 +177,14 @@ class TestDeleteRouting(_TestRoutingCommand, utils.TestCommandWithoutOptions):
self.cmd = routings_cli.DeleteRouting(self.app, None)
def test_delete_routing(self):
_routing = utils.FakeRouting.create_single_routing()
_routings = utils.FakeRouting.create_multiple_routings(count=2)
arglist = [
_routing['routing']['id'],
_routings[0]['id'],
_routings[1]['id'],
]
verifylist = [
('routing', [_routing['routing']['id']]),
('routing', [_routings[0]['id'], _routings[1]['id']]),
]
self.routing_manager.delete = mock.Mock(return_value=None)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)

View File

@ -17,7 +17,7 @@ def prepare_column_headers(columns, remap=None):
for c in columns:
for old, new in remap.items():
c = c.replace(old, new)
new_columns.append(c.replace('_', ' ').capitalize())
new_columns.append(c.replace('_', ' '))
return new_columns

View File

@ -18,10 +18,10 @@ from tricircleclient.v1 import base
class JobManager(base.Manager):
def list(self, search_opts=None):
def list(self, path):
"""Get a list of jobs."""
return self._get(
'/jobs',
path,
headers={'Content-Type': "application/json"}).json()
def create(self, job):

View File

@ -13,6 +13,7 @@
from osc_lib.command import command
from oslo_log import log as logging
from six.moves.urllib import parse
from tricircleclient import constants
from tricircleclient import utils
@ -33,6 +34,59 @@ def _job_from_args(parsed_args):
return {'job': data}
def _add_pagination_argument(parser):
parser.add_argument(
'--limit',
dest='limit', metavar="<num-jobs>", type=int,
help="Maximum number of jobs to return",
default=None)
def _add_marker_argument(parser):
parser.add_argument(
'--marker',
dest='marker', metavar="<job>", type=str,
help="ID of last job in previous page, jobs after marker will be "
"returned. Display all jobs if not specified.",
default=None)
def _add_filtering_arguments(parser):
# available filtering fields: project ID, type, status
parser.add_argument(
'--project-id',
dest='project_id', metavar="<project-id>", type=str,
help="ID of a project object in Keystone",
default=None)
parser.add_argument(
'--type',
dest='type', metavar="<type>", type=str,
choices=constants.job_resource_map.keys(),
help="Job type",
default=None)
parser.add_argument(
'--status',
dest='status', metavar="<status>", type=lambda str: str.lower(),
choices=['new', 'running', 'success', 'fail'],
help="Execution status of the job. It's case-insensitive",
default=None)
def _add_search_options(parsed_args):
search_opts = {}
for key in ('limit', 'marker', 'project_id', 'type', 'status'):
value = getattr(parsed_args, key, None)
if value is not None:
search_opts[key] = value
return search_opts
def _prepare_query_string(params):
"""Convert dict params to query string"""
params = sorted(params.items(), key=lambda x: x[0])
return '?%s' % parse.urlencode(params) if params else ''
def expand_job_resource(job):
# because job['resource'] is a dict value, so we should
# expand its values and let them show as other fields in the
@ -47,10 +101,26 @@ class ListJobs(command.Lister):
"""List Jobs"""
log = logging.getLogger(__name__ + ".ListJobs")
path = '/jobs'
def get_parser(self, prog_name):
parser = super(ListJobs, self).get_parser(prog_name)
_add_pagination_argument(parser)
_add_marker_argument(parser)
_add_filtering_arguments(parser)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args)
client = self.app.client_manager.multiregion_networking
data = client.job.list()
# add pagination/marker/filter to list operation
search_opts = _add_search_options(parsed_args)
self.path += _prepare_query_string(search_opts)
data = client.job.list(self.path)
column_headers = utils.prepare_column_headers(constants.COLUMNS,
constants.COLUMNS_REMAP)
return utils.list2cols(constants.COLUMNS, data['jobs'], column_headers)
@ -75,44 +145,44 @@ class CreateJob(command.ShowOne):
)
parser.add_argument(
'--project_id',
metavar="<project_id>",
metavar="<project-id>",
required=True,
help="Uuid of a project object in Keystone",
help="ID of a project object in Keystone",
)
parser.add_argument(
'--router_id',
metavar="<router_id>",
help="Uuid of a router",
metavar="<router-id>",
help="ID of a router",
)
parser.add_argument(
'--network_id',
metavar="<network_id>",
help="Uuid of a network",
metavar="<network-id>",
help="ID of a network",
)
parser.add_argument(
'--pod_id',
metavar="<pod_id>",
help="Uuid of a pod",
metavar="<pod-id>",
help="ID of a pod",
)
parser.add_argument(
'--port_id',
metavar="<port_id>",
help="Uuid of a port",
metavar="<port-id>",
help="ID of a port",
)
parser.add_argument(
'--trunk_id',
metavar="<trunk_id>",
help="Uuid of a trunk",
metavar="<trunk-id>",
help="ID of a trunk",
)
parser.add_argument(
'--subnet_id',
metavar="<subnet_id>",
help="Uuid of a subnet",
metavar="<subnet-id>",
help="ID of a subnet",
)
parser.add_argument(
'--portchain_id',
metavar="<portchain_id>",
help="Uuid of a port chain",
metavar="<portchain-id>",
help="ID of a port chain",
)
return parser
@ -132,16 +202,16 @@ class ShowJob(command.ShowOne):
def get_parser(self, prog_name):
parser = super(ShowJob, self).get_parser(prog_name)
parser.add_argument(
"id",
metavar="<id>",
help="Uuid of the job to display",
"job",
metavar="<job>",
help="ID of the job to display",
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args)
client = self.app.client_manager.multiregion_networking
data = client.job.get(parsed_args.id)
data = client.job.get(parsed_args.job)
if 'job' in data.keys():
return self.dict2columns(expand_job_resource(data['job']))
@ -155,16 +225,18 @@ class DeleteJob(command.Command):
def get_parser(self, prog_name):
parser = super(DeleteJob, self).get_parser(prog_name)
parser.add_argument(
"id",
metavar="<id>",
help="Uuid of the job to delete",
"job",
metavar="<job>",
nargs="+",
help="ID(s) of the job(s) to delete",
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args)
client = self.app.client_manager.multiregion_networking
client.job.delete(parsed_args.id)
for job_id in parsed_args.job:
client.job.delete(job_id)
class RedoJob(command.Command):
@ -176,13 +248,13 @@ class RedoJob(command.Command):
parser = super(RedoJob, self).get_parser(prog_name)
parser.add_argument(
'id',
metavar="<id>",
help="Uuid of the job to redo",
'job',
metavar="<job>",
help="ID of the job to redo",
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args)
client = self.app.client_manager.multiregion_networking
client.job.update(parsed_args.id)
client.job.update(parsed_args.job)

View File

@ -28,7 +28,7 @@ class ListPods(command.Lister):
self.log.debug("take_action(%s)" % parsed_args)
client = self.app.client_manager.multiregion_networking
data = client.pod.list()
remap = {'pod_id': 'Id',
remap = {'pod_id': 'ID',
'region_name': 'Region Name',
'az_name': 'Availability Zone',
'dc_name': 'Data Center'}

View File

@ -33,50 +33,50 @@ def _routing_from_args(parsed_args):
def _add_pagination_argument(parser):
parser.add_argument(
'-P', '--page-size',
dest='limit', metavar='SIZE', type=int,
'--limit',
dest='limit', metavar="<num-routings>", type=int,
help="Maximum number of routings to return",
default=None)
def _add_marker_argument(parser):
parser.add_argument(
'-M', '--marker',
dest='marker', metavar='MARKER', type=str,
help="Starting point for next routing list "
"that shares same routing id",
'--marker',
dest='marker', metavar="<routing>", type=str,
help="ID of last routing in previous page, routings after marker "
"will be returned. Display all routings if not specified.",
default=None)
def _add_filtering_arguments(parser):
parser.add_argument(
'--id',
dest='id', metavar='id', type=int,
help="Uuid of a routing",
'--routing',
dest='routing', metavar="<routing>", type=int,
help="ID of a routing",
default=None)
parser.add_argument(
'--top-id',
dest='top_id', metavar='top_id', type=str,
dest='top_id', metavar="<top-id>", type=str,
help="Resource id on Central Neutron",
default=None)
parser.add_argument(
'--bottom-id',
dest='bottom_id', metavar='bottom_id', type=str,
dest='bottom_id', metavar="<bottom-id>", type=str,
help="Resource id on Local Neutron",
default=None)
parser.add_argument(
'--pod-id',
dest='pod_id', metavar='pod_id', type=str,
help="Uuid of a pod",
dest='pod_id', metavar="<pod-id>", type=str,
help="ID of a pod",
default=None)
parser.add_argument(
'--project-id',
dest='project_id', metavar='project_id', type=str,
help="Uuid of a project object in Keystone",
dest='project_id', metavar="<project-id>", type=str,
help="ID of a project object in Keystone",
default=None)
parser.add_argument(
'--resource-type',
dest='resource_type', metavar='resource_type', type=str,
dest='resource_type', metavar="<resource-type>", type=str,
choices=['network', 'subnet', 'port', 'router', 'security_group',
'trunk', 'port_pair', 'port_pair_group', 'flow_classifier',
'port_chain'],
@ -84,12 +84,12 @@ def _add_filtering_arguments(parser):
default=None)
parser.add_argument(
'--created-at',
dest='created_at', metavar='created_at', type=str,
dest='created_at', metavar="<created-at>", type=str,
help="Create time of the resource routing",
default=None)
parser.add_argument(
'--updated-at',
dest='updated_at', metavar='updated_at', type=str,
dest='updated_at', metavar="<updated-at>", type=str,
help="Update time of the resource routing",
default=None)
@ -116,18 +116,13 @@ class ListRoutings(command.Lister):
COLS = ('id', 'pod_id', 'resource_type', 'top_id')
path = '/routings'
pagination_support = True
marker_support = True
log = logging.getLogger(__name__ + ".ListRoutings")
def get_parser(self, prog_name):
parser = super(ListRoutings, self).get_parser(prog_name)
if self.pagination_support:
_add_pagination_argument(parser)
if self.marker_support:
_add_marker_argument(parser)
_add_pagination_argument(parser)
_add_marker_argument(parser)
_add_filtering_arguments(parser)
return parser
@ -141,13 +136,13 @@ class ListRoutings(command.Lister):
self.path += _prepare_query_string(search_opts)
data = client.routing.list(self.path)
remap = {'id': 'Id',
'pod_id': 'Pod Id',
'resource_type': 'Resource Type',
'top_id': 'Top Id'}
remap = {'resource_type': 'Resource Type',
'pod': 'Pod',
'id': 'ID',
'top': 'Top',
}
column_headers = utils.prepare_column_headers(self.COLS,
remap)
return utils.list2cols(
self.COLS, data['routings'], column_headers)
@ -162,31 +157,31 @@ class CreateRouting(command.ShowOne):
parser.add_argument(
'--top-id',
metavar="<top_id>",
metavar="<top-id>",
required=True,
help="Resource id on Central Neutron",
)
parser.add_argument(
'--bottom-id',
metavar="<bottom_id>",
metavar="<bottom-id>",
required=True,
help="Resource id on Local Neutron",
)
parser.add_argument(
'--pod-id',
metavar="<pod_id>",
metavar="<pod-id>",
required=True,
help="Uuid of a pod",
help="ID of a pod",
)
parser.add_argument(
'--project-id',
metavar="<project_id>",
metavar="<project-id>",
required=True,
help="Uuid of a project object in Keystone",
help="ID of a project object in Keystone",
)
parser.add_argument(
'--resource-type',
metavar="<resource_type>",
metavar="<resource-type>",
choices=['network', 'subnet', 'port', 'router', 'security_group'],
required=True,
help="Available resource types",
@ -211,7 +206,7 @@ class ShowRouting(command.ShowOne):
parser.add_argument(
"routing",
metavar="<routing>",
help="Id of the routing resource to display",
help="ID of the routing resource to display",
)
return parser
@ -234,7 +229,7 @@ class DeleteRouting(command.Command):
parser = super(DeleteRouting, self).get_parser(prog_name)
parser.add_argument(
"routing",
metavar="<Routing>",
metavar="<routing>",
nargs="+",
help="ID(s) of the routing resource(s) to delete",
)
@ -258,33 +253,33 @@ class UpdateRouting(command.Command):
parser.add_argument(
'--top-id',
metavar="<top_id>",
metavar="<top-id>",
help="Resource id on Central Neutron",
)
parser.add_argument(
'--bottom-id',
metavar="<bottom_id>",
metavar="<bottom-id>",
help="Resource id on Local Neutron",
)
parser.add_argument(
'--pod-id',
metavar="<pod_id>",
help="Uuid of a pod",
metavar="<pod-id>",
help="ID of a pod",
)
parser.add_argument(
'--project-id',
metavar="<project_id>",
help="Uuid of a project object in Keystone",
metavar="<project-id>",
help="ID of a project object in Keystone",
)
parser.add_argument(
'--resource-type',
metavar="<resource_type>",
metavar="<resource-type>",
choices=['network', 'subnet', 'port', 'router', 'security_group'],
help="Available resource types",
)
parser.add_argument(
"routing",
metavar="<Routing>",
metavar="<routing>",
help="ID of the routing resource to update",
)
return parser