Merge lp:~nskaggs/juju-ci-tools/add-snap-to-default-juju-args into lp:juju-ci-tools

Proposed by Nicholas Skaggs
Status: Merged
Merged at revision: 1918
Proposed branch: lp:~nskaggs/juju-ci-tools/add-snap-to-default-juju-args
Merge into: lp:juju-ci-tools
Diff against target: 292 lines (+49/-72)
7 files modified
assess_public_clouds.py (+4/-7)
deploy_stack.py (+4/-27)
tests/test_assess_bootstrap.py (+6/-3)
tests/test_assess_public_clouds.py (+7/-8)
tests/test_deploy_stack.py (+2/-2)
tests/test_utility.py (+4/-12)
utility.py (+22/-13)
To merge this branch: bzr merge lp:~nskaggs/juju-ci-tools/add-snap-to-default-juju-args
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Nicholas Skaggs (community) Needs Information
Review via email: mp+316779@code.launchpad.net

Description of the change

Tweak things to allow running with no arguments if you have the snap version of juju installed. Extra tests were added to clarify the order; GOPATH/bin/juju > /usr/bin/juju > /snap/bin/juju.

To post a comment you must log in.
1876. By Nicholas Skaggs

simplify

1877. By Nicholas Skaggs

Verbose mode enabled for function comment

Revision history for this message
Aaron Bentley (abentley) wrote :

I don't think this is right. If there is a default juju, it should be the juju returned by "which juju", i.e. the one in your PATH. If you have the snap installed, the snap juju should be in your PATH.

client_from_config already gives you this value if juju_path is None.

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

Funny you bring up 'which juju', as indeed I wanted to modify this to use the same logic. The hardcoded paths feel silly when it's really just $PATH/$GOPATH + juju.

I don't remember why I stepped back from that approach, other than it must not have worked. It was the original implementation. hmmm.

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

I attempted to reuse get_full_path for this, it can be nice, make it a utility function. However, it caused spiraling headaches all across the testsuite; into every single test. TestParseArgs everywhere needed a new mock to prevent the popen call. Given that, I think I'd prefer this.

Revision history for this message
Aaron Bentley (abentley) wrote :

You don't need to do more, you need to do less. This change produces only 5 test failures.
=== modified file 'utility.py'
--- utility.py 2017-01-19 04:47:42 +0000
+++ utility.py 2017-02-13 20:19:51 +0000
@@ -338,7 +338,7 @@
                         help='Full path to the Juju binary. By default, this'
                         ' will use $GOPATH/bin/juju or /usr/bin/juju in that'
                         ' order.',
- default=_generate_default_binary())
+ default=None)

 def add_basic_testing_arguments(parser, using_jes=False, deadline=True,

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

I consider the sane defaults useful and essential for letting other folks easily run tests. I think this represents the least invasive option for adding support for snapped juju, but I'm open to other ideas not yet explored. I don't think removing the ability to not require arguments is the right direction.

review: Needs Information
Revision history for this message
Aaron Bentley (abentley) wrote :

Nothing in what I said would remove sane defaults. By setting default to None, our tests will default to whatever juju is in the $PATH, be that a snap or whatever is installed.

1878. By Nicholas Skaggs

swap get_full_path to get_juju_path in utility

1879. By Nicholas Skaggs

revert 1878

1880. By Nicholas Skaggs

wip

1881. By Nicholas Skaggs

wip

1882. By Nicholas Skaggs

fixed logging, fixed juju binary path

1883. By Nicholas Skaggs

lint

1884. By Nicholas Skaggs

merge trunk

Revision history for this message
Aaron Bentley (abentley) wrote :

Thanks.

I'm a little unclear why you moved _generate_default_clean_dir, but I'm fine with it.

I do think that the leading underscore is incorrect. This is a public function meant to be invoked outside utility.py, not a private function. But that's a quibble.

review: Approve
1885. By Nicholas Skaggs

make _generate_default_clean_dir public

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'assess_public_clouds.py'
--- assess_public_clouds.py 2017-02-22 16:08:27 +0000
+++ assess_public_clouds.py 2017-03-01 20:36:24 +0000
@@ -22,8 +22,9 @@
22 )22 )
23from utility import (23from utility import (
24 _clean_dir,24 _clean_dir,
25 generate_default_clean_dir,
26 add_arg_juju_bin,
25 configure_logging,27 configure_logging,
26 _generate_default_binary,
27 LoggedException,28 LoggedException,
28 _to_deadline,29 _to_deadline,
29 )30 )
@@ -101,10 +102,7 @@
101 """Parse all arguments."""102 """Parse all arguments."""
102 parser = ArgumentParser(103 parser = ArgumentParser(
103 description='Assess basic quality of public clouds.')104 description='Assess basic quality of public clouds.')
104 parser.add_argument('juju_bin', nargs='?',105 add_arg_juju_bin(parser)
105 help='Full path to the Juju binary. By default, this'
106 ' will use $GOPATH/bin/juju or /usr/bin/juju in that'
107 ' order.', default=_generate_default_binary())
108 parser.add_argument('logs', nargs='?', type=_clean_dir,106 parser.add_argument('logs', nargs='?', type=_clean_dir,
109 help='A directory in which to store logs. By default,'107 help='A directory in which to store logs. By default,'
110 ' this will use the current directory', default=None)108 ' this will use the current directory', default=None)
@@ -126,8 +124,7 @@
126124
127def default_log_dir(settings):125def default_log_dir(settings):
128 if settings.logs is None:126 if settings.logs is None:
129 settings.logs = BootstrapManager._generate_default_clean_dir(127 settings.logs = generate_default_clean_dir('assess_public_clouds')
130 'assess_public_clouds')
131128
132129
133def main():130def main():
134131
=== modified file 'deploy_stack.py'
--- deploy_stack.py 2017-02-28 18:47:38 +0000
+++ deploy_stack.py 2017-03-01 20:36:24 +0000
@@ -9,12 +9,6 @@
9except ImportError:9except ImportError:
10 from contextlib import ExitStack as nested10 from contextlib import ExitStack as nested
1111
12
13from datetime import (
14 datetime,
15)
16
17import errno
18import glob12import glob
19import logging13import logging
20import os14import os
@@ -63,6 +57,7 @@
63 make_substrate_manager,57 make_substrate_manager,
64)58)
65from utility import (59from utility import (
60 generate_default_clean_dir,
66 add_basic_testing_arguments,61 add_basic_testing_arguments,
67 configure_logging,62 configure_logging,
68 ensure_deleted,63 ensure_deleted,
@@ -473,6 +468,8 @@
473 args = deploy_job_parse_args()468 args = deploy_job_parse_args()
474 configure_logging(args.verbose)469 configure_logging(args.verbose)
475 series = args.series470 series = args.series
471 if not args.logs:
472 args.logs = generate_default_clean_dir(args.temp_env_name)
476 if series is None:473 if series is None:
477 series = 'precise'474 series = 'precise'
478 charm_series = series475 charm_series = series
@@ -706,29 +703,9 @@
706 return self.controller_strategy.tear_down_client703 return self.controller_strategy.tear_down_client
707704
708 @classmethod705 @classmethod
709 def _generate_default_clean_dir(cls, temp_env_name):
710 """Creates a new unique directory for logging and returns name"""
711 logging.info('Environment {}'.format(temp_env_name))
712 test_name = temp_env_name.split('-')[0]
713 timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
714 log_dir = os.path.join('/tmp', test_name, 'logs', timestamp)
715
716 try:
717 os.makedirs(log_dir)
718 logging.info('Created logging directory {}'.format(log_dir))
719 except OSError as e:
720 if e.errno == errno.EEXIST:
721 logging.warn('"Directory {} already exists'.format(log_dir))
722 else:
723 raise('Failed to create logging directory: {} ' +
724 log_dir +
725 '. Please specify empty folder or try again')
726 return log_dir
727
728 @classmethod
729 def from_args(cls, args):706 def from_args(cls, args):
730 if not args.logs:707 if not args.logs:
731 args.logs = cls._generate_default_clean_dir(args.temp_env_name)708 args.logs = generate_default_clean_dir(args.temp_env_name)
732709
733 # GZ 2016-08-11: Move this logic into client_from_config maybe?710 # GZ 2016-08-11: Move this logic into client_from_config maybe?
734 if args.juju_bin == 'FAKE':711 if args.juju_bin == 'FAKE':
735712
=== modified file 'tests/test_assess_bootstrap.py'
--- tests/test_assess_bootstrap.py 2017-02-28 12:55:09 +0000
+++ tests/test_assess_bootstrap.py 2017-03-01 20:36:24 +0000
@@ -219,7 +219,8 @@
219 with assess_bootstrap_cxt():219 with assess_bootstrap_cxt():
220 with self.sub_assess_mocks() as (base_mock, metadata_mock,220 with self.sub_assess_mocks() as (base_mock, metadata_mock,
221 to_mock):221 to_mock):
222 assess_bootstrap(args)222 with patch('jujupy.ModelClient.get_full_path'):
223 assess_bootstrap(args)
223 self.assertEqual(1, base_mock.call_count)224 self.assertEqual(1, base_mock.call_count)
224 self.assertEqual(0, metadata_mock.call_count)225 self.assertEqual(0, metadata_mock.call_count)
225 self.assertEqual(0, to_mock.call_count)226 self.assertEqual(0, to_mock.call_count)
@@ -229,7 +230,8 @@
229 with assess_bootstrap_cxt():230 with assess_bootstrap_cxt():
230 with self.sub_assess_mocks() as (base_mock, metadata_mock,231 with self.sub_assess_mocks() as (base_mock, metadata_mock,
231 to_mock):232 to_mock):
232 assess_bootstrap(args)233 with patch('jujupy.ModelClient.get_full_path'):
234 assess_bootstrap(args)
233 self.assertEqual(0, base_mock.call_count)235 self.assertEqual(0, base_mock.call_count)
234 self.assertEqual(1, metadata_mock.call_count)236 self.assertEqual(1, metadata_mock.call_count)
235 self.assertEqual(0, to_mock.call_count)237 self.assertEqual(0, to_mock.call_count)
@@ -239,7 +241,8 @@
239 with assess_bootstrap_cxt():241 with assess_bootstrap_cxt():
240 with self.sub_assess_mocks() as (base_mock, metadata_mock,242 with self.sub_assess_mocks() as (base_mock, metadata_mock,
241 to_mock):243 to_mock):
242 assess_bootstrap(args)244 with patch('jujupy.ModelClient.get_full_path'):
245 assess_bootstrap(args)
243 self.assertEqual(0, base_mock.call_count)246 self.assertEqual(0, base_mock.call_count)
244 self.assertEqual(0, metadata_mock.call_count)247 self.assertEqual(0, metadata_mock.call_count)
245 self.assertEqual(1, to_mock.call_count)248 self.assertEqual(1, to_mock.call_count)
246249
=== modified file 'tests/test_assess_public_clouds.py'
--- tests/test_assess_public_clouds.py 2017-02-10 20:39:24 +0000
+++ tests/test_assess_public_clouds.py 2017-03-01 20:36:24 +0000
@@ -46,9 +46,10 @@
46class TestParseArgs(TestCase):46class TestParseArgs(TestCase):
4747
48 def test_parse_args(self):48 def test_parse_args(self):
49 args = parse_args([])49 with patch('utility.os.getenv', return_value=False):
50 args = parse_args([])
50 self.assertEqual(Namespace(51 self.assertEqual(Namespace(
51 deadline=None, debug=False, juju_bin='/usr/bin/juju', logs=None,52 deadline=None, debug=False, juju_bin=None, logs=None,
52 start=0, cloud_regions=None,53 start=0, cloud_regions=None,
53 ), args)54 ), args)
5455
@@ -117,18 +118,16 @@
117118
118 def test_default_log_dir(self):119 def test_default_log_dir(self):
119 settings = Namespace(logs=None)120 settings = Namespace(logs=None)
120 with patch(121 with patch('assess_public_clouds.generate_default_clean_dir',
121 'deploy_stack.BootstrapManager._generate_default_clean_dir',122 return_value='/tmp12345') as clean_dir_mock:
122 return_value='/tmp12345') as clean_dir_mock:
123 default_log_dir(settings)123 default_log_dir(settings)
124 self.assertEqual('/tmp12345', settings.logs)124 self.assertEqual('/tmp12345', settings.logs)
125 clean_dir_mock.assert_called_once_with(_LOCAL)125 clean_dir_mock.assert_called_once_with(_LOCAL)
126126
127 def test_default_log_dir_provided(self):127 def test_default_log_dir_provided(self):
128 settings = Namespace(logs='/tmpABCDE')128 settings = Namespace(logs='/tmpABCDE')
129 with patch(129 with patch('assess_public_clouds.generate_default_clean_dir',
130 'deploy_stack.BootstrapManager._generate_default_clean_dir',130 autospec=True) as clean_dir_mock:
131 autospec=True) as clean_dir_mock:
132 default_log_dir(settings)131 default_log_dir(settings)
133 self.assertEqual('/tmpABCDE', settings.logs)132 self.assertEqual('/tmpABCDE', settings.logs)
134 self.assertFalse(clean_dir_mock.called)133 self.assertFalse(clean_dir_mock.called)
135134
=== modified file 'tests/test_deploy_stack.py'
--- tests/test_deploy_stack.py 2017-02-28 16:02:17 +0000
+++ tests/test_deploy_stack.py 2017-03-01 20:36:24 +0000
@@ -1149,7 +1149,7 @@
11491149
1150 def test_deploy_job_changes_series_with_win(self):1150 def test_deploy_job_changes_series_with_win(self):
1151 args = Namespace(1151 args = Namespace(
1152 series='windows', temp_env_name=None, env=None, upgrade=None,1152 series='windows', temp_env_name='windows', env=None, upgrade=None,
1153 charm_prefix=None, bootstrap_host=None, machine=None, logs=None,1153 charm_prefix=None, bootstrap_host=None, machine=None, logs=None,
1154 debug=None, juju_bin=None, agent_url=None, agent_stream=None,1154 debug=None, juju_bin=None, agent_url=None, agent_stream=None,
1155 keep_env=None, upload_tools=None, with_chaos=None, jes=None,1155 keep_env=None, upload_tools=None, with_chaos=None, jes=None,
@@ -1162,7 +1162,7 @@
11621162
1163 def test_deploy_job_changes_series_with_centos(self):1163 def test_deploy_job_changes_series_with_centos(self):
1164 args = Namespace(1164 args = Namespace(
1165 series='centos', temp_env_name=None, env=None, upgrade=None,1165 series='centos', temp_env_name='centos', env=None, upgrade=None,
1166 charm_prefix=None, bootstrap_host=None, machine=None, logs=None,1166 charm_prefix=None, bootstrap_host=None, machine=None, logs=None,
1167 debug=None, juju_bin=None, agent_url=None, agent_stream=None,1167 debug=None, juju_bin=None, agent_url=None, agent_stream=None,
1168 keep_env=None, upload_tools=None, with_chaos=None, jes=None,1168 keep_env=None, upload_tools=None, with_chaos=None, jes=None,
11691169
=== modified file 'tests/test_utility.py'
--- tests/test_utility.py 2017-02-25 06:09:40 +0000
+++ tests/test_utility.py 2017-03-01 20:36:24 +0000
@@ -276,11 +276,11 @@
276276
277 def test_no_args(self):277 def test_no_args(self):
278 cmd_line = []278 cmd_line = []
279 parser = add_basic_testing_arguments(ArgumentParser(), deadline=True)279 parser = add_basic_testing_arguments(ArgumentParser(),
280 with patch('utility.os.getenv', return_value=''):280 deadline=True)
281 args = parser.parse_args(cmd_line)281 args = parser.parse_args(cmd_line)
282 self.assertEqual(args.env, 'lxd')282 self.assertEqual(args.env, 'lxd')
283 self.assertEqual(args.juju_bin, '/usr/bin/juju')283 self.assertEqual(args.juju_bin, None)
284284
285 self.assertEqual(args.logs, None)285 self.assertEqual(args.logs, None)
286286
@@ -292,14 +292,6 @@
292 self.assertEqual(temp_env_name_arg[2:4], ['temp', 'env'])292 self.assertEqual(temp_env_name_arg[2:4], ['temp', 'env'])
293 self.assertIs(None, args.deadline)293 self.assertIs(None, args.deadline)
294294
295 def test_default_binary(self):
296 cmd_line = []
297 with patch('utility.os.getenv', return_value='/tmp'):
298 with patch('utility.os.path.isfile', return_value=True):
299 parser = add_basic_testing_arguments(ArgumentParser())
300 args = parser.parse_args(cmd_line)
301 self.assertEqual(args.juju_bin, '/tmp/bin/juju')
302
303 def test_positional_args(self):295 def test_positional_args(self):
304 cmd_line = ['local', '/foo/juju', '/tmp/logs', 'testtest']296 cmd_line = ['local', '/foo/juju', '/tmp/logs', 'testtest']
305 parser = add_basic_testing_arguments(ArgumentParser(), deadline=True)297 parser = add_basic_testing_arguments(ArgumentParser(), deadline=True)
306298
=== modified file 'utility.py'
--- utility.py 2017-02-25 06:09:40 +0000
+++ utility.py 2017-03-01 20:36:24 +0000
@@ -208,6 +208,26 @@
208 return 'unknown_test'208 return 'unknown_test'
209209
210210
211def generate_default_clean_dir(temp_env_name):
212 """Creates a new unique directory for logging and returns name"""
213 logging.debug('Environment {}'.format(temp_env_name))
214 test_name = temp_env_name.split('-')[0]
215 timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
216 log_dir = os.path.join('/tmp', test_name, 'logs', timestamp)
217
218 try:
219 os.makedirs(log_dir)
220 logging.info('Created logging directory {}'.format(log_dir))
221 except OSError as e:
222 if e.errno == errno.EEXIST:
223 logging.warn('"Directory {} already exists'.format(log_dir))
224 else:
225 raise('Failed to create logging directory: {} ' +
226 log_dir +
227 '. Please specify empty folder or try again')
228 return log_dir
229
230
211def _generate_default_temp_env_name():231def _generate_default_temp_env_name():
212 """Creates a new unique name for environment and returns the name"""232 """Creates a new unique name for environment and returns the name"""
213 # we need to sanitize the name233 # we need to sanitize the name
@@ -216,16 +236,6 @@
216 return '{}-{}-temp-env'.format(test_name, timestamp)236 return '{}-{}-temp-env'.format(test_name, timestamp)
217237
218238
219def _generate_default_binary():
220 """Returns GOPATH juju binary if it exists, otherwise /usr/bin/juju"""
221 if os.getenv('GOPATH'):
222 go_bin = os.getenv('GOPATH') + '/bin/juju'
223 if os.path.isfile(go_bin):
224 return go_bin
225
226 return '/usr/bin/juju'
227
228
229def _to_deadline(timeout):239def _to_deadline(timeout):
230 return datetime.utcnow() + timedelta(seconds=int(timeout))240 return datetime.utcnow() + timedelta(seconds=int(timeout))
231241
@@ -233,9 +243,8 @@
233def add_arg_juju_bin(parser):243def add_arg_juju_bin(parser):
234 parser.add_argument('juju_bin', nargs='?',244 parser.add_argument('juju_bin', nargs='?',
235 help='Full path to the Juju binary. By default, this'245 help='Full path to the Juju binary. By default, this'
236 ' will use $GOPATH/bin/juju or /usr/bin/juju in that'246 ' will use $PATH/juju',
237 ' order.',247 default=None)
238 default=_generate_default_binary())
239248
240249
241def add_basic_testing_arguments(parser, using_jes=False, deadline=True,250def add_basic_testing_arguments(parser, using_jes=False, deadline=True,

Subscribers

People subscribed via source and target branches