Ubuntu

Merge lp:~gz/ubuntu/raring/juju/0.7 into lp:ubuntu/raring/juju

Proposed by Martin Packman on 2013-04-10
Status: Merged
Merge reported by: James Page
Merged at revision: not available
Proposed branch: lp:~gz/ubuntu/raring/juju/0.7
Merge into: lp:ubuntu/raring/juju
Diff against target: 146313 lines (+742/-72218) 437 files modified
To merge this branch: bzr merge lp:~gz/ubuntu/raring/juju/0.7
Reviewer Review Type Date Requested Status
James Page Needs Fixing on 2013-04-10
Ubuntu branches 2013-04-10 Pending
Review via email: mp+158088@code.launchpad.net

Description of the Change

Update python juju for raring

Includes new upstream release with several added features and bug fixes, use of update-alternatives to allow installing of the go juju on the same mahcine (Mark and I would appreciate any comments on how this is done), and a workaround for an lxc regression.

Unfortunately due to bug 985285 requiring some big hammering to get around, the diff in this merge proposal is likely to be useless. Running a straight diff on this branch compared to the existing state should be more enlightening.

To post a comment you must log in.
lp:~gz/ubuntu/raring/juju/0.7 updated on 2013-04-10
31. By Martin Packman on 2013-04-10

Add patch working around local provider issue with Python 3 lxc scripts

James Page (james-page) wrote :

Ignoring the MP as unreadable: had a look at the branch debdiff with current:

1) postinst

Needs to set -e; also you should not set the alternatives manually:

    update-alternatives --set juju $base_dir/juju-$VER/bin/juju

That's something for the end-user to decide; we just decide which is default based on relative priories of juju/go juju binaries.

3) postrm

    rm -Rf /usr/lib/python2.7/dist-packages/juju

That should not be required.

3) rules

Not really the correct way to change the version in the maintainer scripts:

