Merge lp:~jimbaker/juju-jitsu/unit-test into lp:juju-jitsu

Proposed by Jim Baker
Status: Merged
Approved by: Mark Mims
Approved revision: 91
Merged at revision: 79
Proposed branch: lp:~jimbaker/juju-jitsu/unit-test
Merge into: lp:juju-jitsu
Diff against target: 404 lines (+315/-35)
5 files modified
sub-commands/Makefile.am (+3/-1)
sub-commands/aiki/cli.py (+6/-1)
sub-commands/files/juju (+75/-0)
sub-commands/test (+231/-0)
sub-commands/topodump (+0/-33)
To merge this branch: bzr merge lp:~jimbaker/juju-jitsu/unit-test
Reviewer Review Type Date Requested Status
Mark Mims (community) Approve
Review via email: mp+133807@code.launchpad.net

Description of the change

Adds jitsu test subcommand

Adds new subcommand, jitsu test, that runs unit tests in a charm's tests/ directory.

$ jitsu test --logdir=/tmp/test-results-42 mediawiki # --logdir LOGDIR must be specified

Executing this command then results in extensive logging (we may want to consider trimming this down); the key piece for each unit test is something like the following:

PASS: Verify top page is available for mediawiki/0
PASS: Verify top page is available for mediawiki/1
INFO: Completed test ./100_deploy.test
INFO: Passed test results for ./100_deploy.test in /tmp/test-results-42/100_deploy, skips=0

jitsu test ensures that the specified charm (mediawiki in this example) is deployed from the local repository, specifically the directory from which this test is being run. Otherwise any referenced but not qualified charms are deployed from the charm store.

Unit test ouput is captured in the specified LOGDIR. Note that unit tests can capture additional files by using JITSU_LOGDIR, which is passed as an environment variable to each unit test; it's LOGDIR/UNIT_TEST_NAME:

/tmp/test-results-42/100_deploy$ tree
.
├── 0
│   └── var
│   └── log
│   └── juju
│   ├── machine-agent.log
│   └── provision-agent.log
├── 1
│   └── var
│   ├── lib
│   │   └── juju
│   │   └── units
│   │   └── mediawiki-0
│   │   └── charm.log
│   └── log
│   └── juju
│   └── machine-agent.log
...
├── passed
└── wget.log

Other useful options include:

--no-bootstrap - so as to reuse an existing set of machines; note that jitsu test will not destroy services, so this would need to be automated outside jitsu test.

--isolate=ENVIRONMENT - creates an isolated environment using a unique name (jitsu-test-$(uuid)), including corresponding LOGDIR/.juju/environments.yaml. Note that no cleanup of security groups; this could be automated in testing through euca2ools.

https://codereview.appspot.com/6821104/

To post a comment you must log in.
Revision history for this message
Jim Baker (jimbaker) wrote :

Reviewers: mp+133807_code.launchpad.net,

Message:
Please take a look.

Description:
Adds jitsu test subcommand

Adds new subcommand, jitsu test, that runs unit tests in a charm's
tests/ directory.

$ jitsu test --logdir=/tmp/test-results-42 mediawiki # --logdir LOGDIR
must be specified

Executing this command then results in extensive logging (we may want to
consider trimming this down); the key piece for each unit test is
something like the following:

PASS: Verify top page is available for mediawiki/0
PASS: Verify top page is available for mediawiki/1
INFO: Completed test ./100_deploy.test
INFO: Passed test results for ./100_deploy.test in
/tmp/test-results-42/100_deploy, skips=0

jitsu test ensures that the specified charm (mediawiki in this example)
is deployed from the local repository, specifically the directory from
which this test is being run. Otherwise any referenced but not qualified
charms are deployed from the charm store.

Unit test ouput is captured in the specified LOGDIR. Note that unit
tests can capture additional files by using JITSU_LOGDIR, which is
passed as an environment variable to each unit test; it's
LOGDIR/UNIT_TEST_NAME:

/tmp/test-results-42/100_deploy$ tree
.
├── 0
│   └── var
│   └── log
│   └── juju
│   ├── machine-agent.log
│   └── provision-agent.log
├── 1
│   └── var
│   ├── lib
│   │   └── juju
│   │   └── units
│   │   └── mediawiki-0
│   │   └── charm.log
│   └── log
│   └── juju
│   └── machine-agent.log
...
├── passed
└── wget.log

Other useful options include:

--no-bootstrap - so as to reuse an existing set of machines; note that
jitsu test will not destroy services, so this would need to be automated
outside jitsu test.

--isolate=ENVIRONMENT - creates an isolated environment using a unique
name (jitsu-test-$(uuid)), including corresponding
LOGDIR/.juju/environments.yaml. Note that no cleanup of security groups;
this could be automated in testing through euca2ools.

https://code.launchpad.net/~jimbaker/juju-jitsu/unit-test/+merge/133807

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/6821104/

Affected files:
   A [revision details]
   M sub-commands/Makefile.am
   M sub-commands/aiki/cli.py
   A sub-commands/files/juju
   A sub-commands/test
   D sub-commands/topodump

Revision history for this message
Jim Baker (jimbaker) wrote :

I also pushed up lp:~jimbaker/+junk/mediawiki-test-strawman, which shows how to write a unit test, to take advantage of JITSU_LOGDIR. Otherwise lib/test-helpers.sh will generate a temporary dir, but jitsu test won't know about this and therefore cannot capture.

Next up: propose the unit test as an update to the mediawiki charm!

Revision history for this message
Benji York (benji) wrote :

Thanks for this subcommand, Jim. Nicola and I have been using it to write some tests (see a first cut at lp:~juju-gui/charms/precise/juju-gui/trunk). A few remarks follow.

The charm directory name has to be the same as the charm name itself, which makes testing different branches difficult. Is revisiting that requirement possible?

It seems to us that the exit code from each one of the *.test files should be propagated and reported by the test subcommand (e.g., if any test returns a non-zero then Jitsu should return non-zero).

Bootstrapping from within the command either causes errors or times out: one needs to bootstrap in advance and use the --no-bootstrap option.

When accessing machines for log retrieval, rsync asks for a password for ubuntu@localhost. We can not figure out what that password should be.

We would appreciate any information you can give us about the above. Thanks.

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

On Wed, Nov 14, 2012 at 1:10 PM, Benji York <email address hidden>wrote:

> Thanks for this subcommand, Jim. Nicola and I have been using it to write
> some tests (see a first cut at lp:~juju-gui/charms/precise/juju-gui/trunk).
> A few remarks follow.
>
> The charm directory name has to be the same as the charm name itself,
> which makes testing different branches difficult. Is revisiting that
> requirement possible?
>

This is a core requirement to guard against errant charm authorship. Its
not a jitsu check per se, but a juju core one. Its unlikely to be revisited
imo, but #juju-dev would be the appropriate place to confirm that.

>
> It seems to us that the exit code from each one of the *.test files should
> be propagated and reported by the test subcommand (e.g., if any test
> returns a non-zero then Jitsu should return non-zero).
>
> Bootstrapping from within the command either causes errors or times out:
> one needs to bootstrap in advance and use the --no-bootstrap option.
>
> When accessing machines for log retrieval, rsync asks for a password for
> ubuntu@localhost. We can not figure out what that password should be.
>

try setting up the key of your environment file as an authorized key for a
user ubuntu.

i assume based on the address that this is being tested with the local
provider, which is a bit of an oddball compared to other providers as the
default user isn't 'ubuntu' but whatever the user account is. ideally the
test runner could distinguish this based on provider type and use the local
provider user account. that would still need manual addition of the ssh key
to authorized keys as the local machine is not cloud-init initialized.
alternatively in the case of the local provider the testrunner eschews
machine log collection in favor of just unit log collection which should
still work as per other providers.

 hth,
Kapil

Revision history for this message
Benji York (benji) wrote :

> On Wed, Nov 14, 2012 at 1:10 PM, Benji York <email address hidden>wrote:
>
> > Thanks for this subcommand, Jim. Nicola and I have been using it to write
> > some tests (see a first cut at lp:~juju-gui/charms/precise/juju-gui/trunk).
> > A few remarks follow.
> >
> > The charm directory name has to be the same as the charm name itself,
> > which makes testing different branches difficult. Is revisiting that
> > requirement possible?
> >
>
> This is a core requirement to guard against errant charm authorship. Its
> not a jitsu check per se, but a juju core one. Its unlikely to be revisited
> imo, but #juju-dev would be the appropriate place to confirm that.

I can see how that would be important when handling submitted charms.
Our use case is a little different though, we want to run functional
tests of a charm in development. It seems a bit heavy-handed to force
all charm authors to name their development directory after the charm.

>
> > When accessing machines for log retrieval, rsync asks for a password for
> > ubuntu@localhost. We can not figure out what that password should be.
> >
>
> try setting up the key of your environment file as an authorized key for a
> user ubuntu.

I don't quite follow, but the next time I look at this I'll investigate
further.

> i assume based on the address that this is being tested with the local
> provider, which is a bit of an oddball compared to other providers as the
> default user isn't 'ubuntu' but whatever the user account is.

Right.

> alternatively in the case of the local provider the testrunner eschews
> machine log collection in favor of just unit log collection which should
> still work as per other providers.

It would be nice to have access to all the logs, but any way to avoid
the unanswerable password prompts in the middle of test runs would be an
improvement.

>
> hth,
> Kapil

Many thanks.

Revision history for this message
Jim Baker (jimbaker) wrote :

> On Wed, Nov 14, 2012 at 1:10 PM, Benji York <email address hidden>wrote:
>
> > Thanks for this subcommand, Jim. Nicola and I have been using it to write
> > some tests (see a first cut at lp:~juju-gui/charms/precise/juju-gui/trunk).
> > A few remarks follow.
> >
> > The charm directory name has to be the same as the charm name itself,
> > which makes testing different branches difficult. Is revisiting that
> > requirement possible?
> >
>
> This is a core requirement to guard against errant charm authorship. Its
> not a jitsu check per se, but a juju core one. Its unlikely to be revisited
> imo, but #juju-dev would be the appropriate place to confirm that.

Correct, the test in jitsu test is only an extra check to avoid unnecessary test runs. I don't see this changing anytime soon, and if it does in juju, we can readily change jitsu test.

> > It seems to us that the exit code from each one of the *.test files should
> > be propagated and reported by the test subcommand (e.g., if any test
> > returns a non-zero then Jitsu should return non-zero).

I will add that in a future release, it sounds like a great feature.

> >
> > Bootstrapping from within the command either causes errors or times out:
> > one needs to bootstrap in advance and use the --no-bootstrap option.
> >
> > When accessing machines for log retrieval, rsync asks for a password for
> > ubuntu@localhost. We can not figure out what that password should be.
> >
>
> try setting up the key of your environment file as an authorized key for a
> user ubuntu.
>
> i assume based on the address that this is being tested with the local
> provider, which is a bit of an oddball compared to other providers as the
> default user isn't 'ubuntu' but whatever the user account is. ideally the
> test runner could distinguish this based on provider type and use the local
> provider user account. that would still need manual addition of the ssh key
> to authorized keys as the local machine is not cloud-init initialized.
> alternatively in the case of the local provider the testrunner eschews
> machine log collection in favor of just unit log collection which should
> still work as per other providers.

Based on discussion about this in #juju, I will do some additional work on the lxc provider support in the next release.

Revision history for this message
Mark Mims (mark-mims) wrote :

charm_dir name is a #juju-dev issue.

Please clean up / consolidate log extraction in a future release.

Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'sub-commands/Makefile.am'
2--- sub-commands/Makefile.am 2012-08-09 16:40:53 +0000
3+++ sub-commands/Makefile.am 2012-11-10 21:15:24 +0000
4@@ -10,10 +10,11 @@
5 export \
6 import \
7 open-port \
8- provider-info \
9+ provider-info \
10 run-as-hook \
11 search \
12 setup-environment \
13+ test \
14 upgrade-charm \
15 watch \
16 wrap-juju
17@@ -31,6 +32,7 @@
18 run-as-hook \
19 search \
20 setup-environment \
21+ test \
22 upgrade-charm \
23 watch \
24 wrap-juju
25
26=== modified file 'sub-commands/aiki/cli.py'
27--- sub-commands/aiki/cli.py 2012-08-08 21:22:19 +0000
28+++ sub-commands/aiki/cli.py 2012-11-10 21:15:24 +0000
29@@ -34,7 +34,10 @@
30 kwargs["help"] = kwargs["description"] # Fallback
31 parser = root_parser.add_parser(subcommand, **kwargs)
32 parser.add_argument(
33- "-e", "--environment", default=None, help="Environment to act upon (otherwise uses default)", metavar="ENVIRONMENT")
34+ "-e", "--environment",
35+ default=os.environ.get("JUJU_ENV"),
36+ help="Environment to act upon (otherwise uses default)",
37+ metavar="ENVIRONMENT")
38 parser.add_argument(
39 "--loglevel", default=None, choices=LOG_LEVELS, help="Log level",
40 metavar="CRITICAL|ERROR|WARNING|INFO|DEBUG")
41@@ -146,6 +149,8 @@
42 of the script.
43 """
44 filename = os.path.join(details["home"], cmd)
45+ if not (os.path.isfile(filename) and os.access(filename, os.X_OK)):
46+ return
47 attempt_python_parse = subcommand_home == details["home"]
48 with open(filename) as f:
49 lines = f.readlines()
50
51=== added directory 'sub-commands/files'
52=== added file 'sub-commands/files/juju'
53--- sub-commands/files/juju 1970-01-01 00:00:00 +0000
54+++ sub-commands/files/juju 2012-11-10 21:15:24 +0000
55@@ -0,0 +1,75 @@
56+#!/usr/bin/env python
57+
58+# A juju delegator that rewrites juju deploy|upgrade-charm ... charm [service] to use the desired respository
59+
60+# Only matches the charm if it equals the environment variable
61+# $DELEGATE_CHARM and it is not qualified with cs: or local:
62+# specifiers. If so, it rewrites the arg list to include --repository
63+# $DELEGATE_REPOSITORY.
64+#
65+# Then call the real juju with the arg list.
66+#
67+# Requires that $PATH have the directory of the delegator as the first item in the search path.
68+
69+
70+import os
71+import sys
72+
73+from juju.control import setup_parser, SUBCOMMANDS
74+
75+
76+def is_passthru(options):
77+ # Only worry about juju deploy and upgrade-charm subcommands
78+ if options.parser.prog not in ("juju deploy", "juju upgrade-charm"):
79+ return True
80+
81+ if options.charm != os.environ.get("DELEGATE_CHARM"):
82+ return True
83+
84+ # Charm name cannot be qualified with cs: or local:
85+ if len(options.charm.split(":")) > 1:
86+ return True
87+
88+ # Repository cannot be specified; this could be set by
89+ # JUJU_REPOSITORY, so just iterate through the raw args
90+ for arg in sys.argv[1:]:
91+ if arg.startswith("--repository"):
92+ return True
93+
94+ return False
95+
96+
97+def main():
98+ # Use juju itself to parse the command line options, to keep it simple;
99+ # will need to be changed when moving to golang version
100+ original_args = sys.argv[:]
101+ parser = setup_parser(
102+ subcommands=SUBCOMMANDS,
103+ prog="juju",
104+ description="juju cloud orchestration admin")
105+ options, extra = parser.parse_known_args() # don't need to worry about more complex parse setups like juju ssh
106+
107+ # Create a new env with PATH that looks up the real juju; use the
108+ # convention that we pop the first item in the search path to
109+ # restore PATH
110+ new_env = dict(os.environ) # make a copy
111+ new_env["PATH"] = ":".join(new_env["PATH"].split(":")[1:]) # pop the first dir off the path
112+
113+ # Then exec in the real juju, with args modified as necessary
114+ if is_passthru(options):
115+ print "juju", " ".join(original_args)
116+ os.execvpe("juju", original_args, new_env)
117+ else:
118+ if options.service_name:
119+ args = original_args[0:-2]
120+ else:
121+ args = original_args[0:-1]
122+ args.extend(["--repository", os.environ.get("DELEGATE_REPOSITORY")])
123+ args.append("local:" + options.charm)
124+ if options.service_name:
125+ args.append(options.service_name)
126+ print "juju", " ".join(args)
127+ os.execvpe("juju", args, new_env)
128+
129+if __name__ == "__main__":
130+ main()
131
132=== added file 'sub-commands/test'
133--- sub-commands/test 1970-01-01 00:00:00 +0000
134+++ sub-commands/test 2012-11-10 21:15:24 +0000
135@@ -0,0 +1,231 @@
136+#!/usr/bin/env python
137+
138+# TODO snapshot logs at any time in the test
139+
140+import argparse
141+import glob
142+import logging
143+import os.path
144+import subprocess
145+import sys
146+import textwrap
147+import uuid
148+
149+import yaml
150+
151+from aiki.cli import make_arg_parser, setup_logging
152+from juju.lib.format import YAMLFormat
153+
154+
155+log = logging.getLogger("jitsu.test")
156+
157+
158+class Tester(object):
159+
160+ def __init__(self, options):
161+ self.options = options
162+
163+ # Create a search path that includes files/juju as the first
164+ # item when searching for juju; this will enable delegating it
165+ # so that juju deploy|upgrade-charm use the local repository
166+ # for the tested charm
167+ delegated_juju = [os.path.realpath(os.path.normpath(os.path.join(__file__, "..", "files")))]
168+ delegated_juju.extend(os.environ["PATH"].split(":"))
169+ self.delegated_path = ":".join(delegated_juju)
170+ if self.options.isolate:
171+ self.options.bootstrap = True # implied by --isolate
172+
173+ def install_required_test_packages(self):
174+ if os.path.exists("tests/test.yaml"):
175+ with open("tests/test.yaml") as f:
176+ config = yaml.safe_load(f)
177+ packages = config.get("packages")
178+ if packages:
179+ # Check if the test packages are already installed. If
180+ # not, install them. We use this check to avoid an
181+ # unnecessary requirement to sudo.
182+ if subprocess.call(["dpkg", "-s"].extend(packages)):
183+ subprocess.check_call(["sudo", "apt-get", "--yes", "install"].extend(packages), env=self.env)
184+
185+ def run_tests(self):
186+ if not glob.glob("tests/*.test"):
187+ log.info("Nothing to do: charm %s has no tests defined", self.options.charm)
188+ return
189+
190+ if os.path.split(os.getcwd())[1] != self.options.charm:
191+ log.error("Not in the directory of the charm being tested: %s", self.options.charm)
192+ sys.exit(1)
193+
194+ self.install_required_test_packages()
195+ os.chdir("tests")
196+ unit_tests = sorted(glob.glob("*.test"))
197+ for unit_test in unit_tests:
198+ log.info("Running unit test: %s", unit_test)
199+ runner = TestRunner(self, unit_test)
200+ try:
201+ runner.setup()
202+ runner.run_unit_test()
203+ except Exception:
204+ log.exception("Unit test failure")
205+ finally:
206+ runner.teardown()
207+ log.info("Completed unit test: %s", unit_test)
208+
209+
210+class TestRunner(object):
211+
212+ def __init__(self, tester, unit_test):
213+ self.tester = tester
214+ self.unit_test = unit_test
215+ unit_test_name = os.path.splitext(self.unit_test)[0]
216+ self.test_logdir = os.path.join(self.tester.options.logdir, unit_test_name)
217+ if not os.path.exists(self.test_logdir):
218+ os.makedirs(self.test_logdir) # maybe just do this in scope of an exception, to prevent races (but most unlikely)
219+ self.env = dict(os.environ)
220+ self.env["JITSU_LOGDIR"] = self.test_logdir
221+ self.env["JITSU_UNIT_TEST"] = unit_test_name
222+ self.env["PATH"] = self.tester.delegated_path
223+ self.env["DELEGATE_CHARM"] = self.tester.options.charm
224+ self.env["DELEGATE_REPOSITORY"] = os.path.realpath(os.path.normpath(os.path.join(os.getcwd(), "../../..")))
225+ self.bootstrapped = False
226+
227+ def setup(self):
228+ if self.tester.options.isolate:
229+ self.env["HOME"] = self.tester.options.logdir
230+ self.juju_env = "jitsu-test-" + str(uuid.uuid1())
231+ with open(os.path.expanduser("~/.juju/environments.yaml")) as f:
232+ env_config = yaml.safe_load(f.read())
233+ try:
234+ cloned = dict(env_config["environments"][self.tester.options.isolate])
235+ except KeyError:
236+ log.error("Isolated environment %s is not defined in ~/.juju/environments.yaml", self.tester.options.isolate)
237+ sys.exit(1)
238+
239+ if "authorized-keys" not in cloned:
240+ log.error("Isolated environment %s must define authorized-keys", self.tester.options.isolate)
241+ sys.exit(1)
242+ cloned.update({"control-bucket": self.juju_env})
243+ test_config = {
244+ "default": self.juju_env,
245+ "environments": {
246+ self.juju_env: cloned
247+ }}
248+ juju_env_dir = os.path.join(self.tester.options.logdir, ".juju")
249+ try:
250+ os.makedirs(juju_env_dir)
251+ except OSError:
252+ pass # ignore existing directory
253+ with open(os.path.join(juju_env_dir, "environments.yaml"), "w") as f:
254+ f.write(YAMLFormat().format(test_config))
255+ else:
256+ self.juju_env = self.tester.options.environment
257+ if self.juju_env:
258+ self.env["JUJU_ENV"] = self.juju_env
259+
260+ if self.tester.options.bootstrap:
261+ log.info("Bootstrapping %s", self.juju_env or "default environment")
262+ output = subprocess.check_output(
263+ ["juju", "bootstrap"], env=self.env, stderr=subprocess.STDOUT)
264+ if "juju environment previously bootstrapped" in output:
265+ log.error("Environment previously bootstraped (use --no-bootstrap or different environment)")
266+ sys.exit(1)
267+ self.bootstrapped = True
268+ sys.stderr.write(output) # After capture, simply redirect back to stderr
269+
270+ def teardown(self):
271+ log.info("Tearing down unit test: %s", self.unit_test)
272+ if self.tester.options.bootstrap and self.bootstrapped:
273+ log.info("Destroying environment")
274+ subprocess.check_call("echo y | juju destroy-environment", shell=True, env=self.env)
275+
276+ def run_unit_test(self):
277+ try:
278+ cmd = ["timeout", "--kill-after", "2m", self.tester.options.timeout, os.path.join(".", self.unit_test)]
279+ output = subprocess.check_output(cmd, env=self.env)
280+ log.info("Unit test %s: %s", " ".join(cmd), output)
281+ except subprocess.CalledProcessError, e:
282+ log.warn("Error running unit test %s: %s %s", self.unit_test, e.returncode, e.output)
283+ except Exception, e:
284+ log.error("Error running unit test %s: %s", self.unit_test, e)
285+ try:
286+ self.archive_logs()
287+ except Exception, e:
288+ log.error("Error archiving logs for %s: %s", self.unit_test, e)
289+
290+ def archive_logs(self):
291+ status = yaml.safe_load(subprocess.check_output(["juju", "status"]))
292+ for machine, info in status["machines"].iteritems():
293+ self.gather_logs(machine, info["dns-name"])
294+
295+ def gather_logs(self, machine, host):
296+ host_dir = os.path.abspath(os.path.join(self.test_logdir, str(machine)))
297+ if not os.path.exists(host_dir):
298+ os.makedirs(host_dir)
299+ log.info("Gathering logs for %s:%s in %s", machine, host, host_dir)
300+ self.retrieve_path(host, host_dir, "/var/log/juju")
301+ if machine != 0:
302+ self.retrieve_path(host, host_dir, "/var/lib/juju/units/*/charm.log")
303+
304+ def retrieve_path(self, host, local_path, remote_path):
305+ args = ["rsync", "--archive", "--compress", "--relative", "--verbose",
306+ "-e", "ssh",
307+ "ubuntu@{host}:{remote_path}".format(host=host, remote_path=remote_path),
308+ local_path]
309+ try:
310+ subprocess.check_call(args)
311+ except Exception, e:
312+ log.warn("Error retrieving log %s from %s: %s", remote_path, host, e)
313+
314+
315+def main():
316+ parser = make_parser()
317+ options = parser.parse_args()
318+ setup_logging(options)
319+ tester = Tester(options)
320+ if options.archive_only:
321+ runner = TestRunner(tester, "archive-only")
322+ runner.archive_logs()
323+ else:
324+ tester.run_tests()
325+
326+
327+def make_parser(root_parser=None):
328+ main_parser = make_arg_parser(
329+ root_parser, "test",
330+ formatter_class=argparse.RawDescriptionHelpFormatter,
331+ description="runs charm unit tests",
332+ epilog=textwrap.dedent("""\
333+Charms are resolved against the charm store if not otherwise
334+qualified, *except for the charm being tested*.
335+
336+SSH host keys are problematic for testing with juju at this
337+time. Currently this cannot be mitigated by using per-environment
338+ssh_config.
339+
340+Add the following in ~/.ssh/config
341+
342+Host *.amazonaws.com # set for the specific cloud provider
343+ StrictHostKeyChecking no
344+ UserKnownHostsFile /dev/null
345+
346+Remember: such setup is a potential security hole, and it should not
347+be used for any production systems. However, for testing purposes,
348+especially for unit testing, this configuration should be reasonable.
349+
350+TODO: isolated environments should have support for env cleanup,
351+such as security group removal.
352+"""))
353+
354+ main_parser.add_argument("--archive-only", action="store_true", help="Archive all Juju logs")
355+ main_parser.add_argument("--logdir", required=True, help="Write test logs and snapshots to this directory")
356+ main_parser.add_argument("--timeout", default="15m", help="Timeout per unit test. Examples: 10m or 900s.")
357+ main_parser.add_argument("--no-bootstrap", dest="bootstrap", default=True, action="store_false",
358+ help="Do not bootstrap env before each test, then destroy")
359+ main_parser.add_argument("--isolate", metavar="ENVIRONMENT",
360+ help="Runs in an isolated, unique environment cloned from ENVIRONMENT in ~/.juju/environments.yaml")
361+ main_parser.add_argument("charm", metavar="CHARM", help="Test for this charm")
362+ return main_parser
363+
364+
365+if __name__ == '__main__':
366+ main()
367
368=== removed file 'sub-commands/topodump'
369--- sub-commands/topodump 2012-06-21 23:17:57 +0000
370+++ sub-commands/topodump 1970-01-01 00:00:00 +0000
371@@ -1,33 +0,0 @@
372-#!/usr/bin/env python
373-#
374-# topodump - dumps the zookeeper topology
375-
376-from twisted.internet.defer import inlineCallbacks
377-from aiki.cli import make_arg_parser, setup_logging, run_command
378-from juju.state.base import StateBase
379-
380-
381-def main():
382- parser = make_arg_parser()
383- options = parser.parse_args()
384- setup_logging(options)
385- print "options", options
386- run_command(topodump, options)
387-
388-
389-@inlineCallbacks
390-def topodump(result, client, options):
391- Dumper = TopoDump(client)
392- yield Dumper.dump()
393-
394-
395-class TopoDump(StateBase):
396-
397- @inlineCallbacks
398- def dump(self):
399- topology = yield self._read_topology()
400- print topology.dump()
401-
402-
403-if __name__ == '__main__':
404- main()

Subscribers

People subscribed via source and target branches

to all changes: