Merge lp:~billy-olsen/charms/trusty/rabbitmq-server/ch-sync-cli-fix into lp:~openstack-charmers-archive/charms/trusty/rabbitmq-server/next

Proposed by Billy Olsen
Status: Merged
Merged at revision: 104
Proposed branch: lp:~billy-olsen/charms/trusty/rabbitmq-server/ch-sync-cli-fix
Merge into: lp:~openstack-charmers-archive/charms/trusty/rabbitmq-server/next
Diff against target: 424 lines (+366/-2)
8 files modified
charm-helpers-tests.yaml (+1/-0)
hooks/charmhelpers/core/hookenv.py (+16/-1)
tests/charmhelpers/cli/__init__.py (+195/-0)
tests/charmhelpers/cli/benchmark.py (+36/-0)
tests/charmhelpers/cli/commands.py (+32/-0)
tests/charmhelpers/cli/host.py (+31/-0)
tests/charmhelpers/cli/unitdata.py (+39/-0)
tests/charmhelpers/core/hookenv.py (+16/-1)
To merge this branch: bzr merge lp:~billy-olsen/charms/trusty/rabbitmq-server/ch-sync-cli-fix
Reviewer Review Type Date Requested Status
Edward Hope-Morley Approve
Ryan Beisner (community) Approve
Review via email: mp+266619@code.launchpad.net
To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #7375 rabbitmq-server-next for billy-olsen mp266619
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/7375/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #6843 rabbitmq-server-next for billy-olsen mp266619
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/6843/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #5546 rabbitmq-server-next for billy-olsen mp266619
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.

Full amulet test output: http://paste.ubuntu.com/11975719/
Build: http://10.245.162.77:8080/job/charm_amulet_test/5546/

104. By Billy Olsen

Include cli in the rmq amulet

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #7376 rabbitmq-server-next for billy-olsen mp266619
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/7376/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #6844 rabbitmq-server-next for billy-olsen mp266619
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/6844/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #5548 rabbitmq-server-next for billy-olsen mp266619
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.

Full amulet test output: http://paste.ubuntu.com/11976402/
Build: http://10.245.162.77:8080/job/charm_amulet_test/5548/

Revision history for this message
Ryan Beisner (1chb1n) wrote :

I would suggest merging this, even though the amulet test fails. It was previously failing that functional test.

This merge proposal resolves an import error, introduced by a charm-helpers CLI.

review: Approve
Revision history for this message
Ryan Beisner (1chb1n) wrote :

PS FYI, this is the only os-charm which had an amulet test affected by the CLI helper addition.

Revision history for this message
Ryan Beisner (1chb1n) wrote :

Regarding the pre-existing test failure, we will still need to address that separately.

Revision history for this message
Edward Hope-Morley (hopem) wrote :

Lets get this landed. It does not touch any code outside of amulet tests (which are broken anyway).

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charm-helpers-tests.yaml'
2--- charm-helpers-tests.yaml 2015-04-13 22:11:34 +0000
3+++ charm-helpers-tests.yaml 2015-07-31 22:01:37 +0000
4@@ -2,4 +2,5 @@
5 branch: lp:charm-helpers
6 include:
7 - core
8+ - cli
9 - contrib.ssl
10
11=== modified file 'hooks/charmhelpers/core/hookenv.py'
12--- hooks/charmhelpers/core/hookenv.py 2015-07-31 13:11:07 +0000
13+++ hooks/charmhelpers/core/hookenv.py 2015-07-31 22:01:37 +0000
14@@ -34,7 +34,22 @@
15 import tempfile
16 from subprocess import CalledProcessError
17
18-from charmhelpers.cli import cmdline
19+try:
20+ from charmhelpers.cli import cmdline
21+except ImportError as e:
22+ # due to the anti-pattern of partially synching charmhelpers directly
23+ # into charms, it's possible that charmhelpers.cli is not available;
24+ # if that's the case, they don't really care about using the cli anyway,
25+ # so mock it out
26+ if str(e) == 'No module named cli':
27+ class cmdline(object):
28+ @classmethod
29+ def subcommand(cls, *args, **kwargs):
30+ def _wrap(func):
31+ return func
32+ return _wrap
33+ else:
34+ raise
35
36 import six
37 if not six.PY3:
38
39=== added directory 'tests/charmhelpers/cli'
40=== added file 'tests/charmhelpers/cli/__init__.py'
41--- tests/charmhelpers/cli/__init__.py 1970-01-01 00:00:00 +0000
42+++ tests/charmhelpers/cli/__init__.py 2015-07-31 22:01:37 +0000
43@@ -0,0 +1,195 @@
44+# Copyright 2014-2015 Canonical Limited.
45+#
46+# This file is part of charm-helpers.
47+#
48+# charm-helpers is free software: you can redistribute it and/or modify
49+# it under the terms of the GNU Lesser General Public License version 3 as
50+# published by the Free Software Foundation.
51+#
52+# charm-helpers is distributed in the hope that it will be useful,
53+# but WITHOUT ANY WARRANTY; without even the implied warranty of
54+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
55+# GNU Lesser General Public License for more details.
56+#
57+# You should have received a copy of the GNU Lesser General Public License
58+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
59+
60+import inspect
61+import argparse
62+import sys
63+
64+from six.moves import zip
65+
66+from charmhelpers.core import unitdata
67+
68+
69+class OutputFormatter(object):
70+ def __init__(self, outfile=sys.stdout):
71+ self.formats = (
72+ "raw",
73+ "json",
74+ "py",
75+ "yaml",
76+ "csv",
77+ "tab",
78+ )
79+ self.outfile = outfile
80+
81+ def add_arguments(self, argument_parser):
82+ formatgroup = argument_parser.add_mutually_exclusive_group()
83+ choices = self.supported_formats
84+ formatgroup.add_argument("--format", metavar='FMT',
85+ help="Select output format for returned data, "
86+ "where FMT is one of: {}".format(choices),
87+ choices=choices, default='raw')
88+ for fmt in self.formats:
89+ fmtfunc = getattr(self, fmt)
90+ formatgroup.add_argument("-{}".format(fmt[0]),
91+ "--{}".format(fmt), action='store_const',
92+ const=fmt, dest='format',
93+ help=fmtfunc.__doc__)
94+
95+ @property
96+ def supported_formats(self):
97+ return self.formats
98+
99+ def raw(self, output):
100+ """Output data as raw string (default)"""
101+ if isinstance(output, (list, tuple)):
102+ output = '\n'.join(map(str, output))
103+ self.outfile.write(str(output))
104+
105+ def py(self, output):
106+ """Output data as a nicely-formatted python data structure"""
107+ import pprint
108+ pprint.pprint(output, stream=self.outfile)
109+
110+ def json(self, output):
111+ """Output data in JSON format"""
112+ import json
113+ json.dump(output, self.outfile)
114+
115+ def yaml(self, output):
116+ """Output data in YAML format"""
117+ import yaml
118+ yaml.safe_dump(output, self.outfile)
119+
120+ def csv(self, output):
121+ """Output data as excel-compatible CSV"""
122+ import csv
123+ csvwriter = csv.writer(self.outfile)
124+ csvwriter.writerows(output)
125+
126+ def tab(self, output):
127+ """Output data in excel-compatible tab-delimited format"""
128+ import csv
129+ csvwriter = csv.writer(self.outfile, dialect=csv.excel_tab)
130+ csvwriter.writerows(output)
131+
132+ def format_output(self, output, fmt='raw'):
133+ fmtfunc = getattr(self, fmt)
134+ fmtfunc(output)
135+
136+
137+class CommandLine(object):
138+ argument_parser = None
139+ subparsers = None
140+ formatter = None
141+ exit_code = 0
142+
143+ def __init__(self):
144+ if not self.argument_parser:
145+ self.argument_parser = argparse.ArgumentParser(description='Perform common charm tasks')
146+ if not self.formatter:
147+ self.formatter = OutputFormatter()
148+ self.formatter.add_arguments(self.argument_parser)
149+ if not self.subparsers:
150+ self.subparsers = self.argument_parser.add_subparsers(help='Commands')
151+
152+ def subcommand(self, command_name=None):
153+ """
154+ Decorate a function as a subcommand. Use its arguments as the
155+ command-line arguments"""
156+ def wrapper(decorated):
157+ cmd_name = command_name or decorated.__name__
158+ subparser = self.subparsers.add_parser(cmd_name,
159+ description=decorated.__doc__)
160+ for args, kwargs in describe_arguments(decorated):
161+ subparser.add_argument(*args, **kwargs)
162+ subparser.set_defaults(func=decorated)
163+ return decorated
164+ return wrapper
165+
166+ def test_command(self, decorated):
167+ """
168+ Subcommand is a boolean test function, so bool return values should be
169+ converted to a 0/1 exit code.
170+ """
171+ decorated._cli_test_command = True
172+ return decorated
173+
174+ def no_output(self, decorated):
175+ """
176+ Subcommand is not expected to return a value, so don't print a spurious None.
177+ """
178+ decorated._cli_no_output = True
179+ return decorated
180+
181+ def subcommand_builder(self, command_name, description=None):
182+ """
183+ Decorate a function that builds a subcommand. Builders should accept a
184+ single argument (the subparser instance) and return the function to be
185+ run as the command."""
186+ def wrapper(decorated):
187+ subparser = self.subparsers.add_parser(command_name)
188+ func = decorated(subparser)
189+ subparser.set_defaults(func=func)
190+ subparser.description = description or func.__doc__
191+ return wrapper
192+
193+ def run(self):
194+ "Run cli, processing arguments and executing subcommands."
195+ arguments = self.argument_parser.parse_args()
196+ argspec = inspect.getargspec(arguments.func)
197+ vargs = []
198+ kwargs = {}
199+ for arg in argspec.args:
200+ vargs.append(getattr(arguments, arg))
201+ if argspec.varargs:
202+ vargs.extend(getattr(arguments, argspec.varargs))
203+ if argspec.keywords:
204+ for kwarg in argspec.keywords.items():
205+ kwargs[kwarg] = getattr(arguments, kwarg)
206+ output = arguments.func(*vargs, **kwargs)
207+ if getattr(arguments.func, '_cli_test_command', False):
208+ self.exit_code = 0 if output else 1
209+ output = ''
210+ if getattr(arguments.func, '_cli_no_output', False):
211+ output = ''
212+ self.formatter.format_output(output, arguments.format)
213+ if unitdata._KV:
214+ unitdata._KV.flush()
215+
216+
217+cmdline = CommandLine()
218+
219+
220+def describe_arguments(func):
221+ """
222+ Analyze a function's signature and return a data structure suitable for
223+ passing in as arguments to an argparse parser's add_argument() method."""
224+
225+ argspec = inspect.getargspec(func)
226+ # we should probably raise an exception somewhere if func includes **kwargs
227+ if argspec.defaults:
228+ positional_args = argspec.args[:-len(argspec.defaults)]
229+ keyword_names = argspec.args[-len(argspec.defaults):]
230+ for arg, default in zip(keyword_names, argspec.defaults):
231+ yield ('--{}'.format(arg),), {'default': default}
232+ else:
233+ positional_args = argspec.args
234+
235+ for arg in positional_args:
236+ yield (arg,), {}
237+ if argspec.varargs:
238+ yield (argspec.varargs,), {'nargs': '*'}
239
240=== added file 'tests/charmhelpers/cli/benchmark.py'
241--- tests/charmhelpers/cli/benchmark.py 1970-01-01 00:00:00 +0000
242+++ tests/charmhelpers/cli/benchmark.py 2015-07-31 22:01:37 +0000
243@@ -0,0 +1,36 @@
244+# Copyright 2014-2015 Canonical Limited.
245+#
246+# This file is part of charm-helpers.
247+#
248+# charm-helpers is free software: you can redistribute it and/or modify
249+# it under the terms of the GNU Lesser General Public License version 3 as
250+# published by the Free Software Foundation.
251+#
252+# charm-helpers is distributed in the hope that it will be useful,
253+# but WITHOUT ANY WARRANTY; without even the implied warranty of
254+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
255+# GNU Lesser General Public License for more details.
256+#
257+# You should have received a copy of the GNU Lesser General Public License
258+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
259+
260+from . import cmdline
261+from charmhelpers.contrib.benchmark import Benchmark
262+
263+
264+@cmdline.subcommand(command_name='benchmark-start')
265+def start():
266+ Benchmark.start()
267+
268+
269+@cmdline.subcommand(command_name='benchmark-finish')
270+def finish():
271+ Benchmark.finish()
272+
273+
274+@cmdline.subcommand_builder('benchmark-composite', description="Set the benchmark composite score")
275+def service(subparser):
276+ subparser.add_argument("value", help="The composite score.")
277+ subparser.add_argument("units", help="The units the composite score represents, i.e., 'reads/sec'.")
278+ subparser.add_argument("direction", help="'asc' if a lower score is better, 'desc' if a higher score is better.")
279+ return Benchmark.set_composite_score
280
281=== added file 'tests/charmhelpers/cli/commands.py'
282--- tests/charmhelpers/cli/commands.py 1970-01-01 00:00:00 +0000
283+++ tests/charmhelpers/cli/commands.py 2015-07-31 22:01:37 +0000
284@@ -0,0 +1,32 @@
285+# Copyright 2014-2015 Canonical Limited.
286+#
287+# This file is part of charm-helpers.
288+#
289+# charm-helpers is free software: you can redistribute it and/or modify
290+# it under the terms of the GNU Lesser General Public License version 3 as
291+# published by the Free Software Foundation.
292+#
293+# charm-helpers is distributed in the hope that it will be useful,
294+# but WITHOUT ANY WARRANTY; without even the implied warranty of
295+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
296+# GNU Lesser General Public License for more details.
297+#
298+# You should have received a copy of the GNU Lesser General Public License
299+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
300+
301+"""
302+This module loads sub-modules into the python runtime so they can be
303+discovered via the inspect module. In order to prevent flake8 from (rightfully)
304+telling us these are unused modules, throw a ' # noqa' at the end of each import
305+so that the warning is suppressed.
306+"""
307+
308+from . import CommandLine # noqa
309+
310+"""
311+Import the sub-modules which have decorated subcommands to register with chlp.
312+"""
313+import host # noqa
314+import benchmark # noqa
315+import unitdata # noqa
316+from charmhelpers.core import hookenv # noqa
317
318=== added file 'tests/charmhelpers/cli/host.py'
319--- tests/charmhelpers/cli/host.py 1970-01-01 00:00:00 +0000
320+++ tests/charmhelpers/cli/host.py 2015-07-31 22:01:37 +0000
321@@ -0,0 +1,31 @@
322+# Copyright 2014-2015 Canonical Limited.
323+#
324+# This file is part of charm-helpers.
325+#
326+# charm-helpers is free software: you can redistribute it and/or modify
327+# it under the terms of the GNU Lesser General Public License version 3 as
328+# published by the Free Software Foundation.
329+#
330+# charm-helpers is distributed in the hope that it will be useful,
331+# but WITHOUT ANY WARRANTY; without even the implied warranty of
332+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
333+# GNU Lesser General Public License for more details.
334+#
335+# You should have received a copy of the GNU Lesser General Public License
336+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
337+
338+from . import cmdline
339+from charmhelpers.core import host
340+
341+
342+@cmdline.subcommand()
343+def mounts():
344+ "List mounts"
345+ return host.mounts()
346+
347+
348+@cmdline.subcommand_builder('service', description="Control system services")
349+def service(subparser):
350+ subparser.add_argument("action", help="The action to perform (start, stop, etc...)")
351+ subparser.add_argument("service_name", help="Name of the service to control")
352+ return host.service
353
354=== added file 'tests/charmhelpers/cli/unitdata.py'
355--- tests/charmhelpers/cli/unitdata.py 1970-01-01 00:00:00 +0000
356+++ tests/charmhelpers/cli/unitdata.py 2015-07-31 22:01:37 +0000
357@@ -0,0 +1,39 @@
358+# Copyright 2014-2015 Canonical Limited.
359+#
360+# This file is part of charm-helpers.
361+#
362+# charm-helpers is free software: you can redistribute it and/or modify
363+# it under the terms of the GNU Lesser General Public License version 3 as
364+# published by the Free Software Foundation.
365+#
366+# charm-helpers is distributed in the hope that it will be useful,
367+# but WITHOUT ANY WARRANTY; without even the implied warranty of
368+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
369+# GNU Lesser General Public License for more details.
370+#
371+# You should have received a copy of the GNU Lesser General Public License
372+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
373+
374+from . import cmdline
375+from charmhelpers.core import unitdata
376+
377+
378+@cmdline.subcommand_builder('unitdata', description="Store and retrieve data")
379+def unitdata_cmd(subparser):
380+ nested = subparser.add_subparsers()
381+ get_cmd = nested.add_parser('get', help='Retrieve data')
382+ get_cmd.add_argument('key', help='Key to retrieve the value of')
383+ get_cmd.set_defaults(action='get', value=None)
384+ set_cmd = nested.add_parser('set', help='Store data')
385+ set_cmd.add_argument('key', help='Key to set')
386+ set_cmd.add_argument('value', help='Value to store')
387+ set_cmd.set_defaults(action='set')
388+
389+ def _unitdata_cmd(action, key, value):
390+ if action == 'get':
391+ return unitdata.kv().get(key)
392+ elif action == 'set':
393+ unitdata.kv().set(key, value)
394+ unitdata.kv().flush()
395+ return ''
396+ return _unitdata_cmd
397
398=== modified file 'tests/charmhelpers/core/hookenv.py'
399--- tests/charmhelpers/core/hookenv.py 2015-07-31 13:11:07 +0000
400+++ tests/charmhelpers/core/hookenv.py 2015-07-31 22:01:37 +0000
401@@ -34,7 +34,22 @@
402 import tempfile
403 from subprocess import CalledProcessError
404
405-from charmhelpers.cli import cmdline
406+try:
407+ from charmhelpers.cli import cmdline
408+except ImportError as e:
409+ # due to the anti-pattern of partially synching charmhelpers directly
410+ # into charms, it's possible that charmhelpers.cli is not available;
411+ # if that's the case, they don't really care about using the cli anyway,
412+ # so mock it out
413+ if str(e) == 'No module named cli':
414+ class cmdline(object):
415+ @classmethod
416+ def subcommand(cls, *args, **kwargs):
417+ def _wrap(func):
418+ return func
419+ return _wrap
420+ else:
421+ raise
422
423 import six
424 if not six.PY3:

Subscribers

People subscribed via source and target branches