baca313dd5
Here we are adding tenant support and re-enabling unit tests for enqueue and promote. Change-Id: I384128b9b14be1dc3c4a0c914dcaf13d30f1792f Signed-off-by: Paul Belanger <pabelanger@redhat.com>
314 lines
11 KiB
Python
314 lines
11 KiB
Python
#!/usr/bin/env python
|
|
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
|
# Copyright 2013 OpenStack Foundation
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import argparse
|
|
import babel.dates
|
|
import datetime
|
|
import logging
|
|
import prettytable
|
|
import sys
|
|
import time
|
|
|
|
|
|
import zuul.rpcclient
|
|
import zuul.cmd
|
|
|
|
|
|
class Client(zuul.cmd.ZuulApp):
|
|
log = logging.getLogger("zuul.Client")
|
|
|
|
def parse_arguments(self):
|
|
parser = argparse.ArgumentParser(
|
|
description='Zuul Project Gating System Client.')
|
|
parser.add_argument('-c', dest='config',
|
|
help='specify the config file')
|
|
parser.add_argument('-v', dest='verbose', action='store_true',
|
|
help='verbose output')
|
|
parser.add_argument('--version', dest='version', action='version',
|
|
version=self._get_version(),
|
|
help='show zuul version')
|
|
|
|
subparsers = parser.add_subparsers(title='commands',
|
|
description='valid commands',
|
|
help='additional help')
|
|
|
|
cmd_enqueue = subparsers.add_parser('enqueue', help='enqueue a change')
|
|
cmd_enqueue.add_argument('--tenant', help='tenant name',
|
|
required=True)
|
|
cmd_enqueue.add_argument('--trigger', help='trigger name',
|
|
required=True)
|
|
cmd_enqueue.add_argument('--pipeline', help='pipeline name',
|
|
required=True)
|
|
cmd_enqueue.add_argument('--project', help='project name',
|
|
required=True)
|
|
cmd_enqueue.add_argument('--change', help='change id',
|
|
required=True)
|
|
cmd_enqueue.set_defaults(func=self.enqueue)
|
|
|
|
cmd_enqueue = subparsers.add_parser('enqueue-ref',
|
|
help='enqueue a ref')
|
|
cmd_enqueue.add_argument('--tenant', help='tenant name',
|
|
required=True)
|
|
cmd_enqueue.add_argument('--trigger', help='trigger name',
|
|
required=True)
|
|
cmd_enqueue.add_argument('--pipeline', help='pipeline name',
|
|
required=True)
|
|
cmd_enqueue.add_argument('--project', help='project name',
|
|
required=True)
|
|
cmd_enqueue.add_argument('--ref', help='ref name',
|
|
required=True)
|
|
cmd_enqueue.add_argument(
|
|
'--oldrev', help='old revision',
|
|
default='0000000000000000000000000000000000000000')
|
|
cmd_enqueue.add_argument(
|
|
'--newrev', help='new revision',
|
|
default='0000000000000000000000000000000000000000')
|
|
cmd_enqueue.set_defaults(func=self.enqueue_ref)
|
|
|
|
cmd_promote = subparsers.add_parser('promote',
|
|
help='promote one or more changes')
|
|
cmd_promote.add_argument('--tenant', help='tenant name',
|
|
required=True)
|
|
cmd_promote.add_argument('--pipeline', help='pipeline name',
|
|
required=True)
|
|
cmd_promote.add_argument('--changes', help='change ids',
|
|
required=True, nargs='+')
|
|
cmd_promote.set_defaults(func=self.promote)
|
|
|
|
cmd_show = subparsers.add_parser('show',
|
|
help='valid show subcommands')
|
|
show_subparsers = cmd_show.add_subparsers(title='show')
|
|
show_running_jobs = show_subparsers.add_parser(
|
|
'running-jobs',
|
|
help='show the running jobs'
|
|
)
|
|
show_running_jobs.add_argument(
|
|
'--columns',
|
|
help="comma separated list of columns to display (or 'ALL')",
|
|
choices=self._show_running_jobs_columns().keys().append('ALL'),
|
|
default='name, worker.name, start_time, result'
|
|
)
|
|
|
|
# TODO: add filters such as queue, project, changeid etc
|
|
show_running_jobs.set_defaults(func=self.show_running_jobs)
|
|
|
|
self.args = parser.parse_args()
|
|
if self.args.func == self.enqueue_ref:
|
|
if self.args.oldrev == self.args.newrev:
|
|
parser.error("The old and new revisions must not be the same.")
|
|
|
|
def setup_logging(self):
|
|
"""Client logging does not rely on conf file"""
|
|
if self.args.verbose:
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
|
|
def main(self):
|
|
self.parse_arguments()
|
|
self.read_config()
|
|
self.setup_logging()
|
|
|
|
self.server = self.config.get('gearman', 'server')
|
|
if self.config.has_option('gearman', 'port'):
|
|
self.port = self.config.get('gearman', 'port')
|
|
else:
|
|
self.port = 4730
|
|
|
|
if self.args.func():
|
|
sys.exit(0)
|
|
else:
|
|
sys.exit(1)
|
|
|
|
def enqueue(self):
|
|
client = zuul.rpcclient.RPCClient(self.server, self.port)
|
|
r = client.enqueue(tenant=self.args.tenant,
|
|
pipeline=self.args.pipeline,
|
|
project=self.args.project,
|
|
trigger=self.args.trigger,
|
|
change=self.args.change)
|
|
return r
|
|
|
|
def enqueue_ref(self):
|
|
client = zuul.rpcclient.RPCClient(self.server, self.port)
|
|
r = client.enqueue_ref(tenant=self.args.tenant,
|
|
pipeline=self.args.pipeline,
|
|
project=self.args.project,
|
|
trigger=self.args.trigger,
|
|
ref=self.args.ref,
|
|
oldrev=self.args.oldrev,
|
|
newrev=self.args.newrev)
|
|
return r
|
|
|
|
def promote(self):
|
|
client = zuul.rpcclient.RPCClient(self.server, self.port)
|
|
r = client.promote(tenant=self.args.tenant,
|
|
pipeline=self.args.pipeline,
|
|
change_ids=self.args.changes)
|
|
return r
|
|
|
|
def show_running_jobs(self):
|
|
client = zuul.rpcclient.RPCClient(self.server, self.port)
|
|
running_items = client.get_running_jobs()
|
|
|
|
if len(running_items) == 0:
|
|
print("No jobs currently running")
|
|
return True
|
|
|
|
all_fields = self._show_running_jobs_columns()
|
|
if self.args.columns.upper() == 'ALL':
|
|
fields = all_fields.keys()
|
|
else:
|
|
fields = [f.strip().lower() for f in self.args.columns.split(',')
|
|
if f.strip().lower() in all_fields.keys()]
|
|
|
|
table = prettytable.PrettyTable(
|
|
field_names=[all_fields[f]['title'] for f in fields])
|
|
for item in running_items:
|
|
for job in item['jobs']:
|
|
values = []
|
|
for f in fields:
|
|
v = job
|
|
for part in f.split('.'):
|
|
if hasattr(v, 'get'):
|
|
v = v.get(part, '')
|
|
if ('transform' in all_fields[f]
|
|
and callable(all_fields[f]['transform'])):
|
|
v = all_fields[f]['transform'](v)
|
|
if 'append' in all_fields[f]:
|
|
v += all_fields[f]['append']
|
|
values.append(v)
|
|
table.add_row(values)
|
|
print(table)
|
|
return True
|
|
|
|
def _epoch_to_relative_time(self, epoch):
|
|
if epoch:
|
|
delta = datetime.timedelta(seconds=(time.time() - int(epoch)))
|
|
return babel.dates.format_timedelta(delta, locale='en_US')
|
|
else:
|
|
return "Unknown"
|
|
|
|
def _boolean_to_yes_no(self, value):
|
|
return 'Yes' if value else 'No'
|
|
|
|
def _boolean_to_pass_fail(self, value):
|
|
return 'Pass' if value else 'Fail'
|
|
|
|
def _format_list(self, l):
|
|
return ', '.join(l) if isinstance(l, list) else ''
|
|
|
|
def _show_running_jobs_columns(self):
|
|
"""A helper function to get the list of available columns for
|
|
`zuul show running-jobs`. Also describes how to convert particular
|
|
values (for example epoch to time string)"""
|
|
|
|
return {
|
|
'name': {
|
|
'title': 'Job Name',
|
|
},
|
|
'elapsed_time': {
|
|
'title': 'Elapsed Time',
|
|
'transform': self._epoch_to_relative_time
|
|
},
|
|
'remaining_time': {
|
|
'title': 'Remaining Time',
|
|
'transform': self._epoch_to_relative_time
|
|
},
|
|
'url': {
|
|
'title': 'URL'
|
|
},
|
|
'result': {
|
|
'title': 'Result'
|
|
},
|
|
'voting': {
|
|
'title': 'Voting',
|
|
'transform': self._boolean_to_yes_no
|
|
},
|
|
'uuid': {
|
|
'title': 'UUID'
|
|
},
|
|
'launch_time': {
|
|
'title': 'Launch Time',
|
|
'transform': self._epoch_to_relative_time,
|
|
'append': ' ago'
|
|
},
|
|
'start_time': {
|
|
'title': 'Start Time',
|
|
'transform': self._epoch_to_relative_time,
|
|
'append': ' ago'
|
|
},
|
|
'end_time': {
|
|
'title': 'End Time',
|
|
'transform': self._epoch_to_relative_time,
|
|
'append': ' ago'
|
|
},
|
|
'estimated_time': {
|
|
'title': 'Estimated Time',
|
|
'transform': self._epoch_to_relative_time,
|
|
'append': ' to go'
|
|
},
|
|
'pipeline': {
|
|
'title': 'Pipeline'
|
|
},
|
|
'canceled': {
|
|
'title': 'Canceled',
|
|
'transform': self._boolean_to_yes_no
|
|
},
|
|
'retry': {
|
|
'title': 'Retry'
|
|
},
|
|
'number': {
|
|
'title': 'Number'
|
|
},
|
|
'node_labels': {
|
|
'title': 'Node Labels'
|
|
},
|
|
'node_name': {
|
|
'title': 'Node Name'
|
|
},
|
|
'worker.name': {
|
|
'title': 'Worker'
|
|
},
|
|
'worker.hostname': {
|
|
'title': 'Worker Hostname'
|
|
},
|
|
'worker.ips': {
|
|
'title': 'Worker IPs',
|
|
'transform': self._format_list
|
|
},
|
|
'worker.fqdn': {
|
|
'title': 'Worker Domain'
|
|
},
|
|
'worker.program': {
|
|
'title': 'Worker Program'
|
|
},
|
|
'worker.version': {
|
|
'title': 'Worker Version'
|
|
},
|
|
'worker.extra': {
|
|
'title': 'Worker Extra'
|
|
},
|
|
}
|
|
|
|
|
|
def main():
|
|
client = Client()
|
|
client.main()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.path.insert(0, '.')
|
|
main()
|