override_dh_builddeb:
    sed -i -e 's/__NEW_VERSION__/$(VER)/' debian/juju/DEBIAN/*
    dh_builddeb

This would be better done by renaming the maintainer scripts <script>.in and having some rules that generate the actual maintainer scripts before builddeb:

debian/juju.postinst: debian/juju.postinst.in
    sed -e "s/__NEW_VERSION__/$(VER)/g' $< > debian/juju.postinst

debian/juju.prerm: debian/juju.prerm.in
    sed -e "s/__NEW_VERSION__/$(VER)/g' $< > debian/juju.prerm

and then override dh_installdeb to get them to generate prior to debhelper installing them:

override_dh_installdeb: debian/juju.postinst debian/juju.prerm
    dh_installdeb

Remember to add cleanup to remove the generated files as well.

4) manpages

Need to be covered by use of alternatives as well.

Should the resulting binary not be called juju-0.7 or something similar? Otherwise when you upgrade to 7.1, you will have to remove old alternatives.

review: Needs Fixing
lp:~gz/ubuntu/raring/juju/0.7 updated on 2013-04-11
32. By Martin Packman on 2013-04-10

Unbreak agent tests after quilt mishap

33. By Martin Packman on 2013-04-10

Collapse debian changelog to one distro relevent update for raring

34. By Mark Mims on 2013-04-10

Templatify and update postinst and prerm scripts as suggested by jamespage in review

35. By Martin Packman on 2013-04-10

Fix get-orig-source rule

36. By Martin Packman on 2013-04-11

Merge in alternatives support for manpages and bash completion

37. By Martin Packman on 2013-04-11

Rename binary package to juju-0.7 and add virtual package juju that depends on it

Martin Packman (gz) wrote :

Pushed up changes as suggested, Mark and I think everything is covered now. Also included is the rename of the binary package to juju-0.7.

lp:~gz/ubuntu/raring/juju/0.7 updated on 2013-04-11
38. By Martin Packman on 2013-04-11

Fixup rules file after binary package rename

Iain Lane (laney) wrote :

Hmm, this is really quite late for raring given the scale of the refactoring. Do you consider this essential to be in Raring (and not suitable for, say, a backport from S)?

If so, please file a freeze exception with strong rationale.

Iain Lane (laney) wrote :

My bad, I didn't see https://bugs.launchpad.net/ubuntu/+source/juju/+bug/1167921 (because it's not referred to from the Debian changelog; probably worth adding it there before uploading)

lp:~gz/ubuntu/raring/juju/0.7 updated on 2013-04-22
39. By Martin Packman on 2013-04-22

Add reference to ffe bug in changelog as requested by laney in review

40. By Martin Packman on 2013-04-22

Move update-alternatives scripts to juju-0.7 as requested by stgraber

Preview Diff

1=== added file '.bzrignore'
2--- .bzrignore 1970-01-01 00:00:00 +0000
3+++ .bzrignore 2013-04-22 11:00:38 +0000
4@@ -0,0 +1,12 @@
5+/docs/build
6+/docs/source/generated
7+/_trial_temp
8+/_trial_temp-*
9+/tags
10+zookeeper.log
11+.emacs.desktop
12+.emacs.desktop.lock
13+.coverage
14+htmlcov
15+/TAGS
16+/.testrepository
17
18=== modified file '.pc/applied-patches'
19--- .pc/applied-patches 2012-11-06 20:44:41 +0000
20+++ .pc/applied-patches 2013-04-22 11:00:38 +0000
21@@ -1,1 +1,2 @@
22 disable-failing-zookeeper-test.patch
23+workaround-lxc-python-env.patch
24
25=== modified file '.pc/disable-failing-zookeeper-test.patch/juju/agents/tests/test_unit.py'
26--- .pc/disable-failing-zookeeper-test.patch/juju/agents/tests/test_unit.py 2012-10-02 13:05:17 +0000
27+++ .pc/disable-failing-zookeeper-test.patch/juju/agents/tests/test_unit.py 2013-04-22 11:00:38 +0000
28@@ -9,7 +9,9 @@
29 from juju.charm import get_charm_from_path
30 from juju.charm.url import CharmURL
31 from juju.errors import JujuError
32+from juju.hooks.executor import HookExecutor
33 from juju.lib import serializer
34+
35 from juju.state.environment import GlobalSettingsStateManager
36 from juju.state.errors import ServiceStateNotFound
37 from juju.state.service import NO_HOOKS, RETRY_HOOKS
38@@ -30,11 +32,15 @@
39
40 @inlineCallbacks
41 def setUp(self):
42+ self.patch(HookExecutor,
43+ "LOCK_PATH",
44+ os.path.join(self.makeDir(), "hook.lock"))
45 yield super(UnitAgentTestBase, self).setUp()
46 settings = GlobalSettingsStateManager(self.client)
47 yield settings.set_provider_type("dummy")
48 self.change_environment(
49 PATH=get_cli_environ_path(),
50+ JUJU_ENV_UUID="snowflake",
51 JUJU_UNIT_NAME="mysql/0")
52
53 @inlineCallbacks
54
55=== added directory '.pc/workaround-lxc-python-env.patch'
56=== added file '.pc/workaround-lxc-python-env.patch/.timestamp'
57=== added directory '.pc/workaround-lxc-python-env.patch/juju'
58=== added directory '.pc/workaround-lxc-python-env.patch/juju/lib'
59=== added directory '.pc/workaround-lxc-python-env.patch/juju/lib/lxc'
60=== added file '.pc/workaround-lxc-python-env.patch/juju/lib/lxc/__init__.py'
61--- .pc/workaround-lxc-python-env.patch/juju/lib/lxc/__init__.py 1970-01-01 00:00:00 +0000
62+++ .pc/workaround-lxc-python-env.patch/juju/lib/lxc/__init__.py 2013-04-22 11:00:38 +0000
63@@ -0,0 +1,308 @@
64+import os
65+import pipes
66+import subprocess
67+import sys
68+import tempfile
69+
70+from twisted.internet.defer import inlineCallbacks, returnValue
71+from twisted.internet.threads import deferToThread
72+
73+from juju.errors import JujuError
74+
75+DATA_PATH = os.path.abspath(
76+ os.path.join(os.path.dirname(__file__), "data"))
77+
78+CONTAINER_OPTIONS_DOC = """
79+The following options are expected.
80+
81+JUJU_CONTAINER_NAME: Applied as the hostname of the machine.
82+
83+JUJU_ORIGIN: Where to obtain the containers version of juju from.
84+ (ppa, distro or branch). When 'branch' JUJU_SOURCE should
85+ be set to the location of a bzr(1) accessible branch.
86+
87+JUJU_PUBLIC_KEY: An SSH public key used by the ubuntu account for
88+ interaction with the container.
89+
90+"""
91+
92+DEVTMPFS_LINE = """devtmpfs dev devtmpfs mode=0755,nosuid 0 0"""
93+
94+# Used to specify the name of the default LXC template used
95+# for container creation
96+DEFAULT_TEMPLATE = "ubuntu-cloud"
97+
98+
99+class LXCError(JujuError):
100+ """Indicates a low level error with an LXC container"""
101+
102+
103+def _cmd(args):
104+ p = subprocess.Popen(
105+ args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
106+ stdout_data, _ = p.communicate()
107+ r = p.returncode
108+ if r != 0:
109+ # read the stdout/err streams and show the user
110+ print >>sys.stderr, stdout_data
111+ raise LXCError(stdout_data)
112+ return (r, stdout_data)
113+
114+
115+# Wrapped lxc cli primitives
116+def _lxc_create(container_name,
117+ template,
118+ release,
119+ cloud_init_file=None,
120+ auth_key=None,
121+ release_stream=None):
122+ # the -- argument indicates the last parameters are passed
123+ # to the template and not handled by lxc-create
124+ args = ["sudo", "lxc-create",
125+ "-n", container_name,
126+ "-t", template,
127+ "--",
128+ "--debug", # Debug erors / set -x
129+ "--hostid", container_name,
130+ "-r", release]
131+ if cloud_init_file:
132+ args.extend(("--userdata", cloud_init_file))
133+ if auth_key:
134+ args.extend(("-S", auth_key))
135+ if release_stream:
136+ args.extend(("-s", release_stream))
137+ return _cmd(args)
138+
139+
140+def _lxc_start(container_name, debug_log=None, console_log=None):
141+ args = ["sudo", "lxc-start", "--daemon", "-n", container_name]
142+ if console_log:
143+ args.extend(["-c", console_log])
144+ if debug_log:
145+ args.extend(["-l", "DEBUG", "-o", debug_log])
146+ return _cmd(args)
147+
148+
149+def _lxc_stop(container_name):
150+ _cmd(["sudo", "lxc-stop", "-n", container_name])
151+
152+
153+def _lxc_destroy(container_name):
154+ return _cmd(["sudo", "lxc-destroy", "-n", container_name])
155+
156+
157+def _lxc_ls():
158+ _, output = _cmd(["lxc-ls"])
159+ output = output.replace("\n", " ")
160+ return set([c for c in output.split(" ") if c])
161+
162+
163+def _lxc_wait(container_name, state="RUNNING"):
164+ """Wait for container to be in a given state RUNNING|STOPPED."""
165+
166+ def wait(container_name):
167+ rc, _ = _cmd(["sudo", "lxc-wait",
168+ "-n", container_name,
169+ "-s", state])
170+ return rc == 0
171+
172+ return deferToThread(wait, container_name)
173+
174+
175+def _lxc_clone(existing_container_name, new_container_name):
176+ return _cmd(["sudo", "lxc-clone", "-o", existing_container_name, "-n",
177+ new_container_name])
178+
179+
180+def _customize_container(customize_script, container_root):
181+ if not os.path.isdir(container_root):
182+ raise LXCError("Expect container root directory: %s" %
183+ container_root)
184+
185+ # write the container scripts into the container
186+ fd, in_path = tempfile.mkstemp(prefix=os.path.basename(customize_script),
187+ dir=os.path.join(container_root, "tmp"))
188+
189+ os.write(fd, open(customize_script, "r").read())
190+ os.close(fd)
191+ os.chmod(in_path, 0755)
192+
193+ args = ["sudo", "chroot", container_root,
194+ os.path.join("/tmp", os.path.basename(in_path))]
195+ return _cmd(args)
196+
197+
198+def validate_path(pathname):
199+ if not os.access(pathname, os.R_OK):
200+ raise LXCError("Invalid or unreadable file: %s" % pathname)
201+
202+
203+@inlineCallbacks
204+def get_containers(prefix):
205+ """Return a dictionary of containers key names to runtime boolean value.
206+
207+ :param prefix: Optionally specify a prefix that the container should
208+ match any returned containers.
209+ """
210+ _, output = yield deferToThread(_cmd, ["lxc-ls"])
211+
212+ containers = {}
213+ for i in filter(None, output.split("\n")):
214+ if i in containers:
215+ containers[i] = True
216+ else:
217+ containers[i] = False
218+
219+ if prefix:
220+ remove = [k for k in containers.keys() if not
221+ k.startswith(prefix)]
222+ map(containers.pop, remove)
223+
224+ returnValue(containers)
225+
226+
227+def ensure_devtmpfs_fstab(container_home):
228+ """ Workaround for bug in older LXC - We need to force mounting devtmpfs
229+ if it is not already in the rootfs, before starting.
230+ """
231+ rootfs = os.path.join(container_home, 'rootfs')
232+ devpts = os.path.join(rootfs, 'dev', 'pts')
233+ if not os.path.exists(devpts):
234+ fstab_path = os.path.join(container_home, 'fstab')
235+ if os.path.exists(fstab_path):
236+ with open(fstab_path) as fstab:
237+ for line in fstab:
238+ if line.startswith('devtmpfs'):
239+ # Line already there, we are done
240+ return
241+ mode = 'a'
242+ else:
243+ mode = 'w'
244+ with open(fstab_path, mode) as fstab:
245+ print >>fstab, DEVTMPFS_LINE
246+
247+
248+class LXCContainer(object):
249+ def __init__(self,
250+ container_name,
251+ series,
252+ cloud_init=None,
253+ debug_log=None,
254+ console_log=None,
255+ release_stream="released"):
256+ """Create an LXCContainer
257+
258+ :param container_name: should be unique within the system
259+
260+ :param series: distro release series (oneiric, precise, etc)
261+
262+ :param cloud_init: full string of cloud-init userdata
263+
264+ See :data CONFIG_OPTIONS_DOC: explain how these values map
265+ into the container in more detail.
266+ """
267+ self.container_name = container_name
268+ self.debug_log = debug_log
269+ self.console_log = console_log
270+ self.cloud_init = cloud_init
271+ self.series = series
272+ self.release_stream = release_stream
273+
274+ @property
275+ def container_home(self):
276+ return "/var/lib/lxc/%s" % self.container_name
277+
278+ @property
279+ def rootfs(self):
280+ return "%s/rootfs/" % self.container_home
281+
282+ def _p(self, path):
283+ if path[0] == "/":
284+ path = path[1:]
285+ return os.path.join(self.rootfs, path)
286+
287+ def is_constructed(self):
288+ """Does the lxc image exist """
289+ return os.path.exists(self.rootfs)
290+
291+ @inlineCallbacks
292+ def is_running(self):
293+ """Is the lxc image running."""
294+ state = yield get_containers(None)
295+ returnValue(state.get(self.container_name))
296+
297+ def execute(self, args):
298+ if not isinstance(args, (list, tuple)):
299+ args = [args, ]
300+
301+ args = ["sudo", "chroot", self.rootfs] + args
302+ return _cmd(args)
303+
304+ def _create_wait(self):
305+ """Create the container synchronously."""
306+ if self.is_constructed():
307+ return
308+
309+ with tempfile.NamedTemporaryFile() as fh:
310+ if self.cloud_init:
311+ fh.write(self.cloud_init.render())
312+ cloud_init_file = fh.name
313+ else:
314+ cloud_init_file = None
315+ fh.flush()
316+ _lxc_create(self.container_name,
317+ template=DEFAULT_TEMPLATE,
318+ cloud_init_file=cloud_init_file,
319+ release=self.series)
320+
321+ ensure_devtmpfs_fstab(self.container_home)
322+
323+ @inlineCallbacks
324+ def create(self):
325+ # open the template file and create a new temp processed
326+ yield deferToThread(self._create_wait)
327+
328+ def _clone_wait(self, container_name):
329+ """Return a cloned LXCContainer with a the new container name.
330+
331+ This method is synchronous and will provision the new image
332+ blocking till done.
333+ """
334+ if not self.is_constructed():
335+ raise LXCError("Attempted to clone container "
336+ "that hasn't been had create() called")
337+
338+ container = LXCContainer(container_name,
339+ series=self.series,
340+ cloud_init=self.cloud_init,
341+ debug_log=self.debug_log,
342+ console_log=self.console_log,
343+ release_stream=self.release_stream)
344+
345+ if not container.is_constructed():
346+ _lxc_clone(self.container_name, container_name)
347+ return container
348+
349+ def clone(self, container_name):
350+ return deferToThread(self._clone_wait, container_name)
351+
352+ @inlineCallbacks
353+ def run(self):
354+ if not self.is_constructed():
355+ raise LXCError("Attempting to run a container that "
356+ "hasn't been created or cloned.")
357+
358+ yield deferToThread(
359+ _lxc_start, self.container_name,
360+ debug_log=self.debug_log, console_log=self.console_log)
361+ yield _lxc_wait(self.container_name, "RUNNING")
362+
363+ @inlineCallbacks
364+ def stop(self):
365+ yield deferToThread(_lxc_stop, self.container_name)
366+ yield _lxc_wait(self.container_name, "STOPPED")
367+
368+ @inlineCallbacks
369+ def destroy(self):
370+ yield self.stop()
371+ yield deferToThread(_lxc_destroy, self.container_name)
372
373=== added file '.testr.conf'
374--- .testr.conf 1970-01-01 00:00:00 +0000
375+++ .testr.conf 2013-04-22 11:00:38 +0000
376@@ -0,0 +1,4 @@
377+[DEFAULT]
378+test_command=./test --reporter=subunit $LISTOPT $IDLIST
379+test_list_option=-n
380+
381
382=== removed file '.testr.conf'
383--- .testr.conf 2012-07-19 11:01:09 +0000
384+++ .testr.conf 1970-01-01 00:00:00 +0000
385@@ -1,4 +0,0 @@
386-[DEFAULT]
387-test_command=./test --reporter=subunit $LISTOPT $IDLIST
388-test_list_option=-n
389-
390
391=== added file 'COPYING'
392--- COPYING 1970-01-01 00:00:00 +0000
393+++ COPYING 2013-04-22 11:00:38 +0000
394@@ -0,0 +1,661 @@
395+ GNU AFFERO GENERAL PUBLIC LICENSE
396+ Version 3, 19 November 2007
397+
398+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
399+ Everyone is permitted to copy and distribute verbatim copies
400+ of this license document, but changing it is not allowed.
401+
402+ Preamble
403+
404+ The GNU Affero General Public License is a free, copyleft license for
405+software and other kinds of works, specifically designed to ensure
406+cooperation with the community in the case of network server software.
407+
408+ The licenses for most software and other practical works are designed
409+to take away your freedom to share and change the works. By contrast,
410+our General Public Licenses are intended to guarantee your freedom to
411+share and change all versions of a program--to make sure it remains free
412+software for all its users.
413+
414+ When we speak of free software, we are referring to freedom, not
415+price. Our General Public Licenses are designed to make sure that you
416+have the freedom to distribute copies of free software (and charge for
417+them if you wish), that you receive source code or can get it if you
418+want it, that you can change the software or use pieces of it in new
419+free programs, and that you know you can do these things.
420+
421+ Developers that use our General Public Licenses protect your rights
422+with two steps: (1) assert copyright on the software, and (2) offer
423+you this License which gives you legal permission to copy, distribute
424+and/or modify the software.
425+
426+ A secondary benefit of defending all users' freedom is that
427+improvements made in alternate versions of the program, if they
428+receive widespread use, become available for other developers to
429+incorporate. Many developers of free software are heartened and
430+encouraged by the resulting cooperation. However, in the case of
431+software used on network servers, this result may fail to come about.
432+The GNU General Public License permits making a modified version and
433+letting the public access it on a server without ever releasing its
434+source code to the public.
435+
436+ The GNU Affero General Public License is designed specifically to
437+ensure that, in such cases, the modified source code becomes available
438+to the community. It requires the operator of a network server to
439+provide the source code of the modified version running there to the
440+users of that server. Therefore, public use of a modified version, on
441+a publicly accessible server, gives the public access to the source
442+code of the modified version.
443+
444+ An older license, called the Affero General Public License and
445+published by Affero, was designed to accomplish similar goals. This is
446+a different license, not a version of the Affero GPL, but Affero has
447+released a new version of the Affero GPL which permits relicensing under
448+this license.
449+
450+ The precise terms and conditions for copying, distribution and
451+modification follow.
452+
453+ TERMS AND CONDITIONS
454+
455+ 0. Definitions.
456+
457+ "This License" refers to version 3 of the GNU Affero General Public License.
458+
459+ "Copyright" also means copyright-like laws that apply to other kinds of
460+works, such as semiconductor masks.
461+
462+ "The Program" refers to any copyrightable work licensed under this
463+License. Each licensee is addressed as "you". "Licensees" and
464+"recipients" may be individuals or organizations.
465+
466+ To "modify" a work means to copy from or adapt all or part of the work
467+in a fashion requiring copyright permission, other than the making of an
468+exact copy. The resulting work is called a "modified version" of the
469+earlier work or a work "based on" the earlier work.
470+
471+ A "covered work" means either the unmodified Program or a work based
472+on the Program.
473+
474+ To "propagate" a work means to do anything with it that, without
475+permission, would make you directly or secondarily liable for
476+infringement under applicable copyright law, except executing it on a
477+computer or modifying a private copy. Propagation includes copying,
478+distribution (with or without modification), making available to the
479+public, and in some countries other activities as well.
480+
481+ To "convey" a work means any kind of propagation that enables other
482+parties to make or receive copies. Mere interaction with a user through
483+a computer network, with no transfer of a copy, is not conveying.
484+
485+ An interactive user interface displays "Appropriate Legal Notices"
486+to the extent that it includes a convenient and prominently visible
487+feature that (1) displays an appropriate copyright notice, and (2)
488+tells the user that there is no warranty for the work (except to the
489+extent that warranties are provided), that licensees may convey the
490+work under this License, and how to view a copy of this License. If
491+the interface presents a list of user commands or options, such as a
492+menu, a prominent item in the list meets this criterion.
493+
494+ 1. Source Code.
495+
496+ The "source code" for a work means the preferred form of the work
497+for making modifications to it. "Object code" means any non-source
498+form of a work.
499+
500+ A "Standard Interface" means an interface that either is an official
501+standard defined by a recognized standards body, or, in the case of
502+interfaces specified for a particular programming language, one that
503+is widely used among developers working in that language.
504+
505+ The "System Libraries" of an executable work include anything, other
506+than the work as a whole, that (a) is included in the normal form of
507+packaging a Major Component, but which is not part of that Major
508+Component, and (b) serves only to enable use of the work with that
509+Major Component, or to implement a Standard Interface for which an
510+implementation is available to the public in source code form. A
511+"Major Component", in this context, means a major essential component
512+(kernel, window system, and so on) of the specific operating system
513+(if any) on which the executable work runs, or a compiler used to
514+produce the work, or an object code interpreter used to run it.
515+
516+ The "Corresponding Source" for a work in object code form means all
517+the source code needed to generate, install, and (for an executable
518+work) run the object code and to modify the work, including scripts to
519+control those activities. However, it does not include the work's
520+System Libraries, or general-purpose tools or generally available free
521+programs which are used unmodified in performing those activities but
522+which are not part of the work. For example, Corresponding Source
523+includes interface definition files associated with source files for
524+the work, and the source code for shared libraries and dynamically
525+linked subprograms that the work is specifically designed to require,
526+such as by intimate data communication or control flow between those
527+subprograms and other parts of the work.
528+
529+ The Corresponding Source need not include anything that users
530+can regenerate automatically from other parts of the Corresponding
531+Source.
532+
533+ The Corresponding Source for a work in source code form is that
534+same work.
535+
536+ 2. Basic Permissions.
537+
538+ All rights granted under this License are granted for the term of
539+copyright on the Program, and are irrevocable provided the stated
540+conditions are met. This License explicitly affirms your unlimited
541+permission to run the unmodified Program. The output from running a
542+covered work is covered by this License only if the output, given its
543+content, constitutes a covered work. This License acknowledges your
544+rights of fair use or other equivalent, as provided by copyright law.
545+
546+ You may make, run and propagate covered works that you do not
547+convey, without conditions so long as your license otherwise remains
548+in force. You may convey covered works to others for the sole purpose
549+of having them make modifications exclusively for you, or provide you
550+with facilities for running those works, provided that you comply with
551+the terms of this License in conveying all material for which you do
552+not control copyright. Those thus making or running the covered works
553+for you must do so exclusively on your behalf, under your direction
554+and control, on terms that prohibit them from making any copies of
555+your copyrighted material outside their relationship with you.
556+
557+ Conveying under any other circumstances is permitted solely under
558+the conditions stated below. Sublicensing is not allowed; section 10
559+makes it unnecessary.
560+
561+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
562+
563+ No covered work shall be deemed part of an effective technological
564+measure under any applicable law fulfilling obligations under article
565+11 of the WIPO copyright treaty adopted on 20 December 1996, or
566+similar laws prohibiting or restricting circumvention of such
567+measures.
568+
569+ When you convey a covered work, you waive any legal power to forbid
570+circumvention of technological measures to the extent such circumvention
571+is effected by exercising rights under this License with respect to
572+the covered work, and you disclaim any intention to limit operation or
573+modification of the work as a means of enforcing, against the work's
574+users, your or third parties' legal rights to forbid circumvention of
575+technological measures.
576+
577+ 4. Conveying Verbatim Copies.
578+
579+ You may convey verbatim copies of the Program's source code as you
580+receive it, in any medium, provided that you conspicuously and
581+appropriately publish on each copy an appropriate copyright notice;
582+keep intact all notices stating that this License and any
583+non-permissive terms added in accord with section 7 apply to the code;
584+keep intact all notices of the absence of any warranty; and give all
585+recipients a copy of this License along with the Program.
586+
587+ You may charge any price or no price for each copy that you convey,
588+and you may offer support or warranty protection for a fee.
589+
590+ 5. Conveying Modified Source Versions.
591+
592+ You may convey a work based on the Program, or the modifications to
593+produce it from the Program, in the form of source code under the
594+terms of section 4, provided that you also meet all of these conditions:
595+
596+ a) The work must carry prominent notices stating that you modified
597+ it, and giving a relevant date.
598+
599+ b) The work must carry prominent notices stating that it is
600+ released under this License and any conditions added under section
601+ 7. This requirement modifies the requirement in section 4 to
602+ "keep intact all notices".
603+
604+ c) You must license the entire work, as a whole, under this
605+ License to anyone who comes into possession of a copy. This
606+ License will therefore apply, along with any applicable section 7
607+ additional terms, to the whole of the work, and all its parts,
608+ regardless of how they are packaged. This License gives no
609+ permission to license the work in any other way, but it does not
610+ invalidate such permission if you have separately received it.
611+
612+ d) If the work has interactive user interfaces, each must display
613+ Appropriate Legal Notices; however, if the Program has interactive
614+ interfaces that do not display Appropriate Legal Notices, your
615+ work need not make them do so.
616+
617+ A compilation of a covered work with other separate and independent
618+works, which are not by their nature extensions of the covered work,
619+and which are not combined with it such as to form a larger program,
620+in or on a volume of a storage or distribution medium, is called an
621+"aggregate" if the compilation and its resulting copyright are not
622+used to limit the access or legal rights of the compilation's users
623+beyond what the individual works permit. Inclusion of a covered work
624+in an aggregate does not cause this License to apply to the other
625+parts of the aggregate.
626+
627+ 6. Conveying Non-Source Forms.
628+
629+ You may convey a covered work in object code form under the terms
630+of sections 4 and 5, provided that you also convey the
631+machine-readable Corresponding Source under the terms of this License,
632+in one of these ways:
633+
634+ a) Convey the object code in, or embodied in, a physical product
635+ (including a physical distribution medium), accompanied by the
636+ Corresponding Source fixed on a durable physical medium
637+ customarily used for software interchange.
638+
639+ b) Convey the object code in, or embodied in, a physical product
640+ (including a physical distribution medium), accompanied by a
641+ written offer, valid for at least three years and valid for as
642+ long as you offer spare parts or customer support for that product
643+ model, to give anyone who possesses the object code either (1) a
644+ copy of the Corresponding Source for all the software in the
645+ product that is covered by this License, on a durable physical
646+ medium customarily used for software interchange, for a price no
647+ more than your reasonable cost of physically performing this
648+ conveying of source, or (2) access to copy the
649+ Corresponding Source from a network server at no charge.
650+
651+ c) Convey individual copies of the object code with a copy of the
652+ written offer to provide the Corresponding Source. This
653+ alternative is allowed only occasionally and noncommercially, and
654+ only if you received the object code with such an offer, in accord
655+ with subsection 6b.
656+
657+ d) Convey the object code by offering access from a designated
658+ place (gratis or for a charge), and offer equivalent access to the
659+ Corresponding Source in the same way through the same place at no
660+ further charge. You need not require recipients to copy the
661+ Corresponding Source along with the object code. If the place to
662+ copy the object code is a network server, the Corresponding Source
663+ may be on a different server (operated by you or a third party)
664+ that supports equivalent copying facilities, provided you maintain
665+ clear directions next to the object code saying where to find the
666+ Corresponding Source. Regardless of what server hosts the
667+ Corresponding Source, you remain obligated to ensure that it is
668+ available for as long as needed to satisfy these requirements.
669+
670+ e) Convey the object code using peer-to-peer transmission, provided
671+ you inform other peers where the object code and Corresponding
672+ Source of the work are being offered to the general public at no
673+ charge under subsection 6d.
674+
675+ A separable portion of the object code, whose source code is excluded
676+from the Corresponding Source as a System Library, need not be
677+included in conveying the object code work.
678+
679+ A "User Product" is either (1) a "consumer product", which means any
680+tangible personal property which is normally used for personal, family,
681+or household purposes, or (2) anything designed or sold for incorporation
682+into a dwelling. In determining whether a product is a consumer product,
683+doubtful cases shall be resolved in favor of coverage. For a particular
684+product received by a particular user, "normally used" refers to a
685+typical or common use of that class of product, regardless of the status
686+of the particular user or of the way in which the particular user
687+actually uses, or expects or is expected to use, the product. A product
688+is a consumer product regardless of whether the product has substantial
689+commercial, industrial or non-consumer uses, unless such uses represent
690+the only significant mode of use of the product.
691+
692+ "Installation Information" for a User Product means any methods,
693+procedures, authorization keys, or other information required to install
694+and execute modified versions of a covered work in that User Product from
695+a modified version of its Corresponding Source. The information must
696+suffice to ensure that the continued functioning of the modified object
697+code is in no case prevented or interfered with solely because
698+modification has been made.
699+
700+ If you convey an object code work under this section in, or with, or
701+specifically for use in, a User Product, and the conveying occurs as
702+part of a transaction in which the right of possession and use of the
703+User Product is transferred to the recipient in perpetuity or for a
704+fixed term (regardless of how the transaction is characterized), the
705+Corresponding Source conveyed under this section must be accompanied
706+by the Installation Information. But this requirement does not apply
707+if neither you nor any third party retains the ability to install
708+modified object code on the User Product (for example, the work has
709+been installed in ROM).
710+
711+ The requirement to provide Installation Information does not include a
712+requirement to continue to provide support service, warranty, or updates
713+for a work that has been modified or installed by the recipient, or for
714+the User Product in which it has been modified or installed. Access to a
715+network may be denied when the modification itself materially and
716+adversely affects the operation of the network or violates the rules and
717+protocols for communication across the network.
718+
719+ Corresponding Source conveyed, and Installation Information provided,
720+in accord with this section must be in a format that is publicly
721+documented (and with an implementation available to the public in
722+source code form), and must require no special password or key for
723+unpacking, reading or copying.
724+
725+ 7. Additional Terms.
726+
727+ "Additional permissions" are terms that supplement the terms of this
728+License by making exceptions from one or more of its conditions.
729+Additional permissions that are applicable to the entire Program shall
730+be treated as though they were included in this License, to the extent
731+that they are valid under applicable law. If additional permissions
732+apply only to part of the Program, that part may be used separately
733+under those permissions, but the entire Program remains governed by
734+this License without regard to the additional permissions.
735+
736+ When you convey a copy of a covered work, you may at your option
737+remove any additional permissions from that copy, or from any part of
738+it. (Additional permissions may be written to require their own
739+removal in certain cases when you modify the work.) You may place
740+additional permissions on material, added by you to a covered work,
741+for which you have or can give appropriate copyright permission.
742+
743+ Notwithstanding any other provision of this License, for material you
744+add to a covered work, you may (if authorized by the copyright holders of
745+that material) supplement the terms of this License with terms:
746+
747+ a) Disclaiming warranty or limiting liability differently from the
748+ terms of sections 15 and 16 of this License; or
749+
750+ b) Requiring preservation of specified reasonable legal notices or
751+ author attributions in that material or in the Appropriate Legal
752+ Notices displayed by works containing it; or
753+
754+ c) Prohibiting misrepresentation of the origin of that material, or
755+ requiring that modified versions of such material be marked in
756+ reasonable ways as different from the original version; or
757+
758+ d) Limiting the use for publicity purposes of names of licensors or
759+ authors of the material; or
760+
761+ e) Declining to grant rights under trademark law for use of some
762+ trade names, trademarks, or service marks; or
763+
764+ f) Requiring indemnification of licensors and authors of that
765+ material by anyone who conveys the material (or modified versions of
766+ it) with contractual assumptions of liability to the recipient, for
767+ any liability that these contractual assumptions directly impose on
768+ those licensors and authors.
769+
770+ All other non-permissive additional terms are considered "further
771+restrictions" within the meaning of section 10. If the Program as you
772+received it, or any part of it, contains a notice stating that it is
773+governed by this License along with a term that is a further
774+restriction, you may remove that term. If a license document contains
775+a further restriction but permits relicensing or conveying under this
776+License, you may add to a covered work material governed by the terms
777+of that license document, provided that the further restriction does
778+not survive such relicensing or conveying.
779+
780+ If you add terms to a covered work in accord with this section, you
781+must place, in the relevant source files, a statement of the
782+additional terms that apply to those files, or a notice indicating
783+where to find the applicable terms.
784+
785+ Additional terms, permissive or non-permissive, may be stated in the
786+form of a separately written license, or stated as exceptions;
787+the above requirements apply either way.
788+
789+ 8. Termination.
790+
791+ You may not propagate or modify a covered work except as expressly
792+provided under this License. Any attempt otherwise to propagate or
793+modify it is void, and will automatically terminate your rights under
794+this License (including any patent licenses granted under the third
795+paragraph of section 11).
796+
797+ However, if you cease all violation of this License, then your
798+license from a particular copyright holder is reinstated (a)
799+provisionally, unless and until the copyright holder explicitly and
800+finally terminates your license, and (b) permanently, if the copyright
801+holder fails to notify you of the violation by some reasonable means
802+prior to 60 days after the cessation.
803+
804+ Moreover, your license from a particular copyright holder is
805+reinstated permanently if the copyright holder notifies you of the
806+violation by some reasonable means, this is the first time you have
807+received notice of violation of this License (for any work) from that
808+copyright holder, and you cure the violation prior to 30 days after
809+your receipt of the notice.
810+
811+ Termination of your rights under this section does not terminate the
812+licenses of parties who have received copies or rights from you under
813+this License. If your rights have been terminated and not permanently
814+reinstated, you do not qualify to receive new licenses for the same
815+material under section 10.
816+
817+ 9. Acceptance Not Required for Having Copies.
818+
819+ You are not required to accept this License in order to receive or
820+run a copy of the Program. Ancillary propagation of a covered work
821+occurring solely as a consequence of using peer-to-peer transmission
822+to receive a copy likewise does not require acceptance. However,
823+nothing other than this License grants you permission to propagate or
824+modify any covered work. These actions infringe copyright if you do
825+not accept this License. Therefore, by modifying or propagating a
826+covered work, you indicate your acceptance of this License to do so.
827+
828+ 10. Automatic Licensing of Downstream Recipients.
829+
830+ Each time you convey a covered work, the recipient automatically
831+receives a license from the original licensors, to run, modify and
832+propagate that work, subject to this License. You are not responsible
833+for enforcing compliance by third parties with this License.
834+
835+ An "entity transaction" is a transaction transferring control of an
836+organization, or substantially all assets of one, or subdividing an
837+organization, or merging organizations. If propagation of a covered
838+work results from an entity transaction, each party to that
839+transaction who receives a copy of the work also receives whatever
840+licenses to the work the party's predecessor in interest had or could
841+give under the previous paragraph, plus a right to possession of the
842+Corresponding Source of the work from the predecessor in interest, if
843+the predecessor has it or can get it with reasonable efforts.
844+
845+ You may not impose any further restrictions on the exercise of the
846+rights granted or affirmed under this License. For example, you may
847+not impose a license fee, royalty, or other charge for exercise of
848+rights granted under this License, and you may not initiate litigation
849+(including a cross-claim or counterclaim in a lawsuit) alleging that
850+any patent claim is infringed by making, using, selling, offering for
851+sale, or importing the Program or any portion of it.
852+
853+ 11. Patents.
854+
855+ A "contributor" is a copyright holder who authorizes use under this
856+License of the Program or a work on which the Program is based. The
857+work thus licensed is called the contributor's "contributor version".
858+
859+ A contributor's "essential patent claims" are all patent claims
860+owned or controlled by the contributor, whether already acquired or
861+hereafter acquired, that would be infringed by some manner, permitted
862+by this License, of making, using, or selling its contributor version,
863+but do not include claims that would be infringed only as a
864+consequence of further modification of the contributor version. For
865+purposes of this definition, "control" includes the right to grant
866+patent sublicenses in a manner consistent with the requirements of
867+this License.
868+
869+ Each contributor grants you a non-exclusive, worldwide, royalty-free
870+patent license under the contributor's essential patent claims, to
871+make, use, sell, offer for sale, import and otherwise run, modify and
872+propagate the contents of its contributor version.
873+
874+ In the following three paragraphs, a "patent license" is any express
875+agreement or commitment, however denominated, not to enforce a patent
876+(such as an express permission to practice a patent or covenant not to
877+sue for patent infringement). To "grant" such a patent license to a
878+party means to make such an agreement or commitment not to enforce a
879+patent against the party.
880+
881+ If you convey a covered work, knowingly relying on a patent license,
882+and the Corresponding Source of the work is not available for anyone
883+to copy, free of charge and under the terms of this License, through a
884+publicly available network server or other readily accessible means,
885+then you must either (1) cause the Corresponding Source to be so
886+available, or (2) arrange to deprive yourself of the benefit of the
887+patent license for this particular work, or (3) arrange, in a manner
888+consistent with the requirements of this License, to extend the patent
889+license to downstream recipients. "Knowingly relying" means you have
890+actual knowledge that, but for the patent license, your conveying the
891+covered work in a country, or your recipient's use of the covered work
892+in a country, would infringe one or more identifiable patents in that
893+country that you have reason to believe are valid.
894+
895+ If, pursuant to or in connection with a single transaction or
896+arrangement, you convey, or propagate by procuring conveyance of, a
897+covered work, and grant a patent license to some of the parties
898+receiving the covered work authorizing them to use, propagate, modify
899+or convey a specific copy of the covered work, then the patent license
900+you grant is automatically extended to all recipients of the covered
901+work and works based on it.
902+
903+ A patent license is "discriminatory" if it does not include within
904+the scope of its coverage, prohibits the exercise of, or is
905+conditioned on the non-exercise of one or more of the rights that are
906+specifically granted under this License. You may not convey a covered
907+work if you are a party to an arrangement with a third party that is
908+in the business of distributing software, under which you make payment
909+to the third party based on the extent of your activity of conveying
910+the work, and under which the third party grants, to any of the
911+parties who would receive the covered work from you, a discriminatory
912+patent license (a) in connection with copies of the covered work
913+conveyed by you (or copies made from those copies), or (b) primarily
914+for and in connection with specific products or compilations that
915+contain the covered work, unless you entered into that arrangement,
916+or that patent license was granted, prior to 28 March 2007.
917+
918+ Nothing in this License shall be construed as excluding or limiting
919+any implied license or other defenses to infringement that may
920+otherwise be available to you under applicable patent law.
921+
922+ 12. No Surrender of Others' Freedom.
923+
924+ If conditions are imposed on you (whether by court order, agreement or
925+otherwise) that contradict the conditions of this License, they do not
926+excuse you from the conditions of this License. If you cannot convey a
927+covered work so as to satisfy simultaneously your obligations under this
928+License and any other pertinent obligations, then as a consequence you may
929+not convey it at all. For example, if you agree to terms that obligate you
930+to collect a royalty for further conveying from those to whom you convey
931+the Program, the only way you could satisfy both those terms and this
932+License would be to refrain entirely from conveying the Program.
933+
934+ 13. Remote Network Interaction; Use with the GNU General Public License.
935+
936+ Notwithstanding any other provision of this License, if you modify the
937+Program, your modified version must prominently offer all users
938+interacting with it remotely through a computer network (if your version
939+supports such interaction) an opportunity to receive the Corresponding
940+Source of your version by providing access to the Corresponding Source
941+from a network server at no charge, through some standard or customary
942+means of facilitating copying of software. This Corresponding Source
943+shall include the Corresponding Source for any work covered by version 3
944+of the GNU General Public License that is incorporated pursuant to the
945+following paragraph.
946+
947+ Notwithstanding any other provision of this License, you have
948+permission to link or combine any covered work with a work licensed
949+under version 3 of the GNU General Public License into a single
950+combined work, and to convey the resulting work. The terms of this
951+License will continue to apply to the part which is the covered work,
952+but the work with which it is combined will remain governed by version
953+3 of the GNU General Public License.
954+
955+ 14. Revised Versions of this License.
956+
957+ The Free Software Foundation may publish revised and/or new versions of
958+the GNU Affero General Public License from time to time. Such new versions
959+will be similar in spirit to the present version, but may differ in detail to
960+address new problems or concerns.
961+
962+ Each version is given a distinguishing version number. If the
963+Program specifies that a certain numbered version of the GNU Affero General
964+Public License "or any later version" applies to it, you have the
965+option of following the terms and conditions either of that numbered
966+version or of any later version published by the Free Software
967+Foundation. If the Program does not specify a version number of the
968+GNU Affero General Public License, you may choose any version ever published
969+by the Free Software Foundation.
970+
971+ If the Program specifies that a proxy can decide which future
972+versions of the GNU Affero General Public License can be used, that proxy's
973+public statement of acceptance of a version permanently authorizes you
974+to choose that version for the Program.
975+
976+ Later license versions may give you additional or different
977+permissions. However, no additional obligations are imposed on any
978+author or copyright holder as a result of your choosing to follow a
979+later version.
980+
981+ 15. Disclaimer of Warranty.
982+
983+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
984+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
985+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
986+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
987+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
988+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
989+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
990+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
991+
992+ 16. Limitation of Liability.
993+
994+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
995+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
996+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
997+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
998+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
999+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
1000+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
1001+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
1002+SUCH DAMAGES.
1003+
1004+ 17. Interpretation of Sections 15 and 16.
1005+
1006+ If the disclaimer of warranty and limitation of liability provided
1007+above cannot be given local legal effect according to their terms,
1008+reviewing courts shall apply local law that most closely approximates
1009+an absolute waiver of all civil liability in connection with the
1010+Program, unless a warranty or assumption of liability accompanies a
1011+copy of the Program in return for a fee.
1012+
1013+ END OF TERMS AND CONDITIONS
1014+
1015+ How to Apply These Terms to Your New Programs
1016+
1017+ If you develop a new program, and you want it to be of the greatest
1018+possible use to the public, the best way to achieve this is to make it
1019+free software which everyone can redistribute and change under these terms.
1020+
1021+ To do so, attach the following notices to the program. It is safest
1022+to attach them to the start of each source file to most effectively
1023+state the exclusion of warranty; and each file should have at least
1024+the "copyright" line and a pointer to where the full notice is found.
1025+
1026+ <one line to give the program's name and a brief idea of what it does.>
1027+ Copyright (C) <year> <name of author>
1028+
1029+ This program is free software: you can redistribute it and/or modify
1030+ it under the terms of the GNU Affero General Public License as published by
1031+ the Free Software Foundation, either version 3 of the License, or
1032+ (at your option) any later version.
1033+
1034+ This program is distributed in the hope that it will be useful,
1035+ but WITHOUT ANY WARRANTY; without even the implied warranty of
1036+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1037+ GNU Affero General Public License for more details.
1038+
1039+ You should have received a copy of the GNU Affero General Public License
1040+ along with this program. If not, see <http://www.gnu.org/licenses/>.
1041+
1042+Also add information on how to contact you by electronic and paper mail.
1043+
1044+ If your software can interact with users remotely through a computer
1045+network, you should also make sure that it provides a way for users to
1046+get its source. For example, if your program is a web application, its
1047+interface could display a "Source" link that leads users to an archive
1048+of the code. There are many ways you could offer source, and different
1049+solutions will be better for different programs; see section 13 for the
1050+specific requirements.
1051+
1052+ You should also get your employer (if you work as a programmer) or school,
1053+if any, to sign a "copyright disclaimer" for the program, if necessary.
1054+For more information on this, and how to apply and follow the GNU AGPL, see
1055+<http://www.gnu.org/licenses/>.
1056
1057=== removed file 'COPYING'
1058--- COPYING 2012-06-11 15:40:48 +0000
1059+++ COPYING 1970-01-01 00:00:00 +0000
1060@@ -1,661 +0,0 @@
1061- GNU AFFERO GENERAL PUBLIC LICENSE
1062- Version 3, 19 November 2007
1063-
1064- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
1065- Everyone is permitted to copy and distribute verbatim copies
1066- of this license document, but changing it is not allowed.
1067-
1068- Preamble
1069-
1070- The GNU Affero General Public License is a free, copyleft license for
1071-software and other kinds of works, specifically designed to ensure
1072-cooperation with the community in the case of network server software.
1073-
1074- The licenses for most software and other practical works are designed
1075-to take away your freedom to share and change the works. By contrast,
1076-our General Public Licenses are intended to guarantee your freedom to
1077-share and change all versions of a program--to make sure it remains free
1078-software for all its users.
1079-
1080- When we speak of free software, we are referring to freedom, not
1081-price. Our General Public Licenses are designed to make sure that you
1082-have the freedom to distribute copies of free software (and charge for
1083-them if you wish), that you receive source code or can get it if you
1084-want it, that you can change the software or use pieces of it in new
1085-free programs, and that you know you can do these things.
1086-
1087- Developers that use our General Public Licenses protect your rights
1088-with two steps: (1) assert copyright on the software, and (2) offer
1089-you this License which gives you legal permission to copy, distribute
1090-and/or modify the software.
1091-
1092- A secondary benefit of defending all users' freedom is that
1093-improvements made in alternate versions of the program, if they
1094-receive widespread use, become available for other developers to
1095-incorporate. Many developers of free software are heartened and
1096-encouraged by the resulting cooperation. However, in the case of
1097-software used on network servers, this result may fail to come about.
1098-The GNU General Public License permits making a modified version and
1099-letting the public access it on a server without ever releasing its
1100-source code to the public.
1101-
1102- The GNU Affero General Public License is designed specifically to
1103-ensure that, in such cases, the modified source code becomes available
1104-to the community. It requires the operator of a network server to
1105-provide the source code of the modified version running there to the
1106-users of that server. Therefore, public use of a modified version, on
1107-a publicly accessible server, gives the public access to the source
1108-code of the modified version.
1109-
1110- An older license, called the Affero General Public License and
1111-published by Affero, was designed to accomplish similar goals. This is
1112-a different license, not a version of the Affero GPL, but Affero has
1113-released a new version of the Affero GPL which permits relicensing under
1114-this license.
1115-
1116- The precise terms and conditions for copying, distribution and
1117-modification follow.
1118-
1119- TERMS AND CONDITIONS
1120-
1121- 0. Definitions.
1122-
1123- "This License" refers to version 3 of the GNU Affero General Public License.
1124-
1125- "Copyright" also means copyright-like laws that apply to other kinds of
1126-works, such as semiconductor masks.
1127-
1128- "The Program" refers to any copyrightable work licensed under this
1129-License. Each licensee is addressed as "you". "Licensees" and
1130-"recipients" may be individuals or organizations.
1131-
1132- To "modify" a work means to copy from or adapt all or part of the work
1133-in a fashion requiring copyright permission, other than the making of an
1134-exact copy. The resulting work is called a "modified version" of the
1135-earlier work or a work "based on" the earlier work.
1136-
1137- A "covered work" means either the unmodified Program or a work based
1138-on the Program.
1139-
1140- To "propagate" a work means to do anything with it that, without
1141-permission, would make you directly or secondarily liable for
1142-infringement under applicable copyright law, except executing it on a
1143-computer or modifying a private copy. Propagation includes copying,
1144-distribution (with or without modification), making available to the
1145-public, and in some countries other activities as well.
1146-
1147- To "convey" a work means any kind of propagation that enables other
1148-parties to make or receive copies. Mere interaction with a user through
1149-a computer network, with no transfer of a copy, is not conveying.
1150-
1151- An interactive user interface displays "Appropriate Legal Notices"
1152-to the extent that it includes a convenient and prominently visible
1153-feature that (1) displays an appropriate copyright notice, and (2)
1154-tells the user that there is no warranty for the work (except to the
1155-extent that warranties are provided), that licensees may convey the
1156-work under this License, and how to view a copy of this License. If
1157-the interface presents a list of user commands or options, such as a
1158-menu, a prominent item in the list meets this criterion.
1159-
1160- 1. Source Code.
1161-
1162- The "source code" for a work means the preferred form of the work
1163-for making modifications to it. "Object code" means any non-source
1164-form of a work.
1165-
1166- A "Standard Interface" means an interface that either is an official
1167-standard defined by a recognized standards body, or, in the case of
1168-interfaces specified for a particular programming language, one that
1169-is widely used among developers working in that language.
1170-
1171- The "System Libraries" of an executable work include anything, other
1172-than the work as a whole, that (a) is included in the normal form of
1173-packaging a Major Component, but which is not part of that Major
1174-Component, and (b) serves only to enable use of the work with that
1175-Major Component, or to implement a Standard Interface for which an
1176-implementation is available to the public in source code form. A
1177-"Major Component", in this context, means a major essential component
1178-(kernel, window system, and so on) of the specific operating system
1179-(if any) on which the executable work runs, or a compiler used to
1180-produce the work, or an object code interpreter used to run it.
1181-
1182- The "Corresponding Source" for a work in object code form means all
1183-the source code needed to generate, install, and (for an executable
1184-work) run the object code and to modify the work, including scripts to
1185-control those activities. However, it does not include the work's
1186-System Libraries, or general-purpose tools or generally available free
1187-programs which are used unmodified in performing those activities but
1188-which are not part of the work. For example, Corresponding Source
1189-includes interface definition files associated with source files for
1190-the work, and the source code for shared libraries and dynamically
1191-linked subprograms that the work is specifically designed to require,
1192-such as by intimate data communication or control flow between those
1193-subprograms and other parts of the work.
1194-
1195- The Corresponding Source need not include anything that users
1196-can regenerate automatically from other parts of the Corresponding
1197-Source.
1198-
1199- The Corresponding Source for a work in source code form is that
1200-same work.
1201-
1202- 2. Basic Permissions.
1203-
1204- All rights granted under this License are granted for the term of
1205-copyright on the Program, and are irrevocable provided the stated
1206-conditions are met. This License explicitly affirms your unlimited
1207-permission to run the unmodified Program. The output from running a
1208-covered work is covered by this License only if the output, given its
1209-content, constitutes a covered work. This License acknowledges your
1210-rights of fair use or other equivalent, as provided by copyright law.
1211-
1212- You may make, run and propagate covered works that you do not
1213-convey, without conditions so long as your license otherwise remains
1214-in force. You may convey covered works to others for the sole purpose
1215-of having them make modifications exclusively for you, or provide you
1216-with facilities for running those works, provided that you comply with
1217-the terms of this License in conveying all material for which you do
1218-not control copyright. Those thus making or running the covered works
1219-for you must do so exclusively on your behalf, under your direction
1220-and control, on terms that prohibit them from making any copies of
1221-your copyrighted material outside their relationship with you.
1222-
1223- Conveying under any other circumstances is permitted solely under
1224-the conditions stated below. Sublicensing is not allowed; section 10
1225-makes it unnecessary.
1226-
1227- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
1228-
1229- No covered work shall be deemed part of an effective technological
1230-measure under any applicable law fulfilling obligations under article
1231-11 of the WIPO copyright treaty adopted on 20 December 1996, or
1232-similar laws prohibiting or restricting circumvention of such
1233-measures.
1234-
1235- When you convey a covered work, you waive any legal power to forbid
1236-circumvention of technological measures to the extent such circumvention
1237-is effected by exercising rights under this License with respect to
1238-the covered work, and you disclaim any intention to limit operation or
1239-modification of the work as a means of enforcing, against the work's
1240-users, your or third parties' legal rights to forbid circumvention of
1241-technological measures.
1242-
1243- 4. Conveying Verbatim Copies.
1244-
1245- You may convey verbatim copies of the Program's source code as you
1246-receive it, in any medium, provided that you conspicuously and
1247-appropriately publish on each copy an appropriate copyright notice;
1248-keep intact all notices stating that this License and any
1249-non-permissive terms added in accord with section 7 apply to the code;
1250-keep intact all notices of the absence of any warranty; and give all
1251-recipients a copy of this License along with the Program.
1252-
1253- You may charge any price or no price for each copy that you convey,
1254-and you may offer support or warranty protection for a fee.
1255-
1256- 5. Conveying Modified Source Versions.
1257-
1258- You may convey a work based on the Program, or the modifications to
1259-produce it from the Program, in the form of source code under the
1260-terms of section 4, provided that you also meet all of these conditions:
1261-
1262- a) The work must carry prominent notices stating that you modified
1263- it, and giving a relevant date.
1264-
1265- b) The work must carry prominent notices stating that it is
1266- released under this License and any conditions added under section
1267- 7. This requirement modifies the requirement in section 4 to
1268- "keep intact all notices".
1269-
1270- c) You must license the entire work, as a whole, under this
1271- License to anyone who comes into possession of a copy. This
1272- License will therefore apply, along with any applicable section 7
1273- additional terms, to the whole of the work, and all its parts,
1274- regardless of how they are packaged. This License gives no
1275- permission to license the work in any other way, but it does not
1276- invalidate such permission if you have separately received it.
1277-
1278- d) If the work has interactive user interfaces, each must display
1279- Appropriate Legal Notices; however, if the Program has interactive
1280- interfaces that do not display Appropriate Legal Notices, your
1281- work need not make them do so.
1282-
1283- A compilation of a covered work with other separate and independent
1284-works, which are not by their nature extensions of the covered work,
1285-and which are not combined with it such as to form a larger program,
1286-in or on a volume of a storage or distribution medium, is called an
1287-"aggregate" if the compilation and its resulting copyright are not
1288-used to limit the access or legal rights of the compilation's users
1289-beyond what the individual works permit. Inclusion of a covered work
1290-in an aggregate does not cause this License to apply to the other
1291-parts of the aggregate.
1292-
1293- 6. Conveying Non-Source Forms.
1294-
1295- You may convey a covered work in object code form under the terms
1296-of sections 4 and 5, provided that you also convey the
1297-machine-readable Corresponding Source under the terms of this License,
1298-in one of these ways:
1299-
1300- a) Convey the object code in, or embodied in, a physical product
1301- (including a physical distribution medium), accompanied by the
1302- Corresponding Source fixed on a durable physical medium
1303- customarily used for software interchange.
1304-
1305- b) Convey the object code in, or embodied in, a physical product
1306- (including a physical distribution medium), accompanied by a
1307- written offer, valid for at least three years and valid for as
1308- long as you offer spare parts or customer support for that product
1309- model, to give anyone who possesses the object code either (1) a
1310- copy of the Corresponding Source for all the software in the
1311- product that is covered by this License, on a durable physical
1312- medium customarily used for software interchange, for a price no
1313- more than your reasonable cost of physically performing this
1314- conveying of source, or (2) access to copy the
1315- Corresponding Source from a network server at no charge.
1316-
1317- c) Convey individual copies of the object code with a copy of the
1318- written offer to provide the Corresponding Source. This
1319- alternative is allowed only occasionally and noncommercially, and
1320- only if you received the object code with such an offer, in accord
1321- with subsection 6b.
1322-
1323- d) Convey the object code by offering access from a designated
1324- place (gratis or for a charge), and offer equivalent access to the
1325- Corresponding Source in the same way through the same place at no
1326- further charge. You need not require recipients to copy the
1327- Corresponding Source along with the object code. If the place to
1328- copy the object code is a network server, the Corresponding Source
1329- may be on a different server (operated by you or a third party)
1330- that supports equivalent copying facilities, provided you maintain
1331- clear directions next to the object code saying where to find the
1332- Corresponding Source. Regardless of what server hosts the
1333- Corresponding Source, you remain obligated to ensure that it is
1334- available for as long as needed to satisfy these requirements.
1335-
1336- e) Convey the object code using peer-to-peer transmission, provided
1337- you inform other peers where the object code and Corresponding
1338- Source of the work are being offered to the general public at no
1339- charge under subsection 6d.
1340-
1341- A separable portion of the object code, whose source code is excluded
1342-from the Corresponding Source as a System Library, need not be
1343-included in conveying the object code work.
1344-
1345- A "User Product" is either (1) a "consumer product", which means any
1346-tangible personal property which is normally used for personal, family,
1347-or household purposes, or (2) anything designed or sold for incorporation
1348-into a dwelling. In determining whether a product is a consumer product,
1349-doubtful cases shall be resolved in favor of coverage. For a particular
1350-product received by a particular user, "normally used" refers to a
1351-typical or common use of that class of product, regardless of the status
1352-of the particular user or of the way in which the particular user
1353-actually uses, or expects or is expected to use, the product. A product
1354-is a consumer product regardless of whether the product has substantial
1355-commercial, industrial or non-consumer uses, unless such uses represent
1356-the only significant mode of use of the product.
1357-
1358- "Installation Information" for a User Product means any methods,
1359-procedures, authorization keys, or other information required to install
1360-and execute modified versions of a covered work in that User Product from
1361-a modified version of its Corresponding Source. The information must
1362-suffice to ensure that the continued functioning of the modified object
1363-code is in no case prevented or interfered with solely because
1364-modification has been made.
1365-
1366- If you convey an object code work under this section in, or with, or
1367-specifically for use in, a User Product, and the conveying occurs as
1368-part of a transaction in which the right of possession and use of the
1369-User Product is transferred to the recipient in perpetuity or for a
1370-fixed term (regardless of how the transaction is characterized), the
1371-Corresponding Source conveyed under this section must be accompanied
1372-by the Installation Information. But this requirement does not apply
1373-if neither you nor any third party retains the ability to install
1374-modified object code on the User Product (for example, the work has
1375-been installed in ROM).
1376-
1377- The requirement to provide Installation Information does not include a
1378-requirement to continue to provide support service, warranty, or updates
1379-for a work that has been modified or installed by the recipient, or for
1380-the User Product in which it has been modified or installed. Access to a
1381-network may be denied when the modification itself materially and
1382-adversely affects the operation of the network or violates the rules and
1383-protocols for communication across the network.
1384-
1385- Corresponding Source conveyed, and Installation Information provided,
1386-in accord with this section must be in a format that is publicly
1387-documented (and with an implementation available to the public in
1388-source code form), and must require no special password or key for
1389-unpacking, reading or copying.
1390-
1391- 7. Additional Terms.
1392-
1393- "Additional permissions" are terms that supplement the terms of this
1394-License by making exceptions from one or more of its conditions.
1395-Additional permissions that are applicable to the entire Program shall
1396-be treated as though they were included in this License, to the extent
1397-that they are valid under applicable law. If additional permissions
1398-apply only to part of the Program, that part may be used separately
1399-under those permissions, but the entire Program remains governed by
1400-this License without regard to the additional permissions.
1401-
1402- When you convey a copy of a covered work, you may at your option
1403-remove any additional permissions from that copy, or from any part of
1404-it. (Additional permissions may be written to require their own
1405-removal in certain cases when you modify the work.) You may place
1406-additional permissions on material, added by you to a covered work,
1407-for which you have or can give appropriate copyright permission.
1408-
1409- Notwithstanding any other provision of this License, for material you
1410-add to a covered work, you may (if authorized by the copyright holders of
1411-that material) supplement the terms of this License with terms:
1412-
1413- a) Disclaiming warranty or limiting liability differently from the
1414- terms of sections 15 and 16 of this License; or
1415-
1416- b) Requiring preservation of specified reasonable legal notices or
1417- author attributions in that material or in the Appropriate Legal
1418- Notices displayed by works containing it; or
1419-
1420- c) Prohibiting misrepresentation of the origin of that material, or
1421- requiring that modified versions of such material be marked in
1422- reasonable ways as different from the original version; or
1423-
1424- d) Limiting the use for publicity purposes of names of licensors or
1425- authors of the material; or
1426-
1427- e) Declining to grant rights under trademark law for use of some
1428- trade names, trademarks, or service marks; or
1429-
1430- f) Requiring indemnification of licensors and authors of that
1431- material by anyone who conveys the material (or modified versions of
1432- it) with contractual assumptions of liability to the recipient, for
1433- any liability that these contractual assumptions directly impose on
1434- those licensors and authors.
1435-
1436- All other non-permissive additional terms are considered "further
1437-restrictions" within the meaning of section 10. If the Program as you
1438-received it, or any part of it, contains a notice stating that it is
1439-governed by this License along with a term that is a further
1440-restriction, you may remove that term. If a license document contains
1441-a further restriction but permits relicensing or conveying under this
1442-License, you may add to a covered work material governed by the terms
1443-of that license document, provided that the further restriction does
1444-not survive such relicensing or conveying.
1445-
1446- If you add terms to a covered work in accord with this section, you
1447-must place, in the relevant source files, a statement of the
1448-additional terms that apply to those files, or a notice indicating
1449-where to find the applicable terms.
1450-
1451- Additional terms, permissive or non-permissive, may be stated in the
1452-form of a separately written license, or stated as exceptions;
1453-the above requirements apply either way.
1454-
1455- 8. Termination.
1456-
1457- You may not propagate or modify a covered work except as expressly
1458-provided under this License. Any attempt otherwise to propagate or
1459-modify it is void, and will automatically terminate your rights under
1460-this License (including any patent licenses granted under the third
1461-paragraph of section 11).
1462-
1463- However, if you cease all violation of this License, then your
1464-license from a particular copyright holder is reinstated (a)
1465-provisionally, unless and until the copyright holder explicitly and
1466-finally terminates your license, and (b) permanently, if the copyright
1467-holder fails to notify you of the violation by some reasonable means
1468-prior to 60 days after the cessation.
1469-
1470- Moreover, your license from a particular copyright holder is
1471-reinstated permanently if the copyright holder notifies you of the
1472-violation by some reasonable means, this is the first time you have
1473-received notice of violation of this License (for any work) from that
1474-copyright holder, and you cure the violation prior to 30 days after
1475-your receipt of the notice.
1476-
1477- Termination of your rights under this section does not terminate the
1478-licenses of parties who have received copies or rights from you under
1479-this License. If your rights have been terminated and not permanently
1480-reinstated, you do not qualify to receive new licenses for the same
1481-material under section 10.
1482-
1483- 9. Acceptance Not Required for Having Copies.
1484-
1485- You are not required to accept this License in order to receive or
1486-run a copy of the Program. Ancillary propagation of a covered work
1487-occurring solely as a consequence of using peer-to-peer transmission
1488-to receive a copy likewise does not require acceptance. However,
1489-nothing other than this License grants you permission to propagate or
1490-modify any covered work. These actions infringe copyright if you do
1491-not accept this License. Therefore, by modifying or propagating a
1492-covered work, you indicate your acceptance of this License to do so.
1493-
1494- 10. Automatic Licensing of Downstream Recipients.
1495-
1496- Each time you convey a covered work, the recipient automatically
1497-receives a license from the original licensors, to run, modify and
1498-propagate that work, subject to this License. You are not responsible
1499-for enforcing compliance by third parties with this License.
1500-
1501- An "entity transaction" is a transaction transferring control of an
1502-organization, or substantially all assets of one, or subdividing an
1503-organization, or merging organizations. If propagation of a covered
1504-work results from an entity transaction, each party to that
1505-transaction who receives a copy of the work also receives whatever
1506-licenses to the work the party's predecessor in interest had or could
1507-give under the previous paragraph, plus a right to possession of the
1508-Corresponding Source of the work from the predecessor in interest, if
1509-the predecessor has it or can get it with reasonable efforts.
1510-
1511- You may not impose any further restrictions on the exercise of the
1512-rights granted or affirmed under this License. For example, you may
1513-not impose a license fee, royalty, or other charge for exercise of
1514-rights granted under this License, and you may not initiate litigation
1515-(including a cross-claim or counterclaim in a lawsuit) alleging that
1516-any patent claim is infringed by making, using, selling, offering for
1517-sale, or importing the Program or any portion of it.
1518-
1519- 11. Patents.
1520-
1521- A "contributor" is a copyright holder who authorizes use under this
1522-License of the Program or a work on which the Program is based. The
1523-work thus licensed is called the contributor's "contributor version".
1524-
1525- A contributor's "essential patent claims" are all patent claims
1526-owned or controlled by the contributor, whether already acquired or
1527-hereafter acquired, that would be infringed by some manner, permitted
1528-by this License, of making, using, or selling its contributor version,
1529-but do not include claims that would be infringed only as a
1530-consequence of further modification of the contributor version. For
1531-purposes of this definition, "control" includes the right to grant
1532-patent sublicenses in a manner consistent with the requirements of
1533-this License.
1534-
1535- Each contributor grants you a non-exclusive, worldwide, royalty-free
1536-patent license under the contributor's essential patent claims, to
1537-make, use, sell, offer for sale, import and otherwise run, modify and
1538-propagate the contents of its contributor version.
1539-
1540- In the following three paragraphs, a "patent license" is any express
1541-agreement or commitment, however denominated, not to enforce a patent
1542-(such as an express permission to practice a patent or covenant not to
1543-sue for patent infringement). To "grant" such a patent license to a
1544-party means to make such an agreement or commitment not to enforce a
1545-patent against the party.
1546-
1547- If you convey a covered work, knowingly relying on a patent license,
1548-and the Corresponding Source of the work is not available for anyone
1549-to copy, free of charge and under the terms of this License, through a
1550-publicly available network server or other readily accessible means,
1551-then you must either (1) cause the Corresponding Source to be so
1552-available, or (2) arrange to deprive yourself of the benefit of the
1553-patent license for this particular work, or (3) arrange, in a manner
1554-consistent with the requirements of this License, to extend the patent
1555-license to downstream recipients. "Knowingly relying" means you have
1556-actual knowledge that, but for the patent license, your conveying the
1557-covered work in a country, or your recipient's use of the covered work
1558-in a country, would infringe one or more identifiable patents in that
1559-country that you have reason to believe are valid.
1560-
1561- If, pursuant to or in connection with a single transaction or
1562-arrangement, you convey, or propagate by procuring conveyance of, a
1563-covered work, and grant a patent license to some of the parties
1564-receiving the covered work authorizing them to use, propagate, modify
1565-or convey a specific copy of the covered work, then the patent license
1566-you grant is automatically extended to all recipients of the covered
1567-work and works based on it.
1568-
1569- A patent license is "discriminatory" if it does not include within
1570-the scope of its coverage, prohibits the exercise of, or is
1571-conditioned on the non-exercise of one or more of the rights that are
1572-specifically granted under this License. You may not convey a covered
1573-work if you are a party to an arrangement with a third party that is
1574-in the business of distributing software, under which you make payment
1575-to the third party based on the extent of your activity of conveying
1576-the work, and under which the third party grants, to any of the
1577-parties who would receive the covered work from you, a discriminatory
1578-patent license (a) in connection with copies of the covered work
1579-conveyed by you (or copies made from those copies), or (b) primarily
1580-for and in connection with specific products or compilations that
1581-contain the covered work, unless you entered into that arrangement,
1582-or that patent license was granted, prior to 28 March 2007.
1583-
1584- Nothing in this License shall be construed as excluding or limiting
1585-any implied license or other defenses to infringement that may
1586-otherwise be available to you under applicable patent law.
1587-
1588- 12. No Surrender of Others' Freedom.
1589-
1590- If conditions are imposed on you (whether by court order, agreement or
1591-otherwise) that contradict the conditions of this License, they do not
1592-excuse you from the conditions of this License. If you cannot convey a
1593-covered work so as to satisfy simultaneously your obligations under this
1594-License and any other pertinent obligations, then as a consequence you may
1595-not convey it at all. For example, if you agree to terms that obligate you
1596-to collect a royalty for further conveying from those to whom you convey
1597-the Program, the only way you could satisfy both those terms and this
1598-License would be to refrain entirely from conveying the Program.
1599-
1600- 13. Remote Network Interaction; Use with the GNU General Public License.
1601-
1602- Notwithstanding any other provision of this License, if you modify the
1603-Program, your modified version must prominently offer all users
1604-interacting with it remotely through a computer network (if your version
1605-supports such interaction) an opportunity to receive the Corresponding
1606-Source of your version by providing access to the Corresponding Source
1607-from a network server at no charge, through some standard or customary
1608-means of facilitating copying of software. This Corresponding Source
1609-shall include the Corresponding Source for any work covered by version 3
1610-of the GNU General Public License that is incorporated pursuant to the
1611-following paragraph.
1612-
1613- Notwithstanding any other provision of this License, you have
1614-permission to link or combine any covered work with a work licensed
1615-under version 3 of the GNU General Public License into a single
1616-combined work, and to convey the resulting work. The terms of this
1617-License will continue to apply to the part which is the covered work,
1618-but the work with which it is combined will remain governed by version
1619-3 of the GNU General Public License.
1620-
1621- 14. Revised Versions of this License.
1622-
1623- The Free Software Foundation may publish revised and/or new versions of
1624-the GNU Affero General Public License from time to time. Such new versions
1625-will be similar in spirit to the present version, but may differ in detail to
1626-address new problems or concerns.
1627-
1628- Each version is given a distinguishing version number. If the
1629-Program specifies that a certain numbered version of the GNU Affero General
1630-Public License "or any later version" applies to it, you have the
1631-option of following the terms and conditions either of that numbered
1632-version or of any later version published by the Free Software
1633-Foundation. If the Program does not specify a version number of the
1634-GNU Affero General Public License, you may choose any version ever published
1635-by the Free Software Foundation.
1636-
1637- If the Program specifies that a proxy can decide which future
1638-versions of the GNU Affero General Public License can be used, that proxy's
1639-public statement of acceptance of a version permanently authorizes you
1640-to choose that version for the Program.
1641-
1642- Later license versions may give you additional or different
1643-permissions. However, no additional obligations are imposed on any
1644-author or copyright holder as a result of your choosing to follow a
1645-later version.
1646-
1647- 15. Disclaimer of Warranty.
1648-
1649- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
1650-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
1651-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
1652-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
1653-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
1654-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
1655-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
1656-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
1657-
1658- 16. Limitation of Liability.
1659-
1660- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
1661-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
1662-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
1663-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
1664-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
1665-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
1666-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
1667-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
1668-SUCH DAMAGES.
1669-
1670- 17. Interpretation of Sections 15 and 16.
1671-
1672- If the disclaimer of warranty and limitation of liability provided
1673-above cannot be given local legal effect according to their terms,
1674-reviewing courts shall apply local law that most closely approximates
1675-an absolute waiver of all civil liability in connection with the
1676-Program, unless a warranty or assumption of liability accompanies a
1677-copy of the Program in return for a fee.
1678-
1679- END OF TERMS AND CONDITIONS
1680-
1681- How to Apply These Terms to Your New Programs
1682-
1683- If you develop a new program, and you want it to be of the greatest
1684-possible use to the public, the best way to achieve this is to make it
1685-free software which everyone can redistribute and change under these terms.
1686-
1687- To do so, attach the following notices to the program. It is safest
1688-to attach them to the start of each source file to most effectively
1689-state the exclusion of warranty; and each file should have at least
1690-the "copyright" line and a pointer to where the full notice is found.
1691-
1692- <one line to give the program's name and a brief idea of what it does.>
1693- Copyright (C) <year> <name of author>
1694-
1695- This program is free software: you can redistribute it and/or modify
1696- it under the terms of the GNU Affero General Public License as published by
1697- the Free Software Foundation, either version 3 of the License, or
1698- (at your option) any later version.
1699-
1700- This program is distributed in the hope that it will be useful,
1701- but WITHOUT ANY WARRANTY; without even the implied warranty of
1702- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1703- GNU Affero General Public License for more details.
1704-
1705- You should have received a copy of the GNU Affero General Public License
1706- along with this program. If not, see <http://www.gnu.org/licenses/>.
1707-
1708-Also add information on how to contact you by electronic and paper mail.
1709-
1710- If your software can interact with users remotely through a computer
1711-network, you should also make sure that it provides a way for users to
1712-get its source. For example, if your program is a web application, its
1713-interface could display a "Source" link that leads users to an archive
1714-of the code. There are many ways you could offer source, and different
1715-solutions will be better for different programs; see section 13 for the
1716-specific requirements.
1717-
1718- You should also get your employer (if you work as a programmer) or school,
1719-if any, to sign a "copyright disclaimer" for the program, if necessary.
1720-For more information on this, and how to apply and follow the GNU AGPL, see
1721-<http://www.gnu.org/licenses/>.
1722
1723=== added file 'Makefile'
1724--- Makefile 1970-01-01 00:00:00 +0000
1725+++ Makefile 2013-04-22 11:00:38 +0000
1726@@ -0,0 +1,54 @@
1727+PEP8=pep8
1728+COVERAGE_FILES=`find juju -name "*py" | grep -v "tests\|lib/mocker.py\|lib/testing.py"`
1729+
1730+all:
1731+ @echo "You've just watched the fastest build on earth."
1732+
1733+tests:
1734+ ./test
1735+
1736+coverage:
1737+ python -c "import coverage as c; c.main()" run ./test
1738+ python -c "import coverage as c; c.main()" html -d htmlcov $(COVERAGE_FILES)
1739+ gnome-open htmlcov/index.html
1740+
1741+ftests:
1742+ ./test --functional
1743+
1744+tags:
1745+ @ctags --python-kinds=-iv -R juju
1746+
1747+etags:
1748+ @ctags -e --python-kinds=-iv -R juju
1749+
1750+
1751+present_pep8=$(shell which $(PEP8))
1752+present_pyflakes=$(shell which pyflakes)
1753+warn_missing_linters:
1754+ @test -n "$(present_pep8)" || echo "WARNING: $(PEP8) not installed."
1755+ @test -n "$(present_pyflakes)" || echo "WARNING: pyflakes not installed."
1756+
1757+
1758+# "check": Check uncommitted changes for lint.
1759+check_changes=$(shell bzr status -S | grep '^[ +]*[MN]' | awk '{print $$2;}' | grep "\\.py$$")
1760+check: warn_missing_linters
1761+ @test -z $(present_pep8) || (echo $(check_changes) | xargs -r $(PEP8) --repeat)
1762+ @test -z $(present_pyflakes) || (echo $(check_changes) | xargs -r pyflakes)
1763+
1764+
1765+# "review": Check all changes compared to trunk for lint.
1766+review_changes=$(shell bzr status -S -r ancestor:$(JUJU_TRUNK) | grep '^[ +]*[MN]' | awk '{print $$2;}' | grep "\\.py$$")
1767+review: warn_missing_linters
1768+ #@test -z $(present_pep8) || (echo $(review_changes) | xargs -r $(PEP8) --repeat)
1769+ @test -z $(present_pyflakes) || (echo $(review_changes) | xargs -r pyflakes)
1770+
1771+
1772+ptests_changes=$(shell bzr status -S -r branch::prev | grep -P '^[ +]*[MN]' | awk '{print $$2;}'| grep "test_.*\\.py$$")
1773+ptests:
1774+ @echo $(ptests_changes) | xargs -r ./test
1775+
1776+btests_changes=$(shell bzr status -S -r ancestor:$(JUJU_TRUNK)/ | grep "test.*\\.py$$" | awk '{print $$2;}')
1777+btests:
1778+ @./test $(btests_changes)
1779+
1780+.PHONY: tags check review warn_missing_linters
1781
1782=== removed file 'Makefile'
1783--- Makefile 2012-10-09 12:35:35 +0000
1784+++ Makefile 1970-01-01 00:00:00 +0000
1785@@ -1,44 +0,0 @@
1786-PEP8=pep8
1787-COVERAGE_FILES=`find juju -name "*py" | grep -v "tests\|lib/mocker.py\|lib/testing.py"`
1788-
1789-all:
1790- @echo "You've just watched the fastest build on earth."
1791-
1792-tests:
1793- ./test
1794-
1795-coverage:
1796- python -c "import coverage as c; c.main()" run ./test
1797- python -c "import coverage as c; c.main()" html -d htmlcov $(COVERAGE_FILES)
1798- gnome-open htmlcov/index.html
1799-
1800-ftests:
1801- ./test --functional
1802-
1803-tags:
1804- @ctags --python-kinds=-iv -R juju
1805-
1806-etags:
1807- @ctags -e --python-kinds=-iv -R juju
1808-
1809-modified=$(shell bzr status -S |grep -P '^\s*M' | awk '{print $$2;}'| grep -P ".py$$")
1810-check:
1811- @test -n "$(modified)" && echo $(modified) | xargs $(PEP8) --repeat
1812- @test -n "$(modified)" && echo $(modified) | xargs pyflakes
1813-
1814-
1815-modified=$(shell bzr status -S -r ancestor:$(JUJU_TRUNK) |grep -P '^\s*M' | awk '{print $$2;}'| grep -P ".py$$")
1816-review:
1817- #@test -n "$(modified)" && echo $(modified) | xargs $(PEP8) --repeat
1818- @test -n "$(modified)" && echo $(modified) | xargs pyflakes
1819-
1820-
1821-modified=$(shell bzr status -S -r branch::prev |grep -P '^\s*\+?[MN]' | awk '{print $$2;}'| grep -P "test_.*\.py$$")
1822-ptests:
1823- @test -n "$(modified)" && echo $(modified) | xargs ./test
1824-
1825-modified=$(shell bzr status -S -r ancestor:$(JUJU_TRUNK)/|grep -P 'test.*\.py' |awk '{print $$2;}')
1826-btests:
1827- @./test $(modified)
1828-
1829-.PHONY: tags check review
1830
1831=== added file 'README'
1832--- README 1970-01-01 00:00:00 +0000
1833+++ README 2013-04-22 11:00:38 +0000
1834@@ -0,0 +1,34 @@
1835+juju
1836+====
1837+
1838+Welcome to juju, we hope you enjoy your stay.
1839+
1840+You can always get the latest juju code by running::
1841+
1842+ $ bzr branch lp:juju
1843+
1844+The juju bug tracker is at https://bugs.launchpad.net/juju
1845+
1846+Documentation for getting setup and running juju can be found at
1847+http://juju.ubuntu.com/docs
1848+
1849+====
1850+Juju Developers <juju@lists.ubuntu.com>
1851+
1852+Except where stated otherwise, the files contained within this source
1853+code tree are covered under the following copyright and license:
1854+
1855+Copyright 2010, 2011 Canonical Ltd. All Rights Reserved.
1856+
1857+This program is free software: you can redistribute it and/or modify
1858+it under the terms of the GNU Affero General Public License as published by
1859+the Free Software Foundation, either version 3 of the License, or
1860+(at your option) any later version.
1861+
1862+This package is distributed in the hope that it will be useful,
1863+but WITHOUT ANY WARRANTY; without even the implied warranty of
1864+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1865+GNU General Public License for more details.
1866+
1867+You should have received a copy of the GNU Affero General Public License
1868+along with this program. If not, see <http://www.gnu.org/licenses/>.
1869
1870=== removed file 'README'
1871--- README 2012-07-19 11:01:09 +0000
1872+++ README 1970-01-01 00:00:00 +0000
1873@@ -1,34 +0,0 @@
1874-juju
1875-====
1876-
1877-Welcome to juju, we hope you enjoy your stay.
1878-
1879-You can always get the latest juju code by running::
1880-
1881- $ bzr branch lp:juju
1882-
1883-The juju bug tracker is at https://bugs.launchpad.net/juju
1884-
1885-Documentation for getting setup and running juju can be found at
1886-http://juju.ubuntu.com/docs
1887-
1888-====
1889-Juju Developers <juju@lists.ubuntu.com>
1890-
1891-Except where stated otherwise, the files contained within this source
1892-code tree are covered under the following copyright and license:
1893-
1894-Copyright 2010, 2011 Canonical Ltd. All Rights Reserved.
1895-
1896-This program is free software: you can redistribute it and/or modify
1897-it under the terms of the GNU Affero General Public License as published by
1898-the Free Software Foundation, either version 3 of the License, or
1899-(at your option) any later version.
1900-
1901-This package is distributed in the hope that it will be useful,
1902-but WITHOUT ANY WARRANTY; without even the implied warranty of
1903-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1904-GNU General Public License for more details.
1905-
1906-You should have received a copy of the GNU Affero General Public License
1907-along with this program. If not, see <http://www.gnu.org/licenses/>.
1908
1909=== added directory 'bin'
1910=== removed directory 'bin'
1911=== added file 'bin/close-port'
1912--- bin/close-port 1970-01-01 00:00:00 +0000
1913+++ bin/close-port 2013-04-22 11:00:38 +0000
1914@@ -0,0 +1,11 @@
1915+#!/usr/bin/env python
1916+
1917+# We avoid using PYTHONPATH because it can cause side effects on hook execution
1918+import os, sys
1919+if "JUJU_PYTHONPATH" in os.environ:
1920+ sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
1921+
1922+from juju.hooks.commands import close_port
1923+
1924+if __name__ == '__main__':
1925+ close_port()
1926
1927=== removed file 'bin/close-port'
1928--- bin/close-port 2012-06-11 15:40:48 +0000
1929+++ bin/close-port 1970-01-01 00:00:00 +0000
1930@@ -1,11 +0,0 @@
1931-#!/usr/bin/env python
1932-
1933-# We avoid using PYTHONPATH because it can cause side effects on hook execution
1934-import os, sys
1935-if "JUJU_PYTHONPATH" in os.environ:
1936- sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
1937-
1938-from juju.hooks.commands import close_port
1939-
1940-if __name__ == '__main__':
1941- close_port()
1942
1943=== added file 'bin/config-get'
1944--- bin/config-get 1970-01-01 00:00:00 +0000
1945+++ bin/config-get 2013-04-22 11:00:38 +0000
1946@@ -0,0 +1,12 @@
1947+#!/usr/bin/env python
1948+
1949+# We avoid using PYTHONPATH because it can cause side effects on hook execution
1950+import os, sys
1951+
1952+if "JUJU_PYTHONPATH" in os.environ:
1953+ sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
1954+
1955+from juju.hooks.commands import config_get
1956+
1957+if __name__ == '__main__':
1958+ config_get()
1959
1960=== removed file 'bin/config-get'
1961--- bin/config-get 2012-06-11 15:40:48 +0000
1962+++ bin/config-get 1970-01-01 00:00:00 +0000
1963@@ -1,12 +0,0 @@
1964-#!/usr/bin/env python
1965-
1966-# We avoid using PYTHONPATH because it can cause side effects on hook execution
1967-import os, sys
1968-
1969-if "JUJU_PYTHONPATH" in os.environ:
1970- sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
1971-
1972-from juju.hooks.commands import config_get
1973-
1974-if __name__ == '__main__':
1975- config_get()
1976
1977=== added file 'bin/juju'
1978--- bin/juju 1970-01-01 00:00:00 +0000
1979+++ bin/juju 2013-04-22 11:00:38 +0000
1980@@ -0,0 +1,10 @@
1981+#!/usr/bin/env python
1982+import sys
1983+
1984+from juju.control import main
1985+from juju.errors import JujuError
1986+
1987+try:
1988+ main(sys.argv[1:])
1989+except JujuError, error:
1990+ sys.exit("error: %s" % (error,))
1991
1992=== removed file 'bin/juju'
1993--- bin/juju 2012-06-11 15:40:48 +0000
1994+++ bin/juju 1970-01-01 00:00:00 +0000
1995@@ -1,10 +0,0 @@
1996-#!/usr/bin/env python
1997-import sys
1998-
1999-from juju.control import main
2000-from juju.errors import JujuError
2001-
2002-try:
2003- main(sys.argv[1:])
2004-except JujuError, error:
2005- sys.exit("error: %s" % (error,))
2006
2007=== added file 'bin/juju-admin'
2008--- bin/juju-admin 1970-01-01 00:00:00 +0000
2009+++ bin/juju-admin 2013-04-22 11:00:38 +0000
2010@@ -0,0 +1,10 @@
2011+#!/usr/bin/env python
2012+import sys
2013+
2014+from juju.control import admin
2015+from juju.errors import JujuError
2016+
2017+try:
2018+ admin(sys.argv[1:])
2019+except JujuError, error:
2020+ sys.exit("error: %s" % (error,))
2021
2022=== removed file 'bin/juju-admin'
2023--- bin/juju-admin 2012-06-11 15:40:48 +0000
2024+++ bin/juju-admin 1970-01-01 00:00:00 +0000
2025@@ -1,10 +0,0 @@
2026-#!/usr/bin/env python
2027-import sys
2028-
2029-from juju.control import admin
2030-from juju.errors import JujuError
2031-
2032-try:
2033- admin(sys.argv[1:])
2034-except JujuError, error:
2035- sys.exit("error: %s" % (error,))
2036
2037=== added file 'bin/juju-log'
2038--- bin/juju-log 1970-01-01 00:00:00 +0000
2039+++ bin/juju-log 2013-04-22 11:00:38 +0000
2040@@ -0,0 +1,12 @@
2041+#!/usr/bin/env python
2042+
2043+# We avoid using PYTHONPATH because it can cause side effects on hook execution
2044+
2045+import os, sys
2046+if "JUJU_PYTHONPATH" in os.environ:
2047+ sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2048+
2049+from juju.hooks.commands import log
2050+
2051+if __name__ == '__main__':
2052+ log()
2053
2054=== removed file 'bin/juju-log'
2055--- bin/juju-log 2012-06-11 15:40:48 +0000
2056+++ bin/juju-log 1970-01-01 00:00:00 +0000
2057@@ -1,12 +0,0 @@
2058-#!/usr/bin/env python
2059-
2060-# We avoid using PYTHONPATH because it can cause side effects on hook execution
2061-
2062-import os, sys
2063-if "JUJU_PYTHONPATH" in os.environ:
2064- sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2065-
2066-from juju.hooks.commands import log
2067-
2068-if __name__ == '__main__':
2069- log()
2070
2071=== added file 'bin/open-port'
2072--- bin/open-port 1970-01-01 00:00:00 +0000
2073+++ bin/open-port 2013-04-22 11:00:38 +0000
2074@@ -0,0 +1,11 @@
2075+#!/usr/bin/env python
2076+
2077+# We avoid using PYTHONPATH because it can cause side effects on hook execution
2078+import os, sys
2079+if "JUJU_PYTHONPATH" in os.environ:
2080+ sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2081+
2082+from juju.hooks.commands import open_port
2083+
2084+if __name__ == '__main__':
2085+ open_port()
2086
2087=== removed file 'bin/open-port'
2088--- bin/open-port 2012-06-11 15:40:48 +0000
2089+++ bin/open-port 1970-01-01 00:00:00 +0000
2090@@ -1,11 +0,0 @@
2091-#!/usr/bin/env python
2092-
2093-# We avoid using PYTHONPATH because it can cause side effects on hook execution
2094-import os, sys
2095-if "JUJU_PYTHONPATH" in os.environ:
2096- sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2097-
2098-from juju.hooks.commands import open_port
2099-
2100-if __name__ == '__main__':
2101- open_port()
2102
2103=== added file 'bin/relation-get'
2104--- bin/relation-get 1970-01-01 00:00:00 +0000
2105+++ bin/relation-get 2013-04-22 11:00:38 +0000
2106@@ -0,0 +1,12 @@
2107+#!/usr/bin/env python
2108+
2109+# We avoid using PYTHONPATH because it can cause side effects on hook execution
2110+import os, sys
2111+
2112+if "JUJU_PYTHONPATH" in os.environ:
2113+ sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2114+
2115+from juju.hooks.commands import relation_get
2116+
2117+if __name__ == '__main__':
2118+ relation_get()
2119
2120=== removed file 'bin/relation-get'
2121--- bin/relation-get 2012-06-11 15:40:48 +0000
2122+++ bin/relation-get 1970-01-01 00:00:00 +0000
2123@@ -1,12 +0,0 @@
2124-#!/usr/bin/env python
2125-
2126-# We avoid using PYTHONPATH because it can cause side effects on hook execution
2127-import os, sys
2128-
2129-if "JUJU_PYTHONPATH" in os.environ:
2130- sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2131-
2132-from juju.hooks.commands import relation_get
2133-
2134-if __name__ == '__main__':
2135- relation_get()
2136
2137=== added file 'bin/relation-ids'
2138--- bin/relation-ids 1970-01-01 00:00:00 +0000
2139+++ bin/relation-ids 2013-04-22 11:00:38 +0000
2140@@ -0,0 +1,11 @@
2141+#!/usr/bin/env python
2142+
2143+# We avoid using PYTHONPATH because it can cause side effects on hook execution
2144+import os, sys
2145+if "JUJU_PYTHONPATH" in os.environ:
2146+ sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2147+
2148+from juju.hooks.commands import relation_ids
2149+
2150+if __name__ == '__main__':
2151+ relation_ids()
2152
2153=== removed file 'bin/relation-ids'
2154--- bin/relation-ids 2012-06-17 15:39:48 +0000
2155+++ bin/relation-ids 1970-01-01 00:00:00 +0000
2156@@ -1,11 +0,0 @@
2157-#!/usr/bin/env python
2158-
2159-# We avoid using PYTHONPATH because it can cause side effects on hook execution
2160-import os, sys
2161-if "JUJU_PYTHONPATH" in os.environ:
2162- sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2163-
2164-from juju.hooks.commands import relation_ids
2165-
2166-if __name__ == '__main__':
2167- relation_ids()
2168
2169=== added file 'bin/relation-list'
2170--- bin/relation-list 1970-01-01 00:00:00 +0000
2171+++ bin/relation-list 2013-04-22 11:00:38 +0000
2172@@ -0,0 +1,13 @@
2173+#!/usr/bin/env python
2174+
2175+
2176+# We avoid using PYTHONPATH because it can cause side effects on hook execution
2177+import os, sys
2178+
2179+if "JUJU_PYTHONPATH" in os.environ:
2180+ sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2181+
2182+from juju.hooks.commands import relation_list
2183+
2184+if __name__ == '__main__':
2185+ relation_list()
2186
2187=== removed file 'bin/relation-list'
2188--- bin/relation-list 2012-06-11 15:40:48 +0000
2189+++ bin/relation-list 1970-01-01 00:00:00 +0000
2190@@ -1,13 +0,0 @@
2191-#!/usr/bin/env python
2192-
2193-
2194-# We avoid using PYTHONPATH because it can cause side effects on hook execution
2195-import os, sys
2196-
2197-if "JUJU_PYTHONPATH" in os.environ:
2198- sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2199-
2200-from juju.hooks.commands import relation_list
2201-
2202-if __name__ == '__main__':
2203- relation_list()
2204
2205=== added file 'bin/relation-set'
2206--- bin/relation-set 1970-01-01 00:00:00 +0000
2207+++ bin/relation-set 2013-04-22 11:00:38 +0000
2208@@ -0,0 +1,13 @@
2209+#!/usr/bin/env python
2210+
2211+
2212+# We avoid using PYTHONPATH because it can cause side effects on hook execution
2213+import os, sys
2214+
2215+if "JUJU_PYTHONPATH" in os.environ:
2216+ sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2217+
2218+from juju.hooks.commands import relation_set
2219+
2220+if __name__ == '__main__':
2221+ relation_set()
2222
2223=== removed file 'bin/relation-set'
2224--- bin/relation-set 2012-06-11 15:40:48 +0000
2225+++ bin/relation-set 1970-01-01 00:00:00 +0000
2226@@ -1,13 +0,0 @@
2227-#!/usr/bin/env python
2228-
2229-
2230-# We avoid using PYTHONPATH because it can cause side effects on hook execution
2231-import os, sys
2232-
2233-if "JUJU_PYTHONPATH" in os.environ:
2234- sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2235-
2236-from juju.hooks.commands import relation_set
2237-
2238-if __name__ == '__main__':
2239- relation_set()
2240
2241=== added file 'bin/unit-get'
2242--- bin/unit-get 1970-01-01 00:00:00 +0000
2243+++ bin/unit-get 2013-04-22 11:00:38 +0000
2244@@ -0,0 +1,12 @@
2245+#!/usr/bin/env python
2246+
2247+# We avoid using PYTHONPATH because it can cause side effects on hook execution
2248+import os, sys
2249+
2250+if "JUJU_PYTHONPATH" in os.environ:
2251+ sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2252+
2253+from juju.hooks.commands import unit_get
2254+
2255+if __name__ == '__main__':
2256+ unit_get()
2257
2258=== removed file 'bin/unit-get'
2259--- bin/unit-get 2012-06-11 15:40:48 +0000
2260+++ bin/unit-get 1970-01-01 00:00:00 +0000
2261@@ -1,12 +0,0 @@
2262-#!/usr/bin/env python
2263-
2264-# We avoid using PYTHONPATH because it can cause side effects on hook execution
2265-import os, sys
2266-
2267-if "JUJU_PYTHONPATH" in os.environ:
2268- sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
2269-
2270-from juju.hooks.commands import unit_get
2271-
2272-if __name__ == '__main__':
2273- unit_get()
2274
2275=== modified file 'debian/changelog'
2276--- debian/changelog 2012-12-03 15:59:21 +0000
2277+++ debian/changelog 2013-04-22 11:00:38 +0000
2278@@ -1,10 +1,21 @@
2279-juju (0.6-1ubuntu2) UNRELEASED; urgency=low
2280+juju (0.7-0ubuntu1) raring; urgency=low
2281
2282+ [ Clint Byrum ]
2283 * d/p/fix-tests-do-not-use-etc-lsb-release: Dropped, patch applied upstream.
2284 * d/p/maas-tag-conversion.patch: Dropped, patch applied upstream.
2285 * d/juju.docs: Remove examples as they have been dropped upstream.
2286
2287- -- Clint Byrum <clint@ubuntu.com> Tue, 06 Nov 2012 12:14:23 -0800
2288+ [ Mark Mims ]
2289+ * Add postinst and prerm scripts using update-alternatives to install into
2290+ a versioned location, enabling co-installability with the go juju port.
2291+
2292+ [ Martin Packman ]
2293+ * New upstream release. (lp: #1167921)
2294+ * d/p/workaround-lxc-python-env.patch: Workaround regression with local
2295+ provider failing with SyntaxError on running lxc scripts when user has
2296+ Python 3 specific environment variables set. (lp: #1130809)
2297+
2298+ -- Martin Packman <martin.packman@canonical.com> Wed, 10 Apr 2013 13:57:21 +0000
2299
2300 juju (0.6-1ubuntu1) quantal; urgency=low
2301
2302
2303=== modified file 'debian/control'
2304--- debian/control 2012-10-10 14:41:08 +0000
2305+++ debian/control 2013-04-22 11:00:38 +0000
2306@@ -17,11 +17,13 @@
2307 lsb-release,
2308 procps
2309 Homepage: https://launchpad.net/juju
2310-Standards-Version: 3.9.3
2311+Standards-Version: 3.9.4
2312
2313-Package: juju
2314+Package: juju-0.7
2315 Architecture: all
2316 Depends: ${python:Depends}, ${misc:Depends}, python-twisted, python-txzookeeper (>= 0.9.5~), python-txaws, python-yaml, openssh-client, tmux, python-oauth, procps
2317+Breaks: juju (<< 0.7-0ubuntu1~)
2318+Replaces: juju (<< 0.7-0ubuntu1~)
2319 Recommends: python-pydot, byobu
2320 Suggests: apt-cacher-ng, lxc, zookeeper
2321 Provides: ${python:Provides}, ${misc:Depends}
2322@@ -32,3 +34,10 @@
2323 create service formulas, called charms, independently, and make those
2324 services coordinate their communication and configuration through a
2325 simple protocol.
2326+
2327+Package: juju
2328+Architecture: all
2329+Depends: ${misc:Depends}, juju-0.7
2330+Description: next generation service orchestration system
2331+ Virtual dependency package providing Juju, a next generation service
2332+ orchestration framework.
2333
2334=== removed file 'debian/juju.install'
2335--- debian/juju.install 2012-06-11 15:40:48 +0000
2336+++ debian/juju.install 1970-01-01 00:00:00 +0000
2337@@ -1,1 +0,0 @@
2338-misc/bash_completion.d/juju /etc/bash_completion.d/
2339
2340=== removed file 'debian/juju.manpages'
2341--- debian/juju.manpages 2012-06-11 15:40:48 +0000
2342+++ debian/juju.manpages 1970-01-01 00:00:00 +0000
2343@@ -1,1 +0,0 @@
2344-debian/manpages/*.1
2345
2346=== added file 'debian/juju.postinst.in'
2347--- debian/juju.postinst.in 1970-01-01 00:00:00 +0000
2348+++ debian/juju.postinst.in 2013-04-22 11:00:38 +0000
2349@@ -0,0 +1,43 @@
2350+#!/bin/sh
2351+set -e
2352+
2353+VER="__NEW_VERSION__"
2354+base_dir="/usr/lib"
2355+
2356+case "$1" in
2357+ configure)
2358+ update-alternatives --force --install /usr/bin/juju juju $base_dir/juju-$VER/bin/juju 20 \
2359+ --slave /usr/share/man/man1/juju.1.gz juju.1.gz $base_dir/juju-$VER/man/man1/juju.1.gz \
2360+ --slave /usr/bin/open-port open-port $base_dir/juju-$VER/bin/open-port \
2361+ --slave /usr/share/man/man1/open-port.1.gz open-port.1.gz $base_dir/juju-$VER/man/man1/open-port.1.gz \
2362+ --slave /usr/bin/close-port close-port $base_dir/juju-$VER/bin/close-port \
2363+ --slave /usr/share/man/man1/close-port.1.gz close-port.1.gz $base_dir/juju-$VER/man/man1/close-port.1.gz \
2364+ --slave /usr/bin/config-get config-get $base_dir/juju-$VER/bin/config-get \
2365+ --slave /usr/share/man/man1/config-get.1.gz config-get.1.gz $base_dir/juju-$VER/man/man1/config-get.1.gz \
2366+ --slave /usr/bin/relation-list relation-list $base_dir/juju-$VER/bin/relation-list \
2367+ --slave /usr/share/man/man1/relation-list.1.gz relation-list.1.gz $base_dir/juju-$VER/man/man1/relation-list.1.gz \
2368+ --slave /usr/bin/unit-get unit-get $base_dir/juju-$VER/bin/unit-get \
2369+ --slave /usr/share/man/man1/unit-get.1.gz unit-get.1.gz $base_dir/juju-$VER/man/man1/unit-get.1.gz \
2370+ --slave /usr/bin/juju-admin juju-admin $base_dir/juju-$VER/bin/juju-admin \
2371+ --slave /usr/share/man/man1/juju-admin.1.gz juju-admin.1.gz $base_dir/juju-$VER/man/man1/juju-admin.1.gz \
2372+ --slave /usr/bin/relation-ids relation-ids $base_dir/juju-$VER/bin/relation-ids \
2373+ --slave /usr/share/man/man1/relation-ids.1.gz relation-ids.1.gz $base_dir/juju-$VER/man/man1/relation-ids.1.gz \
2374+ --slave /usr/bin/juju-log juju-log $base_dir/juju-$VER/bin/juju-log \
2375+ --slave /usr/share/man/man1/juju-log.1.gz juju-log.1.gz $base_dir/juju-$VER/man/man1/juju-log.1.gz \
2376+ --slave /usr/bin/relation-get relation-get $base_dir/juju-$VER/bin/relation-get \
2377+ --slave /usr/share/man/man1/relation-get.1.gz relation-get.1.gz $base_dir/juju-$VER/man/man1/relation-get.1.gz \
2378+ --slave /usr/bin/relation-set relation-set $base_dir/juju-$VER/bin/relation-set \
2379+ --slave /usr/share/man/man1/relation-set.1.gz relation-set.1.gz $base_dir/juju-$VER/man/man1/relation-set.1.gz \
2380+ --slave /etc/bash_completion.d/juju juju-bash-completion $base_dir/juju-$VER/etc/bash_completion.d/juju
2381+ ;;
2382+ abort-upgrade|abort-remove|abort-deconfigure)
2383+ echo "$1"
2384+ ;;
2385+ *) echo "$0: didn't understand being called with \`$1'" 1>&2
2386+ exit 0
2387+ ;;
2388+esac
2389+
2390+#DEBHELPER#
2391+
2392+exit 0
2393
2394=== added file 'debian/juju.prerm.in'
2395--- debian/juju.prerm.in 1970-01-01 00:00:00 +0000
2396+++ debian/juju.prerm.in 2013-04-22 11:00:38 +0000
2397@@ -0,0 +1,23 @@
2398+#!/bin/sh
2399+set -e
2400+
2401+VER="__NEW_VERSION__"
2402+
2403+case "$1" in
2404+ remove|upgrade|deconfigure)
2405+ update-alternatives --remove juju /usr/lib/juju-$VER/bin/juju
2406+ ;;
2407+
2408+ failed-upgrade)
2409+ echo "$1"
2410+ ;;
2411+
2412+ *)
2413+ echo "prerm called with unknown argument \`$1'" >&2
2414+ exit 0
2415+ ;;
2416+esac
2417+
2418+#DEBHELPER#
2419+
2420+exit 0
2421
2422=== modified file 'debian/patches/series'
2423--- debian/patches/series 2012-11-06 20:44:41 +0000
2424+++ debian/patches/series 2013-04-22 11:00:38 +0000
2425@@ -1,1 +1,2 @@
2426 disable-failing-zookeeper-test.patch
2427+workaround-lxc-python-env.patch
2428
2429=== added file 'debian/patches/workaround-lxc-python-env.patch'
2430--- debian/patches/workaround-lxc-python-env.patch 1970-01-01 00:00:00 +0000
2431+++ debian/patches/workaround-lxc-python-env.patch 2013-04-22 11:00:38 +0000
2432@@ -0,0 +1,26 @@
2433+From: Martin Packman <martin.packman@canonical.com>
2434+Description: The various lxc scripts installed in /usr/bin are Python 3 only.
2435+ If the user has PYTHONPATH or other similar environment variables set, these
2436+ scripts can incorrectly inherit details from the user's Python 2 setup when
2437+ run. Workaround the problem by sanitising the environment before calling them.
2438+Forwarded: N/A
2439+
2440+Index: juju/juju/lib/lxc/__init__.py
2441+===================================================================
2442+--- juju.orig/juju/lib/lxc/__init__.py 2013-04-10 12:11:45.583012000 +0000
2443++++ juju/juju/lib/lxc/__init__.py 2013-04-10 12:12:29.543012000 +0000
2444+@@ -38,8 +38,13 @@
2445+
2446+
2447+ def _cmd(args):
2448++ # GZ 2013-04-10: Cleanup the user's python environment to avoid issues now
2449++ # the lxc scripts require Python 3, see lp:1130809.
2450++ env = dict((k, os.environ[k]) for k in os.environ
2451++ if not k.startswith("PYTHON"))
2452++ env["PYTHONNOUSERSITE"] = "1"
2453+ p = subprocess.Popen(
2454+- args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
2455++ args, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
2456+ stdout_data, _ = p.communicate()
2457+ r = p.returncode
2458+ if r != 0:
2459
2460=== modified file 'debian/rules'
2461--- debian/rules 2012-07-19 11:01:09 +0000
2462+++ debian/rules 2013-04-22 11:00:38 +0000
2463@@ -2,9 +2,14 @@
2464
2465 REV=$(shell dpkg-parsechangelog | sed -rne 's,^Version: .*[+~]bzr([0-9]+).*,\1,p')
2466 VER=$(shell dpkg-parsechangelog | sed -rne 's,^Version: ([^-]+).*,\1,p')
2467+MAJOR_VER=0.7
2468+ifeq "$(REV)" ""
2469+ REV=tag:$(VER)
2470+endif
2471+MANDIR=debian/manpages
2472
2473 %:
2474- dh $@ --buildsystem=python_distutils --with python2
2475+ dh $@ --with python2
2476
2477 override_dh_auto_test:
2478 ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
2479@@ -13,17 +18,16 @@
2480
2481 get-orig-source:
2482 bzr export -r $(REV) --root=juju-$(VER).orig \
2483- juju_$(VER).orig.tar.gz lp:juju
2484+ juju_$(VER).orig.tar.gz lp:juju/$(MAJOR_VER)
2485
2486 override_dh_auto_clean::
2487 rm -f debian/manpages/*.partial
2488 rm -f debian/manpages/*.1
2489
2490-override_dh_auto_build:: debian/manpages/juju.1
2491-
2492 HELP2MAN=PYTHONPATH=`pwd` help2man -N --version-string $(VER)
2493
2494-debian/manpages/juju.1:
2495+generate_juju_manpages:
2496+ mkdir -p $(MANDIR)
2497 $(HELP2MAN) --name "juju service orchestration system admin client" \
2498 --include debian/manpages/juju.includes.in bin/juju \
2499 > debian/manpages/juju.1.partial
2500@@ -34,7 +38,31 @@
2501 $(HELP2MAN) --name "Juju charm API commands" \
2502 --include debian/manpages/charm_api.in \
2503 bin/$$i > debian/manpages/$$i.1.partial ;\
2504- mv debian/manpages/$$i.1.partial debian/manpages/$$i.1 ;\
2505+ mv debian/manpages/$$i.1.partial $(MANDIR)/$$i.1 ;\
2506 done
2507- mv -f debian/manpages/juju-admin.1.partial debian/manpages/juju-admin.1
2508- mv -f debian/manpages/juju.1.partial debian/manpages/juju.1
2509+ mv -f debian/manpages/juju-admin.1.partial $(MANDIR)/juju-admin.1
2510+ mv -f debian/manpages/juju.1.partial $(MANDIR)/juju.1
2511+
2512+override_dh_auto_build: generate_juju_manpages
2513+
2514+install_juju_manpages:
2515+ mkdir -p debian/juju-$(MAJOR_VER)/usr/lib/juju-$(VER)/man/man1
2516+ cp -a debian/manpages/*.1 debian/juju-$(MAJOR_VER)/usr/lib/juju-$(VER)/man/man1/
2517+ gzip debian/juju-$(MAJOR_VER)/usr/lib/juju-$(VER)/man/man1/*.1
2518+
2519+install_bash_completion:
2520+ mkdir -p debian/juju-$(MAJOR_VER)/usr/lib/juju-$(VER)/etc/bash_completion.d
2521+ cp -a misc/bash_completion.d/juju debian/juju-$(MAJOR_VER)/usr/lib/juju-$(VER)/etc/bash_completion.d/juju
2522+
2523+override_dh_auto_install: install_juju_manpages install_bash_completion
2524+ set -e && python setup.py install --root=debian/juju-$(MAJOR_VER) --install-layout=deb --install-scripts=/usr/lib/juju-$(VER)/bin --compile
2525+
2526+debian/juju-$(VER).postinst: debian/juju.postinst.in
2527+ sed -e "s/__NEW_VERSION__/$(VER)/g" $< > $@
2528+
2529+debian/juju-$(VER).prerm: debian/juju.prerm.in
2530+ sed -e "s/__NEW_VERSION__/$(VER)/g" $< > $@
2531+
2532+override_dh_installdeb: debian/juju-$(VER).postinst debian/juju-$(VER).prerm
2533+ dh_installdeb
2534+
2535
2536=== removed directory 'examples'
2537=== removed file 'examples/README'
2538--- examples/README 2012-06-11 15:40:48 +0000
2539+++ examples/README 1970-01-01 00:00:00 +0000
2540@@ -1,24 +0,0 @@
2541-Examples
2542-========
2543-
2544-
2545-These are some example charms that can be deployed together to create some connected services.
2546-
2547-Many more charms of greater functionality and utility exist in the Principia project, which
2548-currently serves as a clearinghouse for juju charms.
2549-
2550-http://launchpad.net/principia
2551-
2552-
2553-Deploying
2554-=========
2555-
2556-As an example of deploying these sample charms as services and connectinng them.
2557-
2558- $ juju bootstrap
2559- $ juju deploy --repository=examples local:mysql
2560- $ juju deploy --repository=examples local:wordpress
2561- $ juju add-relation wordpress mysql
2562- $ juju status
2563-
2564-The status command will show the address of the newly deployed wordpress service.
2565
2566=== removed directory 'examples/precise'
2567=== removed directory 'examples/precise/mysql'
2568=== removed file 'examples/precise/mysql/config.yaml'
2569--- examples/precise/mysql/config.yaml 2012-06-17 15:39:48 +0000
2570+++ examples/precise/mysql/config.yaml 1970-01-01 00:00:00 +0000
2571@@ -1,1 +0,0 @@
2572-options: {}
2573
2574=== removed directory 'examples/precise/mysql/hooks'
2575=== removed file 'examples/precise/mysql/hooks/db-relation-joined'
2576--- examples/precise/mysql/hooks/db-relation-joined 2012-06-17 15:39:48 +0000
2577+++ examples/precise/mysql/hooks/db-relation-joined 1970-01-01 00:00:00 +0000
2578@@ -1,40 +0,0 @@
2579-#!/bin/bash
2580-
2581-set -eu # -x for verbose logging to juju debug-log
2582-
2583-hostname=`unit-get private-address`
2584-
2585-# Get the mysql password that was generated by the install hook
2586-password=`cat /var/lib/juju/mysql.passwd`
2587-
2588-# Get the database name; this takes a service unit (ex: wordpress/0)
2589-# and extracts just 'wordpress'
2590-service=`echo $JUJU_REMOTE_UNIT | cut -d/ -f1` # split on /
2591-
2592-# Determine if a database needs to be created for this service
2593-existing_databases=`mysql --password="$password" --silent --execute 'show databases'`
2594-for db in $existing_databases; do
2595- if [ "$db" = "$service" ] ; then
2596- juju-log "Database already exists, publishing details and exiting"
2597- service_password=`cat /var/lib/juju/$service.passwd`
2598- # Save these settings on the relation; this will trigger the remote
2599- # service unit
2600- relation-set database="$service" user="$service" password="$service_password"
2601- exit 0 # database already exists
2602- fi
2603-done
2604-
2605-# Generate a strong password for the database, using /dev/urandom
2606-service_password=`pwgen 10 1`
2607-# Store service password, new service units of same service would need it
2608-echo $service_password >> /var/lib/juju/$service.passwd
2609-
2610-# Create new database and corresponding security settings
2611-juju-log "Creating new database and corresponding security settings"
2612-mysqladmin --password="$password" create "$service"
2613-echo "grant all on $service.* to $service identified by '$service_password'" | mysql --password="$password" --database="$service"
2614-mysqladmin --password="$password" flush-privileges
2615-
2616-# Save these settings on the relation; this will trigger the remote
2617-# service unit
2618-relation-set database="$service" user="$service" password="$service_password"
2619
2620=== removed file 'examples/precise/mysql/hooks/install'
2621--- examples/precise/mysql/hooks/install 2012-08-24 16:02:42 +0000
2622+++ examples/precise/mysql/hooks/install 1970-01-01 00:00:00 +0000
2623@@ -1,32 +0,0 @@
2624-#!/bin/bash
2625-
2626-set -eu # -x for verbose logging to juju debug-log
2627-
2628-apt-get install -qqy debconf-utils python-mysqldb pwgen
2629-
2630-# Generate a strong password for the mysql service, using /dev/urandom
2631-PASSWORD=`pwgen 10 1`
2632-
2633-# Store the password for later use by the db-relation-changed hook for
2634-# this service unit. As a general note, for data that service units do
2635-# not need to share, simply use the machine's local file store.
2636-PASSFILE=/var/lib/juju/mysql.passwd
2637-if ! [ -f $PASSFILE ] ; then
2638- touch $PASSFILE
2639-fi
2640-chmod 0600 $PASSFILE
2641-if ! [ -s $PASSFILE ] ; then
2642- echo $PASSWORD >> /var/lib/juju/mysql.passwd
2643-fi
2644-
2645-echo mysql-server-5.1 mysql-server/root_password password $PASSWORD | debconf-set-selections
2646-echo mysql-server-5.1 mysql-server/root_password_again password $PASSWORD | debconf-set-selections
2647-
2648-juju-log "mysql-server settings preseeded, now installing via apt-get"
2649-DEBIAN_FRONTEND=noninteractive apt-get -y install -qq mysql-server
2650-
2651-juju-log "Editing my.cnf to allow listening on all interfaces"
2652-sed --in-place=old 's/127\.0\.0\.1/0.0.0.0/' /etc/mysql/my.cnf
2653-
2654-juju-log "Stopping mysql service"
2655-service mysql stop
2656
2657=== removed file 'examples/precise/mysql/hooks/start'
2658--- examples/precise/mysql/hooks/start 2012-06-17 15:39:48 +0000
2659+++ examples/precise/mysql/hooks/start 1970-01-01 00:00:00 +0000
2660@@ -1,3 +0,0 @@
2661-#!/bin/bash
2662-juju-log "Starting mysql service"
2663-service mysql start || service mysql restart
2664
2665=== removed file 'examples/precise/mysql/hooks/stop'
2666--- examples/precise/mysql/hooks/stop 2012-06-17 15:39:48 +0000
2667+++ examples/precise/mysql/hooks/stop 1970-01-01 00:00:00 +0000
2668@@ -1,3 +0,0 @@
2669-#!/bin/bash
2670-juju-log "Stopping mysql service"
2671-service mysql stop || true
2672
2673=== removed file 'examples/precise/mysql/metadata.yaml'
2674--- examples/precise/mysql/metadata.yaml 2012-06-17 15:39:48 +0000
2675+++ examples/precise/mysql/metadata.yaml 1970-01-01 00:00:00 +0000
2676@@ -1,17 +0,0 @@
2677-name: mysql
2678-summary: "MySQL relational database provider"
2679-description: |
2680- Installs and configures the MySQL package (mysqldb), then runs it.
2681-
2682- Upon a consuming service establishing a relation, creates a new
2683- database for that service, if the database does not yet
2684- exist. Publishes the following relation settings for consuming
2685- services:
2686-
2687- database: database name
2688- user: user name to access database
2689- password: password to access the database
2690- host: local hostname
2691-provides:
2692- db:
2693- interface: mysql
2694
2695=== removed file 'examples/precise/mysql/revision'
2696--- examples/precise/mysql/revision 2012-06-17 15:39:48 +0000
2697+++ examples/precise/mysql/revision 1970-01-01 00:00:00 +0000
2698@@ -1,1 +0,0 @@
2699-11
2700
2701=== removed directory 'examples/precise/php'
2702=== removed file 'examples/precise/php/config.yaml'
2703--- examples/precise/php/config.yaml 2012-06-17 15:39:48 +0000
2704+++ examples/precise/php/config.yaml 1970-01-01 00:00:00 +0000
2705@@ -1,5 +0,0 @@
2706-options:
2707- application_file:
2708- description: An application file to push.
2709- type: string
2710-
2711
2712=== removed directory 'examples/precise/php/hooks'
2713=== removed file 'examples/precise/php/hooks/config-changed'
2714--- examples/precise/php/hooks/config-changed 2012-06-17 15:39:48 +0000
2715+++ examples/precise/php/hooks/config-changed 1970-01-01 00:00:00 +0000
2716@@ -1,14 +0,0 @@
2717-#!/bin/bash
2718-
2719-hostname=`unit-get public-address`
2720-
2721-app_dir="/var/www/$hostname/"
2722-app_file=`config-get application_file`
2723-
2724-if [ -z $app_file ]; then
2725- exit 0
2726-fi
2727-
2728-echo "$app_file" > $app_dir/index.php
2729-chmod a+r $app_dir/index.php
2730-
2731
2732=== removed file 'examples/precise/php/hooks/install'
2733--- examples/precise/php/hooks/install 2012-06-17 15:39:48 +0000
2734+++ examples/precise/php/hooks/install 1970-01-01 00:00:00 +0000
2735@@ -1,49 +0,0 @@
2736-#!/bin/bash
2737-
2738-juju-log "Installing php5 via apt-get"
2739-set -eu # -x for verbose logging to juju debug-log
2740-
2741-apt-get -y install php5
2742-
2743-# Create an internal secret key for wordpress; this is unrelated to
2744-# the password generated for the admin user of wordpress
2745-hostname=`unit-get public-address`
2746-
2747-SITE_PATH="/var/www/$hostname"
2748-
2749-
2750-juju-log "Creating appropriate upload paths and directories"
2751-# Setup appropriate upload paths and directories
2752-mkdir -p $SITE_PATH
2753-chown -R root:www-data $SITE_PATH
2754-chmod -R a+rw $SITE_PATH
2755-chmod a+x $SITE_PATH
2756-
2757-# Write the apache config
2758-# XXX a future branch will change this to use augtool
2759-apache_config_file_path="/etc/apache2/sites-available/$hostname"
2760-juju-log "Writing apache config file $apache_config_file_path"
2761-cat > $apache_config_file_path <<EOF
2762-<VirtualHost *:80>
2763- ServerName $hostname
2764- DocumentRoot /var/www/$hostname
2765- Options All
2766- ErrorLog /var/log/apache2/$hostname-error.log
2767- TransferLog /var/log/apache2/$hostname-access.log
2768- <FilesMatch \.php$>
2769- SetHandler application/x-httpd-php
2770- </FilesMatch>
2771-</VirtualHost>
2772-EOF
2773-chmod 0644 $apache_config_file_path
2774-
2775-# Configure apache
2776-juju-log "Enabling apache modules: rewrite, vhost_alias"
2777-a2enmod rewrite
2778-a2enmod vhost_alias
2779-juju-log "Enabling apache site: $hostname"
2780-a2ensite $hostname
2781-
2782-# Restart apache
2783-juju-log "Restarting apache2 service"
2784-/etc/init.d/apache2 restart
2785
2786=== removed file 'examples/precise/php/hooks/start'
2787--- examples/precise/php/hooks/start 2012-06-17 15:39:48 +0000
2788+++ examples/precise/php/hooks/start 1970-01-01 00:00:00 +0000
2789@@ -1,1 +0,0 @@
2790-#!/bin/bash
2791
2792=== removed file 'examples/precise/php/hooks/stop'
2793--- examples/precise/php/hooks/stop 2012-06-17 15:39:48 +0000
2794+++ examples/precise/php/hooks/stop 1970-01-01 00:00:00 +0000
2795@@ -1,3 +0,0 @@
2796-#!/bin/bash
2797-juju-log "Stopping apache"
2798-/etc/init.d/apache2 stop
2799
2800=== removed file 'examples/precise/php/metadata.yaml'
2801--- examples/precise/php/metadata.yaml 2012-06-17 15:39:48 +0000
2802+++ examples/precise/php/metadata.yaml 1970-01-01 00:00:00 +0000
2803@@ -1,4 +0,0 @@
2804-name: php
2805-summary: "php container"
2806-description: |
2807- PHP environment for your code.
2808
2809=== removed file 'examples/precise/php/revision'
2810--- examples/precise/php/revision 2012-06-17 15:39:48 +0000
2811+++ examples/precise/php/revision 1970-01-01 00:00:00 +0000
2812@@ -1,1 +0,0 @@
2813-5
2814
2815=== removed directory 'examples/precise/recorder'
2816=== removed directory 'examples/precise/recorder/hooks'
2817=== removed file 'examples/precise/recorder/hooks/install'
2818--- examples/precise/recorder/hooks/install 2012-07-19 11:01:09 +0000
2819+++ examples/precise/recorder/hooks/install 1970-01-01 00:00:00 +0000
2820@@ -1,3 +0,0 @@
2821-#!/bin/bash
2822-# for testing
2823-open-port 8080
2824\ No newline at end of file
2825
2826=== removed file 'examples/precise/recorder/hooks/juju-info-relation-changed'
2827--- examples/precise/recorder/hooks/juju-info-relation-changed 2012-06-17 15:39:48 +0000
2828+++ examples/precise/recorder/hooks/juju-info-relation-changed 1970-01-01 00:00:00 +0000
2829@@ -1,5 +0,0 @@
2830-#!/bin/bash
2831-set -x
2832-
2833-juju-log "Changed relation with $JUJU_REMOTE_UNIT"
2834-juju-log `relation-list`
2835
2836=== removed file 'examples/precise/recorder/hooks/juju-info-relation-departed'
2837--- examples/precise/recorder/hooks/juju-info-relation-departed 2012-06-17 15:39:48 +0000
2838+++ examples/precise/recorder/hooks/juju-info-relation-departed 1970-01-01 00:00:00 +0000
2839@@ -1,3 +0,0 @@
2840-#!/bin/bash
2841-
2842-juju-log "Departed from $JUJU_REMOTE_UNIT"
2843
2844=== removed file 'examples/precise/recorder/hooks/juju-info-relation-joined'
2845--- examples/precise/recorder/hooks/juju-info-relation-joined 2012-06-17 15:39:48 +0000
2846+++ examples/precise/recorder/hooks/juju-info-relation-joined 1970-01-01 00:00:00 +0000
2847@@ -1,6 +0,0 @@
2848-#!/bin/bash
2849-
2850-set -x
2851-
2852-juju-log "Joined with $JUJU_REMOTE_UNIT"
2853-juju-log `relation-list`
2854
2855=== removed file 'examples/precise/recorder/metadata.yaml'
2856--- examples/precise/recorder/metadata.yaml 2012-06-17 15:39:48 +0000
2857+++ examples/precise/recorder/metadata.yaml 1970-01-01 00:00:00 +0000
2858@@ -1,9 +0,0 @@
2859-name: recorder
2860-summary: "Subordinate test charm"
2861-description: |
2862- Records which units can see each other to show how subordinates work.
2863-subordinate: true
2864-requires:
2865- juju-info:
2866- interface: juju-info
2867- scope: container
2868
2869=== removed file 'examples/precise/recorder/revision'
2870--- examples/precise/recorder/revision 2012-06-17 15:39:48 +0000
2871+++ examples/precise/recorder/revision 1970-01-01 00:00:00 +0000
2872@@ -1,1 +0,0 @@
2873-1
2874\ No newline at end of file
2875
2876=== removed directory 'examples/precise/wordpress'
2877=== removed file 'examples/precise/wordpress/config.yaml'
2878--- examples/precise/wordpress/config.yaml 2012-06-17 15:39:48 +0000
2879+++ examples/precise/wordpress/config.yaml 1970-01-01 00:00:00 +0000
2880@@ -1,7 +0,0 @@
2881-options:
2882- plugin:
2883- description: |
2884- The URL of a wordpress plugin which will be installed and made available in the Admin UI.
2885- http://downloads.wordpress.org/plugin/buddypress.1.2.8.zip is a valid example.
2886- type: string
2887-
2888
2889=== removed directory 'examples/precise/wordpress/hooks'
2890=== removed file 'examples/precise/wordpress/hooks/config-changed'
2891--- examples/precise/wordpress/hooks/config-changed 2012-06-17 15:39:48 +0000
2892+++ examples/precise/wordpress/hooks/config-changed 1970-01-01 00:00:00 +0000
2893@@ -1,19 +0,0 @@
2894-#!/bin/bash
2895-
2896-hostname=`unit-get public-address`
2897-plugin_dir="/var/www/$hostname/wp-content/plugins"
2898-plugin_url=`config-get plugin`
2899-
2900-base_plugin=`basename ${plugin_url}`
2901-
2902-cd /tmp
2903-
2904-curl -O ${plugin_url}
2905-
2906-if [ $? = 0 ]; then
2907- cd ${plugin_dir}
2908- unzip /tmp/${base_plugin}
2909-else
2910- juju-log -l WARNING "Unable to fetch ${plugin_url}"
2911-
2912-fi
2913
2914=== removed file 'examples/precise/wordpress/hooks/db-relation-changed'
2915--- examples/precise/wordpress/hooks/db-relation-changed 2012-06-17 15:39:48 +0000
2916+++ examples/precise/wordpress/hooks/db-relation-changed 1970-01-01 00:00:00 +0000
2917@@ -1,100 +0,0 @@
2918-#!/bin/bash
2919-
2920-set -eu # -x for verbose logging to juju debug-log
2921-
2922-UPLOAD_PATH="/var/www/wp-uploads"
2923-
2924-hostname=`unit-get public-address`
2925-juju-log "Retrieved hostname: $hostname"
2926-
2927-# Check we haven't already been setup.
2928-config_file_path="/etc/wordpress/config-$hostname.php"
2929-if [ -f "$config_file_path" ] ; then
2930- juju-log "Wordpress for site $config_file_path already Configured, exiting"
2931- echo "Already Configured, Exiting"
2932- exit 0 # already setup
2933-fi
2934-
2935-# Get the database settings; if not set, wait for this hook to be
2936-# invoked again
2937-database=`relation-get database`
2938-if [ -z "$database" ] ; then
2939- exit 0 # wait for future handshake from database service unit
2940-fi
2941-
2942-# Our protocol on this interface ensures that all or none of the
2943-# settings are set. But we can verify the setting or the values if
2944-# more error checking if desired.
2945-user=`relation-get user`
2946-password=`relation-get password`
2947-host=`relation-get private-address`
2948-
2949-# Create an internal secret key for wordpress; this is unrelated to
2950-# the password generated for the admin user of wordpress
2951-secret_key=`pwgen 10 1`
2952-
2953-juju-log "Creating appropriate upload paths and directories"
2954-# Setup appropriate upload paths and directories
2955-ln -s /usr/share/wordpress "/var/www/$hostname"
2956-mkdir -p $UPLOAD_PATH
2957-mkdir -p "$UPLOAD_PATH/$hostname"
2958-chown -R root:www-data $UPLOAD_PATH
2959-chmod 0744 $UPLOAD_PATH
2960-chmod 0770 "$UPLOAD_PATH/$hostname"
2961-chown -R root:www-data "/var/www/$hostname/wp-content"
2962-
2963-juju-log "Writing wordpress config file $config_file_path"
2964-# Write the wordpress config
2965-cat > $config_file_path <<EOF
2966-<?php
2967-define('DB_NAME', '$database');
2968-define('DB_USER', '$user');
2969-define('DB_PASSWORD', '$password');
2970-define('DB_HOST', '$host');
2971-define('SECRET_KEY', '$secret_key');
2972-
2973-#This will disable the update notification.
2974-define('WP_CORE_UPDATE', false);
2975-
2976-\$table_prefix = 'wp_';
2977-\$server = '$host';
2978-\$loginsql = '$user';
2979-\$passsql = '$password';
2980-\$base = '$database';
2981-\$upload_path = '/var/www/wp-uploads/$hostname';
2982-\$upload_url_path = 'http://$hostname/wp-uploads';
2983-?>
2984-EOF
2985-chmod 0644 $config_file_path
2986-
2987-# Write the apache config
2988-# XXX a future branch will change this to use augtool
2989-apache_config_file_path="/etc/apache2/sites-available/$hostname"
2990-juju-log "Writing apache config file $apache_config_file_path"
2991-cat > $apache_config_file_path <<EOF
2992-<VirtualHost *:80>
2993- ServerName $hostname
2994- DocumentRoot /var/www/$hostname
2995- Options All
2996- ErrorLog /var/log/apache2/wp-error.log
2997- TransferLog /var/log/apache2/wp-access.log
2998- # Store uploads in /var/www/wp-uploads/$0
2999- RewriteEngine On
3000- RewriteRule ^/wp-uploads/(.*)$ /var/www/wp-uploads/%%{HTTP_HOST}/\$1
3001-</VirtualHost>
3002-EOF
3003-chmod 0644 $apache_config_file_path
3004-
3005-# Configure apache
3006-juju-log "Enabling apache modules: rewrite, vhost_alias"
3007-a2enmod rewrite
3008-a2enmod vhost_alias
3009-juju-log "Enabling apache site: $hostname"
3010-a2ensite $hostname
3011-
3012-# Restart apache
3013-juju-log "Restarting apache2 service"
3014-/etc/init.d/apache2 restart
3015-
3016-# Make it publicly visible, once the wordpress service is exposed
3017-open-port 80/tcp
3018
3019=== removed file 'examples/precise/wordpress/hooks/install'
3020--- examples/precise/wordpress/hooks/install 2012-06-17 15:39:48 +0000
3021+++ examples/precise/wordpress/hooks/install 1970-01-01 00:00:00 +0000
3022@@ -1,6 +0,0 @@
3023-#!/bin/bash
3024-
3025-juju-log "Installing wordpress, pwgen via apt-get"
3026-set -eu # -x for verbose logging to juju debug-log
3027-
3028-apt-get -y install wordpress pwgen unzip
3029
3030=== removed file 'examples/precise/wordpress/hooks/start'
3031--- examples/precise/wordpress/hooks/start 2012-06-17 15:39:48 +0000
3032+++ examples/precise/wordpress/hooks/start 1970-01-01 00:00:00 +0000
3033@@ -1,1 +0,0 @@
3034-#!/bin/bash
3035
3036=== removed file 'examples/precise/wordpress/hooks/stop'
3037--- examples/precise/wordpress/hooks/stop 2012-06-17 15:39:48 +0000
3038+++ examples/precise/wordpress/hooks/stop 1970-01-01 00:00:00 +0000
3039@@ -1,3 +0,0 @@
3040-#!/bin/bash
3041-juju-log "Stopping apache"
3042-/etc/init.d/apache2 stop
3043
3044=== removed file 'examples/precise/wordpress/metadata.yaml'
3045--- examples/precise/wordpress/metadata.yaml 2012-06-17 15:39:48 +0000
3046+++ examples/precise/wordpress/metadata.yaml 1970-01-01 00:00:00 +0000
3047@@ -1,10 +0,0 @@
3048-name: wordpress
3049-summary: "WordPress blog"
3050-description: |
3051- Installs WordPress package (wordpress). Upon the database provider
3052- providing the required database, and the relation settings
3053- necessary to access it, completes the configuration of WordPress
3054- and makes it available on the web.
3055-requires:
3056- db:
3057- interface: mysql
3058
3059=== removed file 'examples/precise/wordpress/revision'
3060--- examples/precise/wordpress/revision 2012-06-17 15:39:48 +0000
3061+++ examples/precise/wordpress/revision 1970-01-01 00:00:00 +0000
3062@@ -1,1 +0,0 @@
3063-31
3064
3065=== added directory 'juju'
3066=== removed directory 'juju'
3067=== added file 'juju/__init__.py'
3068--- juju/__init__.py 1970-01-01 00:00:00 +0000
3069+++ juju/__init__.py 2013-04-22 11:00:38 +0000
3070@@ -0,0 +1,2 @@
3071+#
3072+__version__ = '0.7'
3073
3074=== removed file 'juju/__init__.py'
3075--- juju/__init__.py 2012-10-09 12:35:35 +0000
3076+++ juju/__init__.py 1970-01-01 00:00:00 +0000
3077@@ -1,2 +0,0 @@
3078-#
3079-__version__ = '0.6'
3080
3081=== added directory 'juju/agents'
3082=== removed directory 'juju/agents'
3083=== added file 'juju/agents/__init__.py'
3084--- juju/agents/__init__.py 1970-01-01 00:00:00 +0000
3085+++ juju/agents/__init__.py 2013-04-22 11:00:38 +0000
3086@@ -0,0 +1,1 @@
3087+#
3088
3089=== removed file 'juju/agents/__init__.py'
3090--- juju/agents/__init__.py 2012-06-11 15:40:48 +0000
3091+++ juju/agents/__init__.py 1970-01-01 00:00:00 +0000
3092@@ -1,1 +0,0 @@
3093-#
3094
3095=== added file 'juju/agents/base.py'
3096--- juju/agents/base.py 1970-01-01 00:00:00 +0000
3097+++ juju/agents/base.py 2013-04-22 11:00:38 +0000
3098@@ -0,0 +1,347 @@
3099+import argparse
3100+import os
3101+import logging
3102+import stat
3103+import sys
3104+import yaml
3105+
3106+import zookeeper
3107+
3108+from twisted.application import service
3109+from twisted.internet.defer import inlineCallbacks, returnValue
3110+from twisted.scripts._twistd_unix import UnixApplicationRunner, UnixAppLogger
3111+from twisted.python.log import PythonLoggingObserver
3112+
3113+from txzookeeper import ZookeeperClient
3114+from txzookeeper.managed import ManagedClient
3115+
3116+from juju.control.options import setup_twistd_options
3117+from juju.errors import NoConnection, JujuError
3118+from juju.lib.zklog import ZookeeperHandler
3119+from juju.lib.zk import CLIENT_SESSION_TIMEOUT
3120+from juju.state.environment import GlobalSettingsStateManager
3121+
3122+
3123+def load_client_id(path):
3124+ try:
3125+ with open(path) as f:
3126+ return yaml.load(f.read())
3127+ except IOError:
3128+ return None
3129+
3130+
3131+def save_client_id(path, client_id):
3132+ parent = os.path.dirname(path)
3133+ if not os.path.exists(parent):
3134+ os.makedirs(parent)
3135+ with open(path, "w") as f:
3136+ f.write(yaml.dump(client_id))
3137+ os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
3138+
3139+
3140+class TwistedOptionNamespace(object):
3141+ """
3142+ An argparse namespace implementation that is compatible with twisted
3143+ config dictionary usage.
3144+ """
3145+
3146+ def __getitem__(self, key):
3147+ return self.__dict__[key]
3148+
3149+ def __setitem__(self, key, value):
3150+ self.__dict__[key] = value
3151+
3152+ def get(self, key, default=None):
3153+ return self.__dict__.get(key, default)
3154+
3155+ def has_key(self, key):
3156+ return key in self.__dict__
3157+
3158+
3159+class AgentLogger(UnixAppLogger):
3160+
3161+ def __init__(self, options):
3162+ super(AgentLogger, self).__init__(options)
3163+ self._loglevel = options.get("loglevel", logging.DEBUG)
3164+
3165+ def _getLogObserver(self):
3166+
3167+ if self._logfilename == "-":
3168+ log_file = sys.stdout
3169+ else:
3170+ log_file = open(self._logfilename, "a")
3171+
3172+ # Setup file logger
3173+ log_handler = logging.StreamHandler(log_file)
3174+ formatter = logging.Formatter(
3175+ "%(asctime)s: %(name)s@%(levelname)s: %(message)s")
3176+ log_handler.setFormatter(formatter)
3177+
3178+ # Also capture zookeeper logs (XXX not compatible with rotation)
3179+ zookeeper.set_log_stream(log_file)
3180+ zookeeper.set_debug_level(0)
3181+
3182+ # Configure logging.
3183+ root = logging.getLogger()
3184+ root.addHandler(log_handler)
3185+ root.setLevel(logging.getLevelName(self._loglevel))
3186+
3187+ # Twisted logging is painfully verbose on twisted.web, and
3188+ # there isn't a good way to distinguish different channels
3189+ # within twisted, so just utlize error level logging only for
3190+ # all of twisted.
3191+ twisted_log = logging.getLogger("twisted")
3192+ twisted_log.setLevel(logging.ERROR)
3193+
3194+ observer = PythonLoggingObserver()
3195+ return observer.emit
3196+
3197+
3198+class AgentRunner(UnixApplicationRunner):
3199+
3200+ application = None
3201+ loggerFactory = AgentLogger
3202+
3203+ def createOrGetApplication(self):
3204+ return self.application
3205+
3206+
3207+class BaseAgent(object, service.Service):
3208+
3209+ name = "juju-agent-unknown"
3210+ client = None
3211+
3212+ # Flag when enabling persistent topology watches, testing aid.
3213+ _watch_enabled = True
3214+
3215+ # Distributed debug log handler
3216+ _debug_log_handler = None
3217+
3218+ @classmethod
3219+ def run(cls):
3220+ """Runs the agent as a unix daemon.
3221+
3222+ Main entry point for starting an agent, parses cli options, and setups
3223+ a daemon using twistd as per options.
3224+ """
3225+ parser = argparse.ArgumentParser()
3226+ cls.setup_options(parser)
3227+ config = parser.parse_args(namespace=TwistedOptionNamespace())
3228+ runner = AgentRunner(config)
3229+ agent = cls()
3230+ agent.configure(config)
3231+ runner.application = agent.as_app()
3232+ runner.run()
3233+
3234+ @classmethod
3235+ def setup_options(cls, parser):
3236+ """Configure the argparse cli parser for the agent."""
3237+ return cls.setup_default_options(parser)
3238+
3239+ @classmethod
3240+ def setup_default_options(cls, parser):
3241+ """Setup default twistd daemon and agent options.
3242+
3243+ This method is intended as a utility for subclasses.
3244+
3245+ @param parser an argparse instance.
3246+ @type C{argparse.ArgumentParser}
3247+ """
3248+ setup_twistd_options(parser, cls)
3249+ setup_default_agent_options(parser, cls)
3250+
3251+ def as_app(self):
3252+ """
3253+ Return the agent as a C{twisted.application.service.Application}
3254+ """
3255+ app = service.Application(self.name)
3256+ self.setServiceParent(app)
3257+ return app
3258+
3259+ def configure(self, options):
3260+ """Configure the agent to handle its cli options.
3261+
3262+ Invoked called before the service is started.
3263+
3264+ @param options
3265+ @type C{TwistedOptionNamespace} an argparse namespace corresponding
3266+ to a dict.
3267+ """
3268+ if not options.get("zookeeper_servers"):
3269+ raise NoConnection("No zookeeper connection configured.")
3270+
3271+ if not os.path.exists(options.get("juju_directory", "")):
3272+ raise JujuError(
3273+ "Invalid juju-directory %r, does not exist." % (
3274+ options.get("juju_directory")))
3275+
3276+ if options["session_file"] is None:
3277+ raise JujuError("No session file specified")
3278+
3279+ self.config = options
3280+
3281+ @inlineCallbacks
3282+ def _kill_existing_session(self):
3283+ try:
3284+ # We might have died suddenly, in which case the session may
3285+ # still be alive. If this is the case, shoot it in the head, so
3286+ # it doesn't interfere with our attempts to recreate our state.
3287+ # (We need to be able to recreate our state *anyway*, and it's
3288+ # much simpler to force ourselves to recreate it every time than
3289+ # it is to mess around partially recreating partial state.)
3290+ client_id = load_client_id(self.config["session_file"])
3291+ if client_id is None:
3292+ return
3293+ temp_client = yield ZookeeperClient().connect(
3294+ self.config["zookeeper_servers"], client_id=client_id)
3295+ yield temp_client.close()
3296+ except zookeeper.ZooKeeperException:
3297+ # We don't really care what went wrong; just that we're not able
3298+ # to connect using the old session, and therefore we should be ok
3299+ # to start a fresh one without transient state hanging around.
3300+ pass
3301+
3302+ @inlineCallbacks
3303+ def connect(self):
3304+ """Return an authenticated connection to the juju zookeeper."""
3305+ yield self._kill_existing_session()
3306+ self.client = yield ManagedClient(
3307+ session_timeout=CLIENT_SESSION_TIMEOUT).connect(
3308+ self.config["zookeeper_servers"])
3309+ save_client_id(
3310+ self.config["session_file"], self.client.client_id)
3311+
3312+ principals = self.config.get("principals", ())
3313+ for principal in principals:
3314+ self.client.add_auth("digest", principal)
3315+
3316+ # bug work around to keep auth fast
3317+ if principals:
3318+ yield self.client.exists("/")
3319+
3320+ returnValue(self.client)
3321+
3322+ def start(self):
3323+ """Callback invoked on the agent's startup.
3324+
3325+ The agent will already be connected to zookeeper. Subclasses are
3326+ responsible for implementing.
3327+ """
3328+ raise NotImplementedError
3329+
3330+ def stop(self):
3331+ """Callback invoked on when the agent is shutting down."""
3332+ pass
3333+
3334+ # Twisted IService implementation, used for delegates to maintain naming
3335+ # conventions.
3336+ @inlineCallbacks
3337+ def startService(self):
3338+ yield self.connect()
3339+
3340+ # Start the global settings watch prior to starting the agent.
3341+ # Allows for debug log to be enabled early.
3342+ if self.get_watch_enabled():
3343+ yield self.start_global_settings_watch()
3344+
3345+ yield self.start()
3346+
3347+ @inlineCallbacks
3348+ def stopService(self):
3349+ try:
3350+ yield self.stop()
3351+ finally:
3352+ if self.client and self.client.connected:
3353+ self.client.close()
3354+ session_file = self.config["session_file"]
3355+ if os.path.exists(session_file):
3356+ os.unlink(session_file)
3357+
3358+ def set_watch_enabled(self, flag):
3359+ """Set boolean flag for whether this agent should watching zookeeper.
3360+
3361+ This is mainly used for testing, to allow for setting up the
3362+ various data scenarios, before enabling an agent watch which will
3363+ be observing state.
3364+ """
3365+ self._watch_enabled = bool(flag)
3366+
3367+ def get_watch_enabled(self):
3368+ """Returns a boolean if the agent should be settings state watches.
3369+
3370+ The meaning of this flag is typically agent specific, as each
3371+ agent has separate watches they'd like to establish on agent specific
3372+ state within zookeeper. In general if this flag is False, the agent
3373+ should refrain from establishing a watch on startup. This flag is
3374+ typically used by tests to isolate and test the watch behavior
3375+ independent of the agent startup, via construction of test data.
3376+ """
3377+ return self._watch_enabled
3378+
3379+ def start_global_settings_watch(self):
3380+ """Start watching the runtime state for configuration changes."""
3381+ self.global_settings_state = GlobalSettingsStateManager(self.client)
3382+ return self.global_settings_state.watch_settings_changes(
3383+ self.on_global_settings_change)
3384+
3385+ @inlineCallbacks
3386+ def on_global_settings_change(self, change):
3387+ """On global settings change, take action.
3388+ """
3389+ if (yield self.global_settings_state.is_debug_log_enabled()):
3390+ yield self.start_debug_log()
3391+ else:
3392+ self.stop_debug_log()
3393+
3394+ @inlineCallbacks
3395+ def start_debug_log(self):
3396+ """Enable the distributed debug log handler.
3397+ """
3398+ if self._debug_log_handler is not None:
3399+ returnValue(None)
3400+ context_name = self.get_agent_name()
3401+ self._debug_log_handler = ZookeeperHandler(
3402+ self.client, context_name)
3403+ yield self._debug_log_handler.open()
3404+ log_root = logging.getLogger()
3405+ log_root.addHandler(self._debug_log_handler)
3406+
3407+ def stop_debug_log(self):
3408+ """Disable any configured debug log handler.
3409+ """
3410+ if self._debug_log_handler is None:
3411+ return
3412+ handler, self._debug_log_handler = self._debug_log_handler, None
3413+ log_root = logging.getLogger()
3414+ log_root.removeHandler(handler)
3415+
3416+ def get_agent_name(self):
3417+ """Return the agent's name and context such that it can be identified.
3418+
3419+ Subclasses should override this to provide additional context and
3420+ unique naming.
3421+ """
3422+ return self.__class__.__name__
3423+
3424+
3425+def setup_default_agent_options(parser, cls):
3426+
3427+ principals_default = os.environ.get("JUJU_PRINCIPALS", "").split()
3428+ parser.add_argument(
3429+ "--principal", "-e",
3430+ action="append", dest="principals", default=principals_default,
3431+ help="Agent principals to utilize for the zookeeper connection")
3432+
3433+ servers_default = os.environ.get("JUJU_ZOOKEEPER", "")
3434+ parser.add_argument(
3435+ "--zookeeper-servers", "-z", default=servers_default,
3436+ help="juju Zookeeper servers to connect to ($JUJU_ZOOKEEPER)")
3437+
3438+ juju_home = os.environ.get("JUJU_HOME", "/var/lib/juju")
3439+ parser.add_argument(
3440+ "--juju-directory", default=juju_home, type=os.path.abspath,
3441+ help="juju working directory ($JUJU_HOME)")
3442+
3443+ parser.add_argument(
3444+ "--session-file", default=None, type=os.path.abspath,
3445+ help="like a pidfile, but for the zookeeper session id")
3446
3447=== removed file 'juju/agents/base.py'
3448--- juju/agents/base.py 2012-06-11 15:40:48 +0000
3449+++ juju/agents/base.py 1970-01-01 00:00:00 +0000
3450@@ -1,345 +0,0 @@
3451-import argparse
3452-import os
3453-import logging
3454-import stat
3455-import sys
3456-import yaml
3457-
3458-import zookeeper
3459-
3460-from twisted.application import service
3461-from twisted.internet.defer import inlineCallbacks, returnValue
3462-from twisted.scripts._twistd_unix import UnixApplicationRunner, UnixAppLogger
3463-from twisted.python.log import PythonLoggingObserver
3464-
3465-from txzookeeper import ZookeeperClient
3466-from txzookeeper.managed import ManagedClient
3467-
3468-from juju.control.options import setup_twistd_options
3469-from juju.errors import NoConnection, JujuError
3470-from juju.lib.zklog import ZookeeperHandler
3471-from juju.state.environment import GlobalSettingsStateManager
3472-
3473-
3474-def load_client_id(path):
3475- try:
3476- with open(path) as f:
3477- return yaml.load(f.read())
3478- except IOError:
3479- return None
3480-
3481-
3482-def save_client_id(path, client_id):
3483- parent = os.path.dirname(path)
3484- if not os.path.exists(parent):
3485- os.makedirs(parent)
3486- with open(path, "w") as f:
3487- f.write(yaml.dump(client_id))
3488- os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
3489-
3490-
3491-class TwistedOptionNamespace(object):
3492- """
3493- An argparse namespace implementation that is compatible with twisted
3494- config dictionary usage.
3495- """
3496-
3497- def __getitem__(self, key):
3498- return self.__dict__[key]
3499-
3500- def __setitem__(self, key, value):
3501- self.__dict__[key] = value
3502-
3503- def get(self, key, default=None):
3504- return self.__dict__.get(key, default)
3505-
3506- def has_key(self, key):
3507- return key in self.__dict__
3508-
3509-
3510-class AgentLogger(UnixAppLogger):
3511-
3512- def __init__(self, options):
3513- super(AgentLogger, self).__init__(options)
3514- self._loglevel = options.get("loglevel", logging.DEBUG)
3515-
3516- def _getLogObserver(self):
3517-
3518- if self._logfilename == "-":
3519- log_file = sys.stdout
3520- else:
3521- log_file = open(self._logfilename, "a")
3522-
3523- # Setup file logger
3524- log_handler = logging.StreamHandler(log_file)
3525- formatter = logging.Formatter(
3526- "%(asctime)s: %(name)s@%(levelname)s: %(message)s")
3527- log_handler.setFormatter(formatter)
3528-
3529- # Also capture zookeeper logs (XXX not compatible with rotation)
3530- zookeeper.set_log_stream(log_file)
3531- zookeeper.set_debug_level(0)
3532-
3533- # Configure logging.
3534- root = logging.getLogger()
3535- root.addHandler(log_handler)
3536- root.setLevel(logging.getLevelName(self._loglevel))
3537-
3538- # Twisted logging is painfully verbose on twisted.web, and
3539- # there isn't a good way to distinguish different channels
3540- # within twisted, so just utlize error level logging only for
3541- # all of twisted.
3542- twisted_log = logging.getLogger("twisted")
3543- twisted_log.setLevel(logging.ERROR)
3544-
3545- observer = PythonLoggingObserver()
3546- return observer.emit
3547-
3548-
3549-class AgentRunner(UnixApplicationRunner):
3550-
3551- application = None
3552- loggerFactory = AgentLogger
3553-
3554- def createOrGetApplication(self):
3555- return self.application
3556-
3557-
3558-class BaseAgent(object, service.Service):
3559-
3560- name = "juju-agent-unknown"
3561- client = None
3562-
3563- # Flag when enabling persistent topology watches, testing aid.
3564- _watch_enabled = True
3565-
3566- # Distributed debug log handler
3567- _debug_log_handler = None
3568-
3569- @classmethod
3570- def run(cls):
3571- """Runs the agent as a unix daemon.
3572-
3573- Main entry point for starting an agent, parses cli options, and setups
3574- a daemon using twistd as per options.
3575- """
3576- parser = argparse.ArgumentParser()
3577- cls.setup_options(parser)
3578- config = parser.parse_args(namespace=TwistedOptionNamespace())
3579- runner = AgentRunner(config)
3580- agent = cls()
3581- agent.configure(config)
3582- runner.application = agent.as_app()
3583- runner.run()
3584-
3585- @classmethod
3586- def setup_options(cls, parser):
3587- """Configure the argparse cli parser for the agent."""
3588- return cls.setup_default_options(parser)
3589-
3590- @classmethod
3591- def setup_default_options(cls, parser):
3592- """Setup default twistd daemon and agent options.
3593-
3594- This method is intended as a utility for subclasses.
3595-
3596- @param parser an argparse instance.
3597- @type C{argparse.ArgumentParser}
3598- """
3599- setup_twistd_options(parser, cls)
3600- setup_default_agent_options(parser, cls)
3601-
3602- def as_app(self):
3603- """
3604- Return the agent as a C{twisted.application.service.Application}
3605- """
3606- app = service.Application(self.name)
3607- self.setServiceParent(app)
3608- return app
3609-
3610- def configure(self, options):
3611- """Configure the agent to handle its cli options.
3612-
3613- Invoked called before the service is started.
3614-
3615- @param options
3616- @type C{TwistedOptionNamespace} an argparse namespace corresponding
3617- to a dict.
3618- """
3619- if not options.get("zookeeper_servers"):
3620- raise NoConnection("No zookeeper connection configured.")
3621-
3622- if not os.path.exists(options.get("juju_directory", "")):
3623- raise JujuError(
3624- "Invalid juju-directory %r, does not exist." % (
3625- options.get("juju_directory")))
3626-
3627- if options["session_file"] is None:
3628- raise JujuError("No session file specified")
3629-
3630- self.config = options
3631-
3632- @inlineCallbacks
3633- def _kill_existing_session(self):
3634- try:
3635- # We might have died suddenly, in which case the session may
3636- # still be alive. If this is the case, shoot it in the head, so
3637- # it doesn't interfere with our attempts to recreate our state.
3638- # (We need to be able to recreate our state *anyway*, and it's
3639- # much simpler to force ourselves to recreate it every time than
3640- # it is to mess around partially recreating partial state.)
3641- client_id = load_client_id(self.config["session_file"])
3642- if client_id is None:
3643- return
3644- temp_client = yield ZookeeperClient().connect(
3645- self.config["zookeeper_servers"], client_id=client_id)
3646- yield temp_client.close()
3647- except zookeeper.ZooKeeperException:
3648- # We don't really care what went wrong; just that we're not able
3649- # to connect using the old session, and therefore we should be ok
3650- # to start a fresh one without transient state hanging around.
3651- pass
3652-
3653- @inlineCallbacks
3654- def connect(self):
3655- """Return an authenticated connection to the juju zookeeper."""
3656- yield self._kill_existing_session()
3657- self.client = yield ManagedClient().connect(
3658- self.config["zookeeper_servers"])
3659- save_client_id(
3660- self.config["session_file"], self.client.client_id)
3661-
3662- principals = self.config.get("principals", ())
3663- for principal in principals:
3664- self.client.add_auth("digest", principal)
3665-
3666- # bug work around to keep auth fast
3667- if principals:
3668- yield self.client.exists("/")
3669-
3670- returnValue(self.client)
3671-
3672- def start(self):
3673- """Callback invoked on the agent's startup.
3674-
3675- The agent will already be connected to zookeeper. Subclasses are
3676- responsible for implementing.
3677- """
3678- raise NotImplementedError
3679-
3680- def stop(self):
3681- """Callback invoked on when the agent is shutting down."""
3682- pass
3683-
3684- # Twisted IService implementation, used for delegates to maintain naming
3685- # conventions.
3686- @inlineCallbacks
3687- def startService(self):
3688- yield self.connect()
3689-
3690- # Start the global settings watch prior to starting the agent.
3691- # Allows for debug log to be enabled early.
3692- if self.get_watch_enabled():
3693- yield self.start_global_settings_watch()
3694-
3695- yield self.start()
3696-
3697- @inlineCallbacks
3698- def stopService(self):
3699- try:
3700- yield self.stop()
3701- finally:
3702- if self.client and self.client.connected:
3703- self.client.close()
3704- session_file = self.config["session_file"]
3705- if os.path.exists(session_file):
3706- os.unlink(session_file)
3707-
3708- def set_watch_enabled(self, flag):
3709- """Set boolean flag for whether this agent should watching zookeeper.
3710-
3711- This is mainly used for testing, to allow for setting up the
3712- various data scenarios, before enabling an agent watch which will
3713- be observing state.
3714- """
3715- self._watch_enabled = bool(flag)
3716-
3717- def get_watch_enabled(self):
3718- """Returns a boolean if the agent should be settings state watches.
3719-
3720- The meaning of this flag is typically agent specific, as each
3721- agent has separate watches they'd like to establish on agent specific
3722- state within zookeeper. In general if this flag is False, the agent
3723- should refrain from establishing a watch on startup. This flag is
3724- typically used by tests to isolate and test the watch behavior
3725- independent of the agent startup, via construction of test data.
3726- """
3727- return self._watch_enabled
3728-
3729- def start_global_settings_watch(self):
3730- """Start watching the runtime state for configuration changes."""
3731- self.global_settings_state = GlobalSettingsStateManager(self.client)
3732- return self.global_settings_state.watch_settings_changes(
3733- self.on_global_settings_change)
3734-
3735- @inlineCallbacks
3736- def on_global_settings_change(self, change):
3737- """On global settings change, take action.
3738- """
3739- if (yield self.global_settings_state.is_debug_log_enabled()):
3740- yield self.start_debug_log()
3741- else:
3742- self.stop_debug_log()
3743-
3744- @inlineCallbacks
3745- def start_debug_log(self):
3746- """Enable the distributed debug log handler.
3747- """
3748- if self._debug_log_handler is not None:
3749- returnValue(None)
3750- context_name = self.get_agent_name()
3751- self._debug_log_handler = ZookeeperHandler(
3752- self.client, context_name)
3753- yield self._debug_log_handler.open()
3754- log_root = logging.getLogger()
3755- log_root.addHandler(self._debug_log_handler)
3756-
3757- def stop_debug_log(self):
3758- """Disable any configured debug log handler.
3759- """
3760- if self._debug_log_handler is None:
3761- return
3762- handler, self._debug_log_handler = self._debug_log_handler, None
3763- log_root = logging.getLogger()
3764- log_root.removeHandler(handler)
3765-
3766- def get_agent_name(self):
3767- """Return the agent's name and context such that it can be identified.
3768-
3769- Subclasses should override this to provide additional context and
3770- unique naming.
3771- """
3772- return self.__class__.__name__
3773-
3774-
3775-def setup_default_agent_options(parser, cls):
3776-
3777- principals_default = os.environ.get("JUJU_PRINCIPALS", "").split()
3778- parser.add_argument(
3779- "--principal", "-e",
3780- action="append", dest="principals", default=principals_default,
3781- help="Agent principals to utilize for the zookeeper connection")
3782-
3783- servers_default = os.environ.get("JUJU_ZOOKEEPER", "")
3784- parser.add_argument(
3785- "--zookeeper-servers", "-z", default=servers_default,
3786- help="juju Zookeeper servers to connect to ($JUJU_ZOOKEEPER)")
3787-
3788- juju_home = os.environ.get("JUJU_HOME", "/var/lib/juju")
3789- parser.add_argument(
3790- "--juju-directory", default=juju_home, type=os.path.abspath,
3791- help="juju working directory ($JUJU_HOME)")
3792-
3793- parser.add_argument(
3794- "--session-file", default=None, type=os.path.abspath,
3795- help="like a pidfile, but for the zookeeper session id")
3796
3797=== added file 'juju/agents/dummy.py'
3798--- juju/agents/dummy.py 1970-01-01 00:00:00 +0000
3799+++ juju/agents/dummy.py 2013-04-22 11:00:38 +0000
3800@@ -0,0 +1,16 @@
3801+
3802+from .base import BaseAgent
3803+
3804+
3805+class DummyAgent(BaseAgent):
3806+ """A do nothing juju agent.
3807+
3808+ A bit like a dog, it just lies around basking in the sun,
3809+ doing nothing, nonetheless its quite content. :-)
3810+ """
3811+
3812+ def start(self):
3813+ """nothing to see here, move along."""
3814+
3815+if __name__ == '__main__':
3816+ DummyAgent.run()
3817
3818=== removed file 'juju/agents/dummy.py'
3819--- juju/agents/dummy.py 2012-06-11 15:40:48 +0000
3820+++ juju/agents/dummy.py 1970-01-01 00:00:00 +0000
3821@@ -1,16 +0,0 @@
3822-
3823-from .base import BaseAgent
3824-
3825-
3826-class DummyAgent(BaseAgent):
3827- """A do nothing juju agent.
3828-
3829- A bit like a dog, it just lies around basking in the sun,
3830- doing nothing, nonetheless its quite content. :-)
3831- """
3832-
3833- def start(self):
3834- """nothing to see here, move along."""
3835-
3836-if __name__ == '__main__':
3837- DummyAgent.run()
3838
3839=== added file 'juju/agents/machine.py'
3840--- juju/agents/machine.py 1970-01-01 00:00:00 +0000
3841+++ juju/agents/machine.py 2013-04-22 11:00:38 +0000
3842@@ -0,0 +1,126 @@
3843+import logging
3844+import os
3845+
3846+from twisted.internet.defer import inlineCallbacks
3847+
3848+from juju.errors import JujuError
3849+from juju.state.machine import MachineStateManager
3850+from juju.state.service import ServiceStateManager
3851+from juju.unit.deploy import UnitDeployer
3852+
3853+from .base import BaseAgent
3854+
3855+
3856+log = logging.getLogger("juju.agents.machine")
3857+
3858+
3859+class MachineAgent(BaseAgent):
3860+ """A juju machine agent.
3861+
3862+ The machine agent is responsible for monitoring service units
3863+ assigned to a machine. If a new unit is assigned to machine, the
3864+ machine agent will download the charm, create a working
3865+ space for the service unit agent, and then launch it.
3866+
3867+ Additionally the machine agent will monitor the running service
3868+ unit agents on the machine, via their ephemeral nodes, and
3869+ restart them if they die.
3870+ """
3871+
3872+ name = "juju-machine-agent"
3873+ unit_agent_module = "juju.agents.unit"
3874+
3875+ @property
3876+ def units_directory(self):
3877+ return os.path.join(self.config["juju_directory"], "units")
3878+
3879+ @property
3880+ def unit_state_directory(self):
3881+ return os.path.join(self.config["juju_directory"], "state")
3882+
3883+ @inlineCallbacks
3884+ def start(self):
3885+ """Start the machine agent.
3886+
3887+ Creates state directories on the machine, retrieves the machine state,
3888+ and enables watch on assigned units.
3889+ """
3890+ if not os.path.exists(self.units_directory):
3891+ os.makedirs(self.units_directory)
3892+
3893+ if not os.path.exists(self.unit_state_directory):
3894+ os.makedirs(self.unit_state_directory)
3895+
3896+ # Get state managers we'll be utilizing.
3897+ self.service_state_manager = ServiceStateManager(self.client)
3898+ self.unit_deployer = UnitDeployer(
3899+ self.client, self.get_machine_id(), self.config["juju_directory"])
3900+ yield self.unit_deployer.start()
3901+
3902+ # Retrieve the machine state for the machine we represent.
3903+ machine_manager = MachineStateManager(self.client)
3904+ self.machine_state = yield machine_manager.get_machine_state(
3905+ self.get_machine_id())
3906+
3907+ # Watch assigned units for the machine.
3908+ if self.get_watch_enabled():
3909+ self.machine_state.watch_assigned_units(
3910+ self.watch_service_units)
3911+
3912+ # Connect the machine agent, broadcasting presence to the world.
3913+ yield self.machine_state.connect_agent()
3914+ log.info("Machine agent started id:%s" % self.get_machine_id())
3915+
3916+ @inlineCallbacks
3917+ def watch_service_units(self, old_units, new_units):
3918+ """Callback invoked when the assigned service units change.
3919+ """
3920+ if old_units is None:
3921+ old_units = set()
3922+
3923+ log.debug(
3924+ "Units changed old:%s new:%s", old_units, new_units)
3925+
3926+ stopped = old_units - new_units
3927+ started = new_units - old_units
3928+
3929+ for unit_name in stopped:
3930+ log.debug("Stopping service unit: %s ...", unit_name)
3931+ try:
3932+ yield self.unit_deployer.kill_service_unit(unit_name)
3933+ except Exception:
3934+ log.exception("Error stopping unit: %s", unit_name)
3935+
3936+ for unit_name in started:
3937+ log.debug("Starting service unit: %s ...", unit_name)
3938+ try:
3939+ yield self.unit_deployer.start_service_unit(unit_name)
3940+ except Exception:
3941+ log.exception("Error starting unit: %s", unit_name)
3942+
3943+ def get_machine_id(self):
3944+ """Get the id of the machine as known within the zk state."""
3945+ return self.config["machine_id"]
3946+
3947+ def get_agent_name(self):
3948+ return "Machine:%s" % (self.get_machine_id())
3949+
3950+ def configure(self, options):
3951+ super(MachineAgent, self).configure(options)
3952+ if not options.get("machine_id"):
3953+ msg = ("--machine-id must be provided in the command line, "
3954+ "or $JUJU_MACHINE_ID in the environment")
3955+ raise JujuError(msg)
3956+
3957+ @classmethod
3958+ def setup_options(cls, parser):
3959+ super(MachineAgent, cls).setup_options(parser)
3960+
3961+ machine_id = os.environ.get("JUJU_MACHINE_ID", "")
3962+ parser.add_argument(
3963+ "--machine-id", default=machine_id)
3964+ return parser
3965+
3966+
3967+if __name__ == "__main__":
3968+ MachineAgent().run()
3969
3970=== removed file 'juju/agents/machine.py'
3971--- juju/agents/machine.py 2012-08-24 16:02:42 +0000
3972+++ juju/agents/machine.py 1970-01-01 00:00:00 +0000
3973@@ -1,126 +0,0 @@
3974-import logging
3975-import os
3976-
3977-from twisted.internet.defer import inlineCallbacks
3978-
3979-from juju.errors import JujuError
3980-from juju.state.machine import MachineStateManager
3981-from juju.state.service import ServiceStateManager
3982-from juju.unit.deploy import UnitDeployer
3983-
3984-from .base import BaseAgent
3985-
3986-
3987-log = logging.getLogger("juju.agents.machine")
3988-
3989-
3990-class MachineAgent(BaseAgent):
3991- """A juju machine agent.
3992-
3993- The machine agent is responsible for monitoring service units
3994- assigned to a machine. If a new unit is assigned to machine, the
3995- machine agent will download the charm, create a working
3996- space for the service unit agent, and then launch it.
3997-
3998- Additionally the machine agent will monitor the running service
3999- unit agents on the machine, via their ephemeral nodes, and
4000- restart them if they die.
4001- """
4002-
4003- name = "juju-machine-agent"
4004- unit_agent_module = "juju.agents.unit"
4005-
4006- @property
4007- def units_directory(self):
4008- return os.path.join(self.config["juju_directory"], "units")
4009-
4010- @property
4011- def unit_state_directory(self):
4012- return os.path.join(self.config["juju_directory"], "state")
4013-
4014- @inlineCallbacks
4015- def start(self):
4016- """Start the machine agent.
4017-
4018- Creates state directories on the machine, retrieves the machine state,
4019- and enables watch on assigned units.
4020- """
4021- if not os.path.exists(self.units_directory):
4022- os.makedirs(self.units_directory)
4023-
4024- if not os.path.exists(self.unit_state_directory):
4025- os.makedirs(self.unit_state_directory)
4026-
4027- # Get state managers we'll be utilizing.
4028- self.service_state_manager = ServiceStateManager(self.client)
4029- self.unit_deployer = UnitDeployer(
4030- self.client, self.get_machine_id(), self.config["juju_directory"])
4031- yield self.unit_deployer.start()
4032-
4033- # Retrieve the machine state for the machine we represent.
4034- machine_manager = MachineStateManager(self.client)
4035- self.machine_state = yield machine_manager.get_machine_state(
4036- self.get_machine_id())
4037-
4038- # Watch assigned units for the machine.
4039- if self.get_watch_enabled():
4040- self.machine_state.watch_assigned_units(
4041- self.watch_service_units)
4042-
4043- # Connect the machine agent, broadcasting presence to the world.
4044- yield self.machine_state.connect_agent()
4045- log.info("Machine agent started id:%s" % self.get_machine_id())
4046-
4047- @inlineCallbacks
4048- def watch_service_units(self, old_units, new_units):
4049- """Callback invoked when the assigned service units change.
4050- """
4051- if old_units is None:
4052- old_units = set()
4053-
4054- log.debug(
4055- "Units changed old:%s new:%s", old_units, new_units)
4056-
4057- stopped = old_units - new_units
4058- started = new_units - old_units
4059-
4060- for unit_name in stopped:
4061- log.debug("Stopping service unit: %s ...", unit_name)
4062- try:
4063- yield self.unit_deployer.kill_service_unit(unit_name)
4064- except Exception:
4065- log.exception("Error stopping unit: %s", unit_name)
4066-
4067- for unit_name in started:
4068- log.debug("Starting service unit: %s ...", unit_name)
4069- try:
4070- yield self.unit_deployer.start_service_unit(unit_name)
4071- except Exception:
4072- log.exception("Error starting unit: %s", unit_name)
4073-
4074- def get_machine_id(self):
4075- """Get the id of the machine as known within the zk state."""
4076- return self.config["machine_id"]
4077-
4078- def get_agent_name(self):
4079- return "Machine:%s" % (self.get_machine_id())
4080-
4081- def configure(self, options):
4082- super(MachineAgent, self).configure(options)
4083- if not options.get("machine_id"):
4084- msg = ("--machine-id must be provided in the command line, "
4085- "or $JUJU_MACHINE_ID in the environment")
4086- raise JujuError(msg)
4087-
4088- @classmethod
4089- def setup_options(cls, parser):
4090- super(MachineAgent, cls).setup_options(parser)
4091-
4092- machine_id = os.environ.get("JUJU_MACHINE_ID", "")
4093- parser.add_argument(
4094- "--machine-id", default=machine_id)
4095- return parser
4096-
4097-
4098-if __name__ == "__main__":
4099- MachineAgent().run()
4100
4101=== added file 'juju/agents/provision.py'
4102--- juju/agents/provision.py 1970-01-01 00:00:00 +0000
4103+++ juju/agents/provision.py 2013-04-22 11:00:38 +0000
4104@@ -0,0 +1,239 @@
4105+import logging
4106+
4107+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
4108+from zookeeper import NoNodeException
4109+
4110+from juju.environment.config import EnvironmentsConfig
4111+from juju.errors import ProviderError
4112+from juju.lib.twistutils import concurrent_execution_guard
4113+from juju.state.errors import MachineStateNotFound, StopWatcher
4114+from juju.state.firewall import FirewallManager
4115+from juju.state.machine import MachineStateManager
4116+from juju.state.service import ServiceStateManager
4117+
4118+from .base import BaseAgent
4119+
4120+
4121+log = logging.getLogger("juju.agents.provision")
4122+
4123+
4124+class ProvisioningAgent(BaseAgent):
4125+
4126+ name = "juju-provisoning-agent"
4127+
4128+ _current_machines = ()
4129+
4130+ # time in seconds
4131+ machine_check_period = 60
4132+
4133+ def get_agent_name(self):
4134+ return "provision:%s" % (self.environment.type)
4135+
4136+ @inlineCallbacks
4137+ def start(self):
4138+ self._running = True
4139+
4140+ self.environment = yield self.configure_environment()
4141+ self.provider = self.environment.get_machine_provider()
4142+ self.machine_state_manager = MachineStateManager(self.client)
4143+ self.service_state_manager = ServiceStateManager(self.client)
4144+ self.firewall_manager = FirewallManager(
4145+ self.client, self.is_running, self.provider)
4146+
4147+ if self.get_watch_enabled():
4148+ self.machine_state_manager.watch_machine_states(
4149+ self.watch_machine_changes)
4150+ self.service_state_manager.watch_service_states(
4151+ self.firewall_manager.watch_service_changes)
4152+ from twisted.internet import reactor
4153+ reactor.callLater(
4154+ self.machine_check_period, self.periodic_machine_check)
4155+ log.info("Started provisioning agent")
4156+ else:
4157+ log.info("Started provisioning agent without watches enabled")
4158+
4159+ def stop(self):
4160+ log.info("Stopping provisioning agent")
4161+ self._running = False
4162+ return succeed(True)
4163+
4164+ def is_running(self):
4165+ """Whether this agent is running or not."""
4166+ return self._running
4167+
4168+ @inlineCallbacks
4169+ def configure_environment(self):
4170+ """The provisioning agent configure its environment on start or change.
4171+
4172+ The environment contains the configuration th agent needs to interact
4173+ with its machine provider, in order to do its work. This configuration
4174+ data is deployed lazily over an encrypted connection upon first usage.
4175+
4176+ The agent waits for this data to exist before completing its startup.
4177+ """
4178+ try:
4179+ get_d, watch_d = self.client.get_and_watch("/environment")
4180+ environment_data, stat = yield get_d
4181+ watch_d.addCallback(self._on_environment_changed)
4182+ except NoNodeException:
4183+ # Wait till the environment node appears. play twisted gymnastics
4184+ exists_d, watch_d = self.client.exists_and_watch("/environment")
4185+ stat = yield exists_d
4186+ if stat:
4187+ environment = yield self.configure_environment()
4188+ else:
4189+ watch_d.addCallback(
4190+ lambda result: self.configure_environment())
4191+ if not stat:
4192+ environment = yield watch_d
4193+ returnValue(environment)
4194+
4195+ config = EnvironmentsConfig()
4196+ config.parse(environment_data)
4197+ returnValue(config.get_default())
4198+
4199+ @inlineCallbacks
4200+ def _on_environment_changed(self, event):
4201+ """Reload the environment if its data changes."""
4202+
4203+ if event.type_name == "deleted":
4204+ return
4205+
4206+ self.environment = yield self.configure_environment()
4207+ self.provider = self.environment.get_machine_provider()
4208+
4209+ def periodic_machine_check(self):
4210+ """A periodic checking of machine states and provider machines.
4211+
4212+ In addition to the on demand changes to zookeeper states that are
4213+ monitored by L{watch_machine_changes}, the periodic machine check
4214+ performs non zookeeper state related verification by periodically
4215+ checking the last current provider machine states against the
4216+ last known zookeeper state.
4217+
4218+ Primarily this helps in recovering from transient error conditions
4219+ which may have prevent processing of an individual machine state, as
4220+ well as verifying the current state of the provider's running machines
4221+ against the zk state, thus pruning unused resources.
4222+ """
4223+ from twisted.internet import reactor
4224+ d = self.process_machines(self._current_machines)
4225+ d.addBoth(
4226+ lambda result: reactor.callLater(
4227+ self.machine_check_period, self.periodic_machine_check))
4228+ return d
4229+
4230+ @inlineCallbacks
4231+ def watch_machine_changes(self, old_machines, new_machines):
4232+ """Watches and processes machine state changes.
4233+
4234+ This function is used to subscribe to topology changes, and
4235+ specifically changes to machines within the topology. It performs
4236+ work against the machine provider to ensure that the currently
4237+ running state of the juju cluster corresponds to the topology
4238+ via creation and deletion of machines.
4239+
4240+ The subscription utilized is a permanent one, meaning that this
4241+ function will automatically be rescheduled to run whenever a topology
4242+ state change happens that involves machines.
4243+
4244+ This functional also caches the current set of machines as an agent
4245+ instance attribute.
4246+
4247+ @param old_machines machine ids as existed in the previous topology.
4248+ @param new_machines machine ids as exist in the current topology.
4249+ """
4250+ if not self._running:
4251+ raise StopWatcher()
4252+ log.debug("Machines changed old:%s new:%s", old_machines, new_machines)
4253+ self._current_machines = new_machines
4254+ try:
4255+ yield self.process_machines(self._current_machines)
4256+ except Exception:
4257+ # Log and effectively retry later in periodic_machine_check
4258+ log.exception(
4259+ "Got unexpected exception in processing machines,"
4260+ " will retry")
4261+
4262+ @concurrent_execution_guard("_processing_machines")
4263+ @inlineCallbacks
4264+ def process_machines(self, current_machines):
4265+ """Ensure the currently running machines correspond to state.
4266+
4267+ At the end of each process_machines execution, verify that all
4268+ running machines within the provider correspond to machine_ids within
4269+ the topology. If they don't then shut them down.
4270+
4271+ Utilizes concurrent execution guard, to ensure that this is only being
4272+ executed at most once per process.
4273+ """
4274+ # XXX this is obviously broken, but the margins of 80 columns prevent
4275+ # me from describing. hint think concurrent agents, and use a lock.
4276+
4277+ # map of instance_id -> machine
4278+ try:
4279+ provider_machines = yield self.provider.get_machines()
4280+ except ProviderError:
4281+ log.exception("Cannot get machine list")
4282+ return
4283+
4284+ provider_machines = dict(
4285+ [(m.instance_id, m) for m in provider_machines])
4286+
4287+ instance_ids = []
4288+ for machine_state_id in current_machines:
4289+ try:
4290+ instance_id = yield self.process_machine(
4291+ machine_state_id, provider_machines)
4292+ except (MachineStateNotFound, ProviderError):
4293+ log.exception("Cannot process machine %s", machine_state_id)
4294+ continue
4295+ instance_ids.append(instance_id)
4296+
4297+ # Terminate all unused juju machines running within the cluster.
4298+ unused = set(provider_machines.keys()) - set(instance_ids)
4299+ for instance_id in unused:
4300+ log.info("Shutting down machine id:%s ...", instance_id)
4301+ machine = provider_machines[instance_id]
4302+
4303+ try:
4304+ yield self.provider.shutdown_machine(machine)
4305+ except ProviderError:
4306+ log.exception("Cannot shutdown machine %s", instance_id)
4307+ continue
4308+
4309+ @inlineCallbacks
4310+ def process_machine(self, machine_state_id, provider_machine_map):
4311+ """Ensure a provider machine for a machine state id.
4312+
4313+ For each machine_id in new machines which represents the current state
4314+ of the topology:
4315+
4316+ * Check to ensure its state reflects that it has been
4317+ launched. If it hasn't then create the machine and update
4318+ the state.
4319+
4320+ * Watch the machine's assigned services so that changes can
4321+ be applied to the firewall for service exposing support.
4322+ """
4323+ # fetch the machine state
4324+ machine_state = yield self.machine_state_manager.get_machine_state(
4325+ machine_state_id)
4326+ instance_id = yield machine_state.get_instance_id()
4327+
4328+ # Verify a machine id has state and is running, else launch it.
4329+ if instance_id is None or not instance_id in provider_machine_map:
4330+ log.info("Starting machine id:%s ...", machine_state.id)
4331+ constraints = yield machine_state.get_constraints()
4332+ machines = yield self.provider.start_machine(
4333+ {"machine-id": machine_state.id, "constraints": constraints})
4334+ instance_id = machines[0].instance_id
4335+ yield machine_state.set_instance_id(instance_id)
4336+
4337+ # The firewall manager also needs to be checked for any
4338+ # outstanding retries on this machine
4339+ yield self.firewall_manager.process_machine(machine_state)
4340+ returnValue(instance_id)
4341+
4342+if __name__ == '__main__':
4343+ ProvisioningAgent().run()
4344
4345=== removed file 'juju/agents/provision.py'
4346--- juju/agents/provision.py 2012-07-19 11:01:09 +0000
4347+++ juju/agents/provision.py 1970-01-01 00:00:00 +0000
4348@@ -1,239 +0,0 @@
4349-import logging
4350-
4351-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
4352-from zookeeper import NoNodeException
4353-
4354-from juju.environment.config import EnvironmentsConfig
4355-from juju.errors import ProviderError
4356-from juju.lib.twistutils import concurrent_execution_guard
4357-from juju.state.errors import MachineStateNotFound, StopWatcher
4358-from juju.state.firewall import FirewallManager
4359-from juju.state.machine import MachineStateManager
4360-from juju.state.service import ServiceStateManager
4361-
4362-from .base import BaseAgent
4363-
4364-
4365-log = logging.getLogger("juju.agents.provision")
4366-
4367-
4368-class ProvisioningAgent(BaseAgent):
4369-
4370- name = "juju-provisoning-agent"
4371-
4372- _current_machines = ()
4373-
4374- # time in seconds
4375- machine_check_period = 60
4376-
4377- def get_agent_name(self):
4378- return "provision:%s" % (self.environment.type)
4379-
4380- @inlineCallbacks
4381- def start(self):
4382- self._running = True
4383-
4384- self.environment = yield self.configure_environment()
4385- self.provider = self.environment.get_machine_provider()
4386- self.machine_state_manager = MachineStateManager(self.client)
4387- self.service_state_manager = ServiceStateManager(self.client)
4388- self.firewall_manager = FirewallManager(
4389- self.client, self.is_running, self.provider)
4390-
4391- if self.get_watch_enabled():
4392- self.machine_state_manager.watch_machine_states(
4393- self.watch_machine_changes)
4394- self.service_state_manager.watch_service_states(
4395- self.firewall_manager.watch_service_changes)
4396- from twisted.internet import reactor
4397- reactor.callLater(
4398- self.machine_check_period, self.periodic_machine_check)
4399- log.info("Started provisioning agent")
4400- else:
4401- log.info("Started provisioning agent without watches enabled")
4402-
4403- def stop(self):
4404- log.info("Stopping provisioning agent")
4405- self._running = False
4406- return succeed(True)
4407-
4408- def is_running(self):
4409- """Whether this agent is running or not."""
4410- return self._running
4411-
4412- @inlineCallbacks
4413- def configure_environment(self):
4414- """The provisioning agent configure its environment on start or change.
4415-
4416- The environment contains the configuration th agent needs to interact
4417- with its machine provider, in order to do its work. This configuration
4418- data is deployed lazily over an encrypted connection upon first usage.
4419-
4420- The agent waits for this data to exist before completing its startup.
4421- """
4422- try:
4423- get_d, watch_d = self.client.get_and_watch("/environment")
4424- environment_data, stat = yield get_d
4425- watch_d.addCallback(self._on_environment_changed)
4426- except NoNodeException:
4427- # Wait till the environment node appears. play twisted gymnastics
4428- exists_d, watch_d = self.client.exists_and_watch("/environment")
4429- stat = yield exists_d
4430- if stat:
4431- environment = yield self.configure_environment()
4432- else:
4433- watch_d.addCallback(
4434- lambda result: self.configure_environment())
4435- if not stat:
4436- environment = yield watch_d
4437- returnValue(environment)
4438-
4439- config = EnvironmentsConfig()
4440- config.parse(environment_data)
4441- returnValue(config.get_default())
4442-
4443- @inlineCallbacks
4444- def _on_environment_changed(self, event):
4445- """Reload the environment if its data changes."""
4446-
4447- if event.type_name == "deleted":
4448- return
4449-
4450- self.environment = yield self.configure_environment()
4451- self.provider = self.environment.get_machine_provider()
4452-
4453- def periodic_machine_check(self):
4454- """A periodic checking of machine states and provider machines.
4455-
4456- In addition to the on demand changes to zookeeper states that are
4457- monitored by L{watch_machine_changes}, the periodic machine check
4458- performs non zookeeper state related verification by periodically
4459- checking the last current provider machine states against the
4460- last known zookeeper state.
4461-
4462- Primarily this helps in recovering from transient error conditions
4463- which may have prevent processing of an individual machine state, as
4464- well as verifying the current state of the provider's running machines
4465- against the zk state, thus pruning unused resources.
4466- """
4467- from twisted.internet import reactor
4468- d = self.process_machines(self._current_machines)
4469- d.addBoth(
4470- lambda result: reactor.callLater(
4471- self.machine_check_period, self.periodic_machine_check))
4472- return d
4473-
4474- @inlineCallbacks
4475- def watch_machine_changes(self, old_machines, new_machines):
4476- """Watches and processes machine state changes.
4477-
4478- This function is used to subscribe to topology changes, and
4479- specifically changes to machines within the topology. It performs
4480- work against the machine provider to ensure that the currently
4481- running state of the juju cluster corresponds to the topology
4482- via creation and deletion of machines.
4483-
4484- The subscription utilized is a permanent one, meaning that this
4485- function will automatically be rescheduled to run whenever a topology
4486- state change happens that involves machines.
4487-
4488- This functional also caches the current set of machines as an agent
4489- instance attribute.
4490-
4491- @param old_machines machine ids as existed in the previous topology.
4492- @param new_machines machine ids as exist in the current topology.
4493- """
4494- if not self._running:
4495- raise StopWatcher()
4496- log.debug("Machines changed old:%s new:%s", old_machines, new_machines)
4497- self._current_machines = new_machines
4498- try:
4499- yield self.process_machines(self._current_machines)
4500- except Exception:
4501- # Log and effectively retry later in periodic_machine_check
4502- log.exception(
4503- "Got unexpected exception in processing machines,"
4504- " will retry")
4505-
4506- @concurrent_execution_guard("_processing_machines")
4507- @inlineCallbacks
4508- def process_machines(self, current_machines):
4509- """Ensure the currently running machines correspond to state.
4510-
4511- At the end of each process_machines execution, verify that all
4512- running machines within the provider correspond to machine_ids within
4513- the topology. If they don't then shut them down.
4514-
4515- Utilizes concurrent execution guard, to ensure that this is only being
4516- executed at most once per process.
4517- """
4518- # XXX this is obviously broken, but the margins of 80 columns prevent
4519- # me from describing. hint think concurrent agents, and use a lock.
4520-
4521- # map of instance_id -> machine
4522- try:
4523- provider_machines = yield self.provider.get_machines()
4524- except ProviderError:
4525- log.exception("Cannot get machine list")
4526- return
4527-
4528- provider_machines = dict(
4529- [(m.instance_id, m) for m in provider_machines])
4530-
4531- instance_ids = []
4532- for machine_state_id in current_machines:
4533- try:
4534- instance_id = yield self.process_machine(
4535- machine_state_id, provider_machines)
4536- except (MachineStateNotFound, ProviderError):
4537- log.exception("Cannot process machine %s", machine_state_id)
4538- continue
4539- instance_ids.append(instance_id)
4540-
4541- # Terminate all unused juju machines running within the cluster.
4542- unused = set(provider_machines.keys()) - set(instance_ids)
4543- for instance_id in unused:
4544- log.info("Shutting down machine id:%s ...", instance_id)
4545- machine = provider_machines[instance_id]
4546-
4547- try:
4548- yield self.provider.shutdown_machine(machine)
4549- except ProviderError:
4550- log.exception("Cannot shutdown machine %s", instance_id)
4551- continue
4552-
4553- @inlineCallbacks
4554- def process_machine(self, machine_state_id, provider_machine_map):
4555- """Ensure a provider machine for a machine state id.
4556-
4557- For each machine_id in new machines which represents the current state
4558- of the topology:
4559-
4560- * Check to ensure its state reflects that it has been
4561- launched. If it hasn't then create the machine and update
4562- the state.
4563-
4564- * Watch the machine's assigned services so that changes can
4565- be applied to the firewall for service exposing support.
4566- """
4567- # fetch the machine state
4568- machine_state = yield self.machine_state_manager.get_machine_state(
4569- machine_state_id)
4570- instance_id = yield machine_state.get_instance_id()
4571-
4572- # Verify a machine id has state and is running, else launch it.
4573- if instance_id is None or not instance_id in provider_machine_map:
4574- log.info("Starting machine id:%s ...", machine_state.id)
4575- constraints = yield machine_state.get_constraints()
4576- machines = yield self.provider.start_machine(
4577- {"machine-id": machine_state.id, "constraints": constraints})
4578- instance_id = machines[0].instance_id
4579- yield machine_state.set_instance_id(instance_id)
4580-
4581- # The firewall manager also needs to be checked for any
4582- # outstanding retries on this machine
4583- yield self.firewall_manager.process_machine(machine_state)
4584- returnValue(instance_id)
4585-
4586-if __name__ == '__main__':
4587- ProvisioningAgent().run()
4588
4589=== added directory 'juju/agents/tests'
4590=== removed directory 'juju/agents/tests'
4591=== added file 'juju/agents/tests/__init__.py'
4592--- juju/agents/tests/__init__.py 1970-01-01 00:00:00 +0000
4593+++ juju/agents/tests/__init__.py 2013-04-22 11:00:38 +0000
4594@@ -0,0 +1,1 @@
4595+#
4596
4597=== removed file 'juju/agents/tests/__init__.py'
4598--- juju/agents/tests/__init__.py 2012-06-11 15:40:48 +0000
4599+++ juju/agents/tests/__init__.py 1970-01-01 00:00:00 +0000
4600@@ -1,1 +0,0 @@
4601-#
4602
4603=== added file 'juju/agents/tests/common.py'
4604--- juju/agents/tests/common.py 1970-01-01 00:00:00 +0000
4605+++ juju/agents/tests/common.py 2013-04-22 11:00:38 +0000
4606@@ -0,0 +1,52 @@
4607+import os
4608+
4609+from twisted.internet.defer import inlineCallbacks, succeed
4610+
4611+from txzookeeper.tests.utils import deleteTree
4612+
4613+from juju.agents.base import TwistedOptionNamespace
4614+from juju.state.tests.common import StateTestBase
4615+from juju.tests.common import get_test_zookeeper_address
4616+
4617+
4618+class AgentTestBase(StateTestBase):
4619+
4620+ agent_class = None
4621+ juju_directory = None
4622+ setup_environment = True
4623+
4624+ @inlineCallbacks
4625+ def setUp(self):
4626+ self.juju_directory = self.makeDir()
4627+ yield super(AgentTestBase, self).setUp()
4628+ assert self.agent_class, "Agent Class must be specified on test"
4629+ if self.setup_environment:
4630+ yield self.push_default_config()
4631+ self.agent = self.agent_class()
4632+ self.options = yield self.get_agent_config()
4633+ self.agent.configure(self.options)
4634+ self.agent.set_watch_enabled(False)
4635+
4636+ def tearDown(self):
4637+ if self.agent.client and self.agent.client.connected:
4638+ self.agent.client.close()
4639+
4640+ if self.client.connected:
4641+ deleteTree("/", self.client.handle)
4642+ self.client.close()
4643+
4644+ def get_agent_config(self):
4645+ options = TwistedOptionNamespace()
4646+ options["juju_directory"] = self.juju_directory
4647+ options["zookeeper_servers"] = get_test_zookeeper_address()
4648+ options["session_file"] = self.makeFile()
4649+ return succeed(options)
4650+
4651+ @inlineCallbacks
4652+ def debug_pprint_tree(self, path="/", indent=1):
4653+ children = yield self.client.get_children(path)
4654+ for n in children:
4655+ print " " * indent, "/" + n
4656+ yield self.debug_pprint_tree(
4657+ os.path.join(path, n),
4658+ indent + 1)
4659
4660=== removed file 'juju/agents/tests/common.py'
4661--- juju/agents/tests/common.py 2012-06-11 15:40:48 +0000
4662+++ juju/agents/tests/common.py 1970-01-01 00:00:00 +0000
4663@@ -1,52 +0,0 @@
4664-import os
4665-
4666-from twisted.internet.defer import inlineCallbacks, succeed
4667-
4668-from txzookeeper.tests.utils import deleteTree
4669-
4670-from juju.agents.base import TwistedOptionNamespace
4671-from juju.state.tests.common import StateTestBase
4672-from juju.tests.common import get_test_zookeeper_address
4673-
4674-
4675-class AgentTestBase(StateTestBase):
4676-
4677- agent_class = None
4678- juju_directory = None
4679- setup_environment = True
4680-
4681- @inlineCallbacks
4682- def setUp(self):
4683- self.juju_directory = self.makeDir()
4684- yield super(AgentTestBase, self).setUp()
4685- assert self.agent_class, "Agent Class must be specified on test"
4686- if self.setup_environment:
4687- yield self.push_default_config()
4688- self.agent = self.agent_class()
4689- self.options = yield self.get_agent_config()
4690- self.agent.configure(self.options)
4691- self.agent.set_watch_enabled(False)
4692-
4693- def tearDown(self):
4694- if self.agent.client and self.agent.client.connected:
4695- self.agent.client.close()
4696-
4697- if self.client.connected:
4698- deleteTree("/", self.client.handle)
4699- self.client.close()
4700-
4701- def get_agent_config(self):
4702- options = TwistedOptionNamespace()
4703- options["juju_directory"] = self.juju_directory
4704- options["zookeeper_servers"] = get_test_zookeeper_address()
4705- options["session_file"] = self.makeFile()
4706- return succeed(options)
4707-
4708- @inlineCallbacks
4709- def debug_pprint_tree(self, path="/", indent=1):
4710- children = yield self.client.get_children(path)
4711- for n in children:
4712- print " " * indent, "/" + n
4713- yield self.debug_pprint_tree(
4714- os.path.join(path, n),
4715- indent + 1)
4716
4717=== added file 'juju/agents/tests/test_base.py'
4718--- juju/agents/tests/test_base.py 1970-01-01 00:00:00 +0000
4719+++ juju/agents/tests/test_base.py 2013-04-22 11:00:38 +0000
4720@@ -0,0 +1,633 @@
4721+import argparse
4722+import json
4723+import logging
4724+import os
4725+import stat
4726+import sys
4727+import yaml
4728+
4729+from twisted.application.app import AppLogger
4730+from twisted.application.service import IService, IServiceCollection
4731+from twisted.internet.defer import (
4732+ fail, succeed, Deferred, inlineCallbacks, returnValue)
4733+from twisted.python.components import Componentized
4734+from twisted.python import log
4735+
4736+import zookeeper
4737+from txzookeeper import ZookeeperClient
4738+from juju.lib.testing import TestCase
4739+from juju.lib.mocker import MATCH
4740+from juju.tests.common import get_test_zookeeper_address
4741+
4742+from juju.agents.base import (
4743+ BaseAgent, TwistedOptionNamespace, AgentRunner, AgentLogger)
4744+from juju.agents.dummy import DummyAgent
4745+from juju.errors import NoConnection, JujuError
4746+from juju.lib.zklog import ZookeeperHandler
4747+
4748+from juju.agents.tests.common import AgentTestBase
4749+
4750+MATCH_APP = MATCH(lambda x: isinstance(x, Componentized))
4751+MATCH_HANDLER = MATCH(lambda x: isinstance(x, ZookeeperHandler))
4752+
4753+
4754+class BaseAgentTest(TestCase):
4755+
4756+ @inlineCallbacks
4757+ def setUp(self):
4758+ yield super(BaseAgentTest, self).setUp()
4759+ self.juju_home = self.makeDir()
4760+ self.change_environment(JUJU_HOME=self.juju_home)
4761+
4762+ def test_as_app(self):
4763+ """The agent class can be accessed as an application."""
4764+ app = BaseAgent().as_app()
4765+ multi_service = IService(app, None)
4766+ self.assertTrue(IServiceCollection.providedBy(multi_service))
4767+ services = list(multi_service)
4768+ self.assertEqual(len(services), 1)
4769+
4770+ def test_twistd_default_options(self):
4771+ """The agent cli parsing, populates standard twistd options."""
4772+ parser = argparse.ArgumentParser()
4773+ BaseAgent.setup_options(parser)
4774+
4775+ # Daemon group
4776+ self.assertEqual(
4777+ parser.get_default("logfile"), "%s.log" % BaseAgent.name)
4778+ self.assertEqual(parser.get_default("pidfile"), "")
4779+
4780+ self.assertEqual(parser.get_default("loglevel"), "DEBUG")
4781+ self.assertFalse(parser.get_default("nodaemon"))
4782+ self.assertEqual(parser.get_default("rundir"), ".")
4783+ self.assertEqual(parser.get_default("chroot"), None)
4784+ self.assertEqual(parser.get_default("umask"), '0022')
4785+ self.assertEqual(parser.get_default("uid"), None)
4786+ self.assertEqual(parser.get_default("gid"), None)
4787+ self.assertEqual(parser.get_default("euid"), None)
4788+ self.assertEqual(parser.get_default("prefix"), BaseAgent.name)
4789+ self.assertEqual(parser.get_default("syslog"), False)
4790+
4791+ # Development Group
4792+ self.assertFalse(parser.get_default("debug"))
4793+ self.assertFalse(parser.get_default("profile"))
4794+ self.assertFalse(parser.get_default("savestats"))
4795+ self.assertEqual(parser.get_default("profiler"), "cprofile")
4796+
4797+ # Hidden defaults
4798+ self.assertEqual(parser.get_default("reactor"), "epoll")
4799+ self.assertEqual(parser.get_default("originalname"), None)
4800+
4801+ # Agent options
4802+ self.assertEqual(parser.get_default("principals"), [])
4803+ self.assertEqual(parser.get_default("zookeeper_servers"), "")
4804+ self.assertEqual(parser.get_default("juju_directory"), self.juju_home)
4805+ self.assertEqual(parser.get_default("session_file"), None)
4806+
4807+ def test_twistd_flags_correspond(self):
4808+ parser = argparse.ArgumentParser()
4809+ BaseAgent.setup_options(parser)
4810+ args = [
4811+ "--profile",
4812+ "--savestats",
4813+ "--nodaemon"]
4814+
4815+ options = parser.parse_args(args, namespace=TwistedOptionNamespace())
4816+ self.assertEqual(options.get("savestats"), True)
4817+ self.assertEqual(options.get("nodaemon"), True)
4818+ self.assertEqual(options.get("profile"), True)
4819+
4820+ def test_agent_logger(self):
4821+ parser = argparse.ArgumentParser()
4822+ BaseAgent.setup_options(parser)
4823+ log_file_path = self.makeFile()
4824+
4825+ options = parser.parse_args(
4826+ ["--logfile", log_file_path, "--session-file", self.makeFile()],
4827+ namespace=TwistedOptionNamespace())
4828+
4829+ def match_observer(observer):
4830+ return isinstance(observer.im_self, log.PythonLoggingObserver)
4831+
4832+ def cleanup(observer):
4833+ # post test cleanup of global state.
4834+ log.removeObserver(observer)
4835+ logging.getLogger().handlers = []
4836+
4837+ original_log_with_observer = log.startLoggingWithObserver
4838+
4839+ def _start_log_with_observer(observer):
4840+ self.addCleanup(cleanup, observer)
4841+ # by default logging will replace stdout/stderr
4842+ return original_log_with_observer(observer, 0)
4843+
4844+ app = self.mocker.mock()
4845+ app.getComponent(log.ILogObserver, None)
4846+ self.mocker.result(None)
4847+
4848+ start_log_with_observer = self.mocker.replace(
4849+ log.startLoggingWithObserver)
4850+ start_log_with_observer(MATCH(match_observer))
4851+ self.mocker.call(_start_log_with_observer)
4852+ self.mocker.replay()
4853+
4854+ agent_logger = AgentLogger(options)
4855+ agent_logger.start(app)
4856+
4857+ # We suppress twisted messages below the error level.
4858+ output = open(log_file_path).read()
4859+ self.assertFalse(output)
4860+
4861+ # also verify we didn't mess with the app logging.
4862+ app_log = logging.getLogger()
4863+ app_log.info("Good")
4864+
4865+ # and that twisted errors still go through.
4866+ log.err("Something bad happened")
4867+ output = open(log_file_path).read()
4868+
4869+ self.assertIn("Good", output)
4870+ self.assertIn("Something bad happened", output)
4871+
4872+ def test_custom_log_level(self):
4873+ parser = argparse.ArgumentParser()
4874+ BaseAgent.setup_options(parser)
4875+ options = parser.parse_args(
4876+ ["--loglevel", "INFO"], namespace=TwistedOptionNamespace())
4877+ self.assertEqual(options.loglevel, "INFO")
4878+
4879+ def test_twistd_option_namespace(self):
4880+ """
4881+ The twisted option namespace bridges argparse attribute access,
4882+ to twisted dictionary access for cli options.
4883+ """
4884+ options = TwistedOptionNamespace()
4885+ options.x = 1
4886+ self.assertEqual(options['x'], 1)
4887+ self.assertEqual(options.get('x'), 1)
4888+ self.assertEqual(options.get('y'), None)
4889+ self.assertRaises(KeyError, options.__getitem__, 'y')
4890+ options['y'] = 2
4891+ self.assertEqual(options.y, 2)
4892+ self.assertTrue(options.has_key('y'))
4893+ self.assertFalse(options.has_key('z'))
4894+
4895+ def test_runner_attribute_application(self):
4896+ """The agent runner retrieve the application as an attribute."""
4897+ runner = AgentRunner({})
4898+ self.assertEqual(runner.createOrGetApplication(), None)
4899+ runner.application = 21
4900+ self.assertEqual(runner.createOrGetApplication(), 21)
4901+
4902+ def test_run(self):
4903+ """Invokes the run class method on an agent.
4904+
4905+ This will create an agent instance, parse the cli args, passes them to
4906+ the agent, and starts the agent runner.
4907+ """
4908+ self.change_args(
4909+ "es-agent", "--zookeeper-servers", get_test_zookeeper_address(),
4910+ "--session-file", self.makeFile())
4911+ runner = self.mocker.patch(AgentRunner)
4912+ runner.run()
4913+ mock_agent = self.mocker.patch(BaseAgent)
4914+
4915+ def match_args(config):
4916+ self.assertEqual(config["zookeeper_servers"],
4917+ get_test_zookeeper_address())
4918+ return True
4919+
4920+ mock_agent.configure(MATCH(match_args))
4921+ self.mocker.passthrough()
4922+
4923+ self.mocker.replay()
4924+ BaseAgent.run()
4925+
4926+ def test_full_run(self):
4927+ """Verify a functional agent start via the 'run' method.
4928+
4929+ This test requires Zookeeper running on the default port of localhost.
4930+ The mocked portions are to prevent the daemon start from altering the
4931+ test environment (sys.stdout/sys.stderr, and reactor start).
4932+ """
4933+ zookeeper.set_debug_level(0)
4934+ started = Deferred()
4935+
4936+ class DummyAgent(BaseAgent):
4937+ started = False
4938+
4939+ def start(self):
4940+ started.callback(self)
4941+
4942+ def validate_started(agent):
4943+ self.assertTrue(agent.client.connected)
4944+
4945+ started.addCallback(validate_started)
4946+
4947+ self.change_args(
4948+ "es-agent", "--nodaemon",
4949+ "--zookeeper-servers", get_test_zookeeper_address(),
4950+ "--session-file", self.makeFile())
4951+ runner = self.mocker.patch(AgentRunner)
4952+ logger = self.mocker.patch(AppLogger)
4953+ logger.start(MATCH_APP)
4954+ runner.startReactor(None, sys.stdout, sys.stderr)
4955+ logger.stop()
4956+ self.mocker.replay()
4957+ DummyAgent.run()
4958+ return started
4959+
4960+ @inlineCallbacks
4961+ def test_stop_service_stub_closes_agent(self):
4962+ """The base class agent, stopService will the stop method.
4963+
4964+ Additionally it will close the agent's zookeeper client if
4965+ the client is still connected.
4966+ """
4967+ mock_agent = self.mocker.patch(BaseAgent)
4968+ mock_client = self.mocker.mock(ZookeeperClient)
4969+ session_file = self.makeFile()
4970+
4971+ # connection is closed after agent.stop invoked.
4972+ with self.mocker.order():
4973+ mock_agent.stop()
4974+ self.mocker.passthrough()
4975+
4976+ # client existence check
4977+ mock_agent.client
4978+ self.mocker.result(mock_client)
4979+
4980+ # client connected check
4981+ mock_agent.client
4982+ self.mocker.result(mock_client)
4983+ mock_client.connected
4984+ self.mocker.result(True)
4985+
4986+ # client close
4987+ mock_agent.client
4988+ self.mocker.result(mock_client)
4989+ mock_client.close()
4990+
4991+ # delete session file
4992+ mock_agent.config
4993+ self.mocker.result({"session_file": session_file})
4994+
4995+ self.mocker.replay()
4996+
4997+ agent = BaseAgent()
4998+ yield agent.stopService()
4999+ self.assertFalse(os.path.exists(session_file))
5000+
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches