Merge lp:~nskaggs/juju-ci-tools/add-sane-args-defaults into lp:juju-ci-tools

Proposed by Nicholas Skaggs
Status: Merged
Merged at revision: 1513
Proposed branch: lp:~nskaggs/juju-ci-tools/add-sane-args-defaults
Merge into: lp:juju-ci-tools
Diff against target: 256 lines (+91/-64)
4 files modified
deploy_stack.py (+28/-0)
tests/test_deploy_stack.py (+32/-0)
tests/test_utility.py (+23/-37)
utility.py (+8/-27)
To merge this branch: bzr merge lp:~nskaggs/juju-ci-tools/add-sane-args-defaults
Reviewer Review Type Date Requested Status
Martin Packman (community) Approve
Christopher Lee Pending
Review via email: mp+299588@code.launchpad.net

This proposal supersedes a proposal from 2016-06-02.

Description of the change

Make juju_bin, temp_env_name and logs all as optional arguments with sane defaults

To post a comment you must log in.
Revision history for this message
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal

Reviewers note; I'm tempted to add a timestamp to the environment name or otherwise make it more unique. Ideas welcome. Would you find that useful or annoying (especially since it may not be the exact same ts as the logging folder)?

Revision history for this message
Martin Packman (gz) wrote : Posted in a previous version of this proposal

The general idea of having sane defaults for the positional arguments I'm down with, I think we could even default 'env' to 'lxd' or something.

Given this changes add_basic_testing_arguments, TestAddBasicTestingArguments should be changed.

$ rm -rf /tmp/logs
$ touch /tmp/logs
$ make test
...
FAILED (failures=2, errors=37)

Some other comments inline.

review: Needs Fixing
Revision history for this message
Aaron Bentley (abentley) wrote : Posted in a previous version of this proposal

It seems strange to provide a default for the juju under test. It is, after all, the thing we are testing, and if we don't want to specify it, we might as well not run the test at all.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal

Replies.

Revision history for this message
Aaron Bentley (abentley) wrote : Posted in a previous version of this proposal

You don't seem to have replied to me.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal

@abentley, I suppose that is the crux of the argument for /usr/bin/juju vs $GOPATH/bin/juju. Odds are you care enough to specify. However, in the case of someone wanting to run the tests locally, they might not particularly care beyond "use the juju I have".

However, I went full bore on Martin's idea of going ahead and removing env too and making a sane default for it. I think the ability to just be able to run our tests is useful. For those of us who will actually attempt to do CI, we'll always want to specify everything, but we're presumably not the primary audience here (other than our localized test writing).

Revision history for this message
Aaron Bentley (abentley) wrote : Posted in a previous version of this proposal

> However, in the case of
> someone wanting to run the tests locally, they might not particularly care
> beyond "use the juju I have".

Who is that person? Are they a juju developer?

If so, I think they are probably
1. writing a test for a feature they have written, or
2. running a test to make sure their changes don't break it

In both cases, they most likely have a custom binary that they want to test against.

So I still don't see who you're thinking would want to omit the path to the juju binary.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal

> > However, in the case of
> > someone wanting to run the tests locally, they might not particularly care
> > beyond "use the juju I have".
>
> Who is that person? Are they a juju developer?
Anyone who wishes to run tests locally; aka the maas guys, charm guys, openstack team, docs guys, juju developers. Granted ATM, few if any outside of our team run these tests locally.

> If so, I think they are probably
> 1. writing a test for a feature they have written, or
> 2. running a test to make sure their changes don't break it
>
> In both cases, they most likely have a custom binary that they want to test
> against.
I will grant you that indeed that a developer will have a custom binary in either case. Hence I was trying to phrase it as 'use the juju I have'. I won't pretend to know how many binaries developers tend to keep on there system; thus it depends on the developer if we can pick a sane default for them or not. It's likely they'll specify things.

If a developer has the juju binary they are hacking on set in a single place, it would be nice to just use that to enable that workflow, if indeed it exists. That would be an argument for $GOPATH. For the others listed above, I think /usr/bin/juju would be there expectation.

However, even if we assume developers and ourselves will always specify (and I agree that is likely), I do see value in having things just work when executed beyond enabling others. Removing all required arguments does that, so I would argue for adding the default on that basis alone. If we are left with only one argument, and we can reasonably remove it, let's do so. Lowering the barrier to running tests is the goal; albeit without breaking ourselves.

Revision history for this message
Aaron Bentley (abentley) wrote : Posted in a previous version of this proposal

Additional temp_env_name comment.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal

Addressed uniqueness issue with temp_env_name

Revision history for this message
Martin Packman (gz) wrote : Posted in a previous version of this proposal

Most changes seem fine, but we still have the test isolation issue from creating the logdir I pointed out before:

ubuntu@go:~/juju-ci-tools$ bzr st
modified:
  tests/test_utility.py
  utility.py
pending merge tips: (use -v to see all merge revisions)
  Nicholas Skaggs 2016-06-07 make temp_env_name truly unique

ubuntu@go:~/juju-ci-tools$ make test
...
FAILED (failures=2, errors=39)

ubuntu@go:~/juju-ci-tools$ bzr st
modified:
  tests/test_utility.py
  utility.py
unknown:
  baz/
  d/
  log/
  log_dir/
  test_utility_20160608144450_logs/
pending merge tips: (use -v to see all merge revisions)
  Nicholas Skaggs 2016-06-07 make temp_env_name truly unique

review: Needs Fixing
Revision history for this message
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal

@mgz, I don't get any errors running this. I think you are pointing out I shouldn't be creating a folder during test runs; absolutely correct, so I've patched that out. I also will add some more tests to ensure __clean_dir() works as it should given I've modified it as well. Is there something else you are referring to?

Revision history for this message
Martin Packman (gz) wrote : Posted in a previous version of this proposal

See my first comment for instructions. Tests should not fail based on the contents on /tmp.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal

I fixed everything in test_utility to use a temp dir instead of a hardcoded path. In a seperate MP I'll fix the rest of trunk with the same fix.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal

Ok, I did this in a simpler way -- only throw an exception if the directory doesn't exist, and we fail to create it. We continue on with warnings otherwise. Tests pass, new tests confirm my expectations.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal

This fixes the /tmp/logs problem by not raising the issue. The only remaining annoyance is -h or --help will create a directory and leave the warning (since the default functions still run). Also, running make test will spam blank folders in /tmp. Comments welcome.

Revision history for this message
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal

Some comments inline.

review: Needs Fixing
Revision history for this message
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal

Replies inline.

Revision history for this message
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal

Comment re: readability

review: Needs Fixing
Revision history for this message
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal

Reply to comments

Revision history for this message
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal

LGTM

review: Approve
Revision history for this message
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal

> LGTM
Actually I take that back. I have concerns on calling --help creating a directory.

review: Needs Fixing
Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

Creating a new MP to hash out the --help creating a directory issue. Continuing down the road of abusing things, this works to the extent we don't need to changed the existing tests. Given the constraint of not wanting to change the tests, I'm not sure there's something better. Chris gave the suggestion to just check sys.argv, which is better than my thought of parsing args twice :-)

1469. By Nicholas Skaggs

fix for bug #1601987

1470. By Nicholas Skaggs

fix help test

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

@mgz or @veebers, I'd like to get this landed as there's a bug now, bug #1601987, that needs fixed. Can you review again?

Revision history for this message
Martin Packman (gz) wrote :

Sorry Nicholas.

I think the current approach has gone past the point of sanity, we just have to split the parsing from the post-parsing configuration.

Am well aware I am the one who started us down the current path _clean_dir as a cunning hack, but we just should not be doing anything to the environment as a side effect of arg parsing.

1471. By Nicholas Skaggs

re-merge trunk

1472. By Nicholas Skaggs

remove directory creation as part of arg parsing. By default if nothing is specified, we will us the current directory

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

@mgz, are you "ok" with chucking out generating a directory then and just defaulting to something we know exists? I've updated to use cwd, but we could also use temp. If this also doesn't work, I'll make specifying the directory required again and submit.

1473. By Nicholas Skaggs

move directory creation to bootstrap manager

1474. By Nicholas Skaggs

flake8

1475. By Nicholas Skaggs

patch out directory creation during no_args test

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

This is ready again for review. Nothing is leftover after running make test, no directories are created when you specify arguments, and no directories are made by running help. This uses bootstrap manager instead of hacking arg parser :-)

1476. By Nicholas Skaggs

restore _generate_default_clean_dir

Revision history for this message
Martin Packman (gz) wrote :

Okay, so this is an improvement, lets land it.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'deploy_stack.py'
2--- deploy_stack.py 2016-07-18 17:06:54 +0000
3+++ deploy_stack.py 2016-07-21 13:37:06 +0000
4@@ -7,6 +7,11 @@
5 contextmanager,
6 nested,
7 )
8+from datetime import (
9+ datetime,
10+)
11+
12+import errno
13 import glob
14 import logging
15 import os
16@@ -487,7 +492,30 @@
17 self.known_hosts['0'] = bootstrap_host
18
19 @classmethod
20+ def _generate_default_clean_dir(cls, temp_env_name):
21+ """Creates a new unique directory for logging and returns name"""
22+ logging.info('Environment {}'.format(temp_env_name))
23+ test_name = temp_env_name.split('-')[0]
24+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
25+ log_dir = os.path.join('/tmp', test_name, 'logs', timestamp)
26+
27+ try:
28+ os.makedirs(log_dir)
29+ logging.info('Created logging directory {}'.format(log_dir))
30+ except OSError as e:
31+ if e.errno == errno.EEXIST:
32+ logging.warn('"Directory {} already exists'.format(log_dir))
33+ else:
34+ raise('Failed to create logging directory: {} ' +
35+ log_dir +
36+ '. Please specify empty folder or try again')
37+ return log_dir
38+
39+ @classmethod
40 def from_args(cls, args):
41+ if not args.logs:
42+ args.logs = cls._generate_default_clean_dir(args.temp_env_name)
43+
44 if args.juju_bin == 'FAKE':
45 env = SimpleEnvironment.from_config(args.env)
46 from tests.test_jujupy import fake_juju_client # Circular imports
47
48=== modified file 'tests/test_deploy_stack.py'
49--- tests/test_deploy_stack.py 2016-07-05 14:15:27 +0000
50+++ tests/test_deploy_stack.py 2016-07-21 13:37:06 +0000
51@@ -2,6 +2,9 @@
52 Namespace,
53 )
54 from contextlib import contextmanager
55+from datetime import (
56+ datetime,
57+)
58 import json
59 import logging
60 import os
61@@ -1034,6 +1037,35 @@
62 self.assertEqual(jes_enabled, bs_manager.jes_enabled)
63 self.assertEqual({'0': 'example.org'}, bs_manager.known_hosts)
64
65+ def test_no_args(self):
66+ args = Namespace(
67+ env='foo', juju_bin='bar', debug=True, temp_env_name='baz',
68+ bootstrap_host='example.org', machine=['example.com'],
69+ series='angsty', agent_url='qux', agent_stream='escaped',
70+ region='eu-west-northwest-5', logs=None, keep_env=True)
71+ with patch('deploy_stack.client_from_config') as fc_mock:
72+ with patch('utility.os.makedirs'):
73+ bs_manager = BootstrapManager.from_args(args)
74+ fc_mock.assert_called_once_with('foo', 'bar', debug=True)
75+ self.assertEqual('baz', bs_manager.temp_env_name)
76+ self.assertIs(fc_mock.return_value, bs_manager.client)
77+ self.assertIs(fc_mock.return_value, bs_manager.tear_down_client)
78+ self.assertEqual('example.org', bs_manager.bootstrap_host)
79+ self.assertEqual(['example.com'], bs_manager.machines)
80+ self.assertEqual('angsty', bs_manager.series)
81+ self.assertEqual('qux', bs_manager.agent_url)
82+ self.assertEqual('escaped', bs_manager.agent_stream)
83+ self.assertEqual('eu-west-northwest-5', bs_manager.region)
84+ self.assertIs(True, bs_manager.keep_env)
85+ logs_arg = bs_manager.log_dir.split("/")
86+ logs_ts = logs_arg[4]
87+ self.assertEqual(logs_arg[1:4], ['tmp', 'baz', 'logs'])
88+ self.assertTrue(logs_ts, datetime.strptime(logs_ts, "%Y%m%d%H%M%S"))
89+ jes_enabled = bs_manager.client.is_jes_enabled.return_value
90+ self.assertEqual(jes_enabled, bs_manager.permanent)
91+ self.assertEqual(jes_enabled, bs_manager.jes_enabled)
92+ self.assertEqual({'0': 'example.org'}, bs_manager.known_hosts)
93+
94 def test_jes_not_permanent(self):
95 with self.assertRaisesRegexp(ValueError, 'Cannot set permanent False'
96 ' if jes_enabled is True.'):
97
98=== modified file 'tests/test_utility.py'
99--- tests/test_utility.py 2016-07-07 21:47:48 +0000
100+++ tests/test_utility.py 2016-07-21 13:37:06 +0000
101@@ -365,33 +365,26 @@
102 def test_no_args(self):
103 cmd_line = []
104 parser = add_basic_testing_arguments(ArgumentParser())
105- with patch('utility.os.makedirs'):
106- with patch('utility.os.getenv', return_value=''):
107- args = parser.parse_args(cmd_line)
108+ with patch('utility.os.getenv', return_value=''):
109+ args = parser.parse_args(cmd_line)
110 self.assertEqual(args.env, 'lxd')
111 self.assertEqual(args.juju_bin, '/usr/bin/juju')
112
113- logs_arg = args.logs.split("/")
114- logs_ts = logs_arg[4]
115- self.assertEqual(logs_arg[1:4], ['tmp', 'test_utility', 'logs'])
116- self.assertTrue(logs_ts, datetime.strptime(logs_ts, "%Y%m%d%H%M%S"))
117+ self.assertEqual(args.logs, None)
118
119- temp_env_name_arg = args.temp_env_name.split("_")
120- temp_env_name_ts = temp_env_name_arg[2]
121- self.assertEqual(temp_env_name_arg[0:2], ['test', 'utility'])
122+ temp_env_name_arg = args.temp_env_name.split("-")
123+ temp_env_name_ts = temp_env_name_arg[1]
124+ self.assertEqual(temp_env_name_arg[0:1], ['testutility'])
125 self.assertTrue(temp_env_name_ts,
126 datetime.strptime(temp_env_name_ts, "%Y%m%d%H%M%S"))
127- self.assertEqual(temp_env_name_arg[3:5], ['temp', 'env'])
128-
129- self.assertEqual(logs_ts, temp_env_name_ts)
130+ self.assertEqual(temp_env_name_arg[2:4], ['temp', 'env'])
131
132 def test_default_binary(self):
133 cmd_line = []
134- with patch('utility.os.makedirs'):
135- with patch('utility.os.getenv', return_value='/tmp'):
136- with patch('utility.os.path.isfile', return_value=True):
137- parser = add_basic_testing_arguments(ArgumentParser())
138- args = parser.parse_args(cmd_line)
139+ with patch('utility.os.getenv', return_value='/tmp'):
140+ with patch('utility.os.path.isfile', return_value=True):
141+ parser = add_basic_testing_arguments(ArgumentParser())
142+ args = parser.parse_args(cmd_line)
143 self.assertEqual(args.juju_bin, '/tmp/bin/juju')
144
145 def test_positional_args(self):
146@@ -441,6 +434,18 @@
147 self.assertEqual(warned, [])
148 self.assertEqual("", self.log_stream.getvalue())
149
150+ def test_no_warn_on_help(self):
151+ """Special case help should not generate a warning"""
152+ with warnings.catch_warnings(record=True) as warned:
153+ with patch('utility.sys.exit'):
154+ parser = add_basic_testing_arguments(ArgumentParser())
155+ cmd_line = ['-h']
156+ parser.parse_args(cmd_line)
157+ cmd_line = ['--help']
158+ parser.parse_args(cmd_line)
159+
160+ self.assertEqual(warned, [])
161+
162 def test_warn_on_nonexistent_directory_creation(self):
163 with warnings.catch_warnings(record=True) as warned:
164 log_dir = mkdtemp()
165@@ -454,25 +459,6 @@
166 r"Not a directory " + log_dir)
167 self.assertEqual("", self.log_stream.getvalue())
168
169- def test_warn_on_directory_creation_failure(self):
170- with warnings.catch_warnings(record=True) as warned:
171- with patch('utility.os.makedirs', side_effect=OSError):
172- cmd_line = []
173- try:
174- parser = add_basic_testing_arguments(ArgumentParser())
175- parser.parse_args(cmd_line)
176- except OSError:
177- # we catch our thrown OSError
178- pass
179- else:
180- self.fail('No exception thrown after' +
181- ' directory creation failure')
182- self.assertEqual(len(warned), 1)
183- self.assertRegexpMatches(
184- str(warned[0].message),
185- r"Failed to create logging directory: /tmp/test_utility/" +
186- "logs/.*. Please specify empty folder or try again")
187-
188 def test_debug(self):
189 cmd_line = ['local', '/foo/juju', '/tmp/logs', 'testtest', '--debug']
190 parser = add_basic_testing_arguments(ArgumentParser())
191
192=== modified file 'utility.py'
193--- utility.py 2016-07-07 22:00:51 +0000
194+++ utility.py 2016-07-21 13:37:06 +0000
195@@ -300,27 +300,12 @@
196 return 'unknown_test'
197
198
199-def _generate_default_clean_dir(timestamp):
200- """Creates a new unique directory for logging and returns the name"""
201- log_dir = os.path.join('/tmp', _get_test_name_from_filename(),
202- 'logs', timestamp)
203- try:
204- os.makedirs(log_dir)
205- warnings.warn('"Created logging directory {}'.format(log_dir))
206- except OSError as e:
207- if e.errno == errno.EEXIST:
208- pass
209- else:
210- warnings.warn('Failed to create logging directory: ' +
211- log_dir +
212- ". Please specify empty folder or try again")
213- raise
214- return log_dir
215-
216-
217-def _generate_default_temp_env_name(timestamp):
218+def _generate_default_temp_env_name():
219 """Creates a new unique name for environment and returns the name"""
220- return '{}_{}_temp_env'.format(_get_test_name_from_filename(), timestamp)
221+ # we need to sanitize the name
222+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
223+ test_name = re.sub('[^a-zA-Z]', '', _get_test_name_from_filename())
224+ return '{}-{}-temp-env'.format(test_name, timestamp)
225
226
227 def _generate_default_binary():
228@@ -357,8 +342,6 @@
229 """
230
231 # Optional postional arguments
232- # generate timestamp for use with default args
233- timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
234 parser.add_argument(
235 'env', nargs='?',
236 help='The juju environment to base the temp test environment on.',
237@@ -370,16 +353,14 @@
238 default=_generate_default_binary())
239 parser.add_argument('logs', nargs='?', type=_clean_dir,
240 help='A directory in which to store logs. By default,'
241- ' this will generate a directory named after the test'
242- ' and under /tmp, with a timestamp of the run. '
243- ' /tmp/test_name/logs/timestamp',
244- default=_generate_default_clean_dir(timestamp))
245+ ' this will use the current directory',
246+ default=None)
247 parser.add_argument('temp_env_name', nargs='?',
248 help='A temporary test environment name. By default, '
249 ' this will generate an enviroment name using the '
250 ' timestamp and testname. '
251 ' test_name_timestamp_temp_env',
252- default=_generate_default_temp_env_name(timestamp))
253+ default=_generate_default_temp_env_name())
254
255 # Optional keyword arguments.
256 parser.add_argument('--debug', action='store_true',

Subscribers

People subscribed via source and target branches