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

Proposed by Martin Packman
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
.bzrignore (+12/-0)
.pc/applied-patches (+1/-0)
.pc/disable-failing-zookeeper-test.patch/juju/agents/tests/test_unit.py (+6/-0)
.pc/workaround-lxc-python-env.patch/juju/lib/lxc/__init__.py (+308/-0)
.testr.conf (+0/-4)
COPYING (+0/-661)
Makefile (+0/-44)
README (+0/-34)
bin/close-port (+0/-11)
bin/config-get (+0/-12)
bin/juju (+0/-10)
bin/juju-admin (+0/-10)
bin/juju-log (+0/-12)
bin/open-port (+0/-11)
bin/relation-get (+0/-12)
bin/relation-ids (+0/-11)
bin/relation-list (+0/-13)
bin/relation-set (+0/-13)
bin/unit-get (+0/-12)
debian/changelog (+13/-2)
debian/control (+11/-2)
debian/juju.install (+0/-1)
debian/juju.manpages (+0/-1)
debian/juju.postinst.in (+43/-0)
debian/juju.prerm.in (+23/-0)
debian/patches/series (+1/-0)
debian/patches/workaround-lxc-python-env.patch (+26/-0)
debian/rules (+36/-8)
examples/README (+0/-24)
examples/precise/mysql/config.yaml (+0/-1)
examples/precise/mysql/hooks/db-relation-joined (+0/-40)
examples/precise/mysql/hooks/install (+0/-32)
examples/precise/mysql/hooks/start (+0/-3)
examples/precise/mysql/hooks/stop (+0/-3)
examples/precise/mysql/metadata.yaml (+0/-17)
examples/precise/mysql/revision (+0/-1)
examples/precise/php/config.yaml (+0/-5)
examples/precise/php/hooks/config-changed (+0/-14)
examples/precise/php/hooks/install (+0/-49)
examples/precise/php/hooks/start (+0/-1)
examples/precise/php/hooks/stop (+0/-3)
examples/precise/php/metadata.yaml (+0/-4)
examples/precise/php/revision (+0/-1)
examples/precise/recorder/hooks/install (+0/-3)
examples/precise/recorder/hooks/juju-info-relation-changed (+0/-5)
examples/precise/recorder/hooks/juju-info-relation-departed (+0/-3)
examples/precise/recorder/hooks/juju-info-relation-joined (+0/-6)
examples/precise/recorder/metadata.yaml (+0/-9)
examples/precise/recorder/revision (+0/-1)
examples/precise/wordpress/config.yaml (+0/-7)
examples/precise/wordpress/hooks/config-changed (+0/-19)
examples/precise/wordpress/hooks/db-relation-changed (+0/-100)
examples/precise/wordpress/hooks/install (+0/-6)
examples/precise/wordpress/hooks/start (+0/-1)
examples/precise/wordpress/hooks/stop (+0/-3)
examples/precise/wordpress/metadata.yaml (+0/-10)
examples/precise/wordpress/revision (+0/-1)
juju/__init__.py (+0/-2)
juju/agents/__init__.py (+0/-1)
juju/agents/base.py (+0/-345)
juju/agents/dummy.py (+0/-16)
juju/agents/machine.py (+0/-126)
juju/agents/provision.py (+0/-239)
juju/agents/tests/__init__.py (+0/-1)
juju/agents/tests/common.py (+0/-52)
juju/agents/tests/test_base.py (+0/-633)
juju/agents/tests/test_dummy.py (+0/-13)
juju/agents/tests/test_machine.py (+0/-241)
juju/agents/tests/test_provision.py (+0/-527)
juju/agents/tests/test_unit.py (+0/-718)
juju/agents/unit.py (+0/-230)
juju/charm/__init__.py (+0/-3)
juju/charm/base.py (+0/-62)
juju/charm/bundle.py (+0/-85)
juju/charm/config.py (+0/-220)
juju/charm/directory.py (+0/-135)
juju/charm/errors.py (+0/-91)
juju/charm/metadata.py (+0/-264)
juju/charm/provider.py (+0/-28)
juju/charm/publisher.py (+0/-123)
juju/charm/repository.py (+0/-214)
juju/charm/tests/__init__.py (+0/-3)
juju/charm/tests/repository/series/configtest/config.yaml (+0/-9)
juju/charm/tests/repository/series/configtest/metadata.yaml (+0/-8)
juju/charm/tests/repository/series/configtest/revision (+0/-1)
juju/charm/tests/repository/series/dummy/.ignored (+0/-1)
juju/charm/tests/repository/series/dummy/config.yaml (+0/-5)
juju/charm/tests/repository/series/dummy/hooks/install (+0/-2)
juju/charm/tests/repository/series/dummy/metadata.yaml (+0/-5)
juju/charm/tests/repository/series/dummy/revision (+0/-1)
juju/charm/tests/repository/series/dummy/src/hello.c (+0/-7)
juju/charm/tests/repository/series/funkyblog/config.yaml (+0/-3)
juju/charm/tests/repository/series/funkyblog/metadata.yaml (+0/-17)
juju/charm/tests/repository/series/funkyblog/revision (+0/-1)
juju/charm/tests/repository/series/mysql-alternative/metadata.yaml (+0/-9)
juju/charm/tests/repository/series/mysql-alternative/revision (+0/-1)
juju/charm/tests/repository/series/mysql-format-v2/config.yaml (+0/-17)
juju/charm/tests/repository/series/mysql-format-v2/metadata.yaml (+0/-6)
juju/charm/tests/repository/series/mysql-format-v2/revision (+0/-1)
juju/charm/tests/repository/series/mysql/config.yaml (+0/-17)
juju/charm/tests/repository/series/mysql/metadata.yaml (+0/-6)
juju/charm/tests/repository/series/mysql/revision (+0/-1)
juju/charm/tests/repository/series/new/metadata.yaml (+0/-5)
juju/charm/tests/repository/series/new/revision (+0/-1)
juju/charm/tests/repository/series/old/metadata.yaml (+0/-5)
juju/charm/tests/repository/series/old/revision (+0/-1)
juju/charm/tests/repository/series/riak/metadata.yaml (+0/-11)
juju/charm/tests/repository/series/riak/revision (+0/-1)
juju/charm/tests/repository/series/varnish-alternative/hooks/install (+0/-3)
juju/charm/tests/repository/series/varnish-alternative/metadata.yaml (+0/-5)
juju/charm/tests/repository/series/varnish-alternative/revision (+0/-1)
juju/charm/tests/repository/series/varnish/metadata.yaml (+0/-5)
juju/charm/tests/repository/series/varnish/revision (+0/-1)
juju/charm/tests/repository/series/wordpress/config.yaml (+0/-3)
juju/charm/tests/repository/series/wordpress/metadata.yaml (+0/-19)
juju/charm/tests/repository/series/wordpress/revision (+0/-1)
juju/charm/tests/test_base.py (+0/-80)
juju/charm/tests/test_bundle.py (+0/-240)
juju/charm/tests/test_config.py (+0/-223)
juju/charm/tests/test_directory.py (+0/-227)
juju/charm/tests/test_errors.py (+0/-68)
juju/charm/tests/test_metadata.py (+0/-370)
juju/charm/tests/test_provider.py (+0/-31)
juju/charm/tests/test_publisher.py (+0/-223)
juju/charm/tests/test_repository.py (+0/-579)
juju/charm/tests/test_url.py (+0/-140)
juju/charm/url.py (+0/-148)
juju/control/__init__.py (+0/-192)
juju/control/add_relation.py (+0/-70)
juju/control/add_unit.py (+0/-65)
juju/control/bootstrap.py (+0/-39)
juju/control/command.py (+0/-75)
juju/control/config_get.py (+0/-95)
juju/control/config_set.py (+0/-113)
juju/control/constraints_get.py (+0/-76)
juju/control/debug_hooks.py (+0/-182)
juju/control/debug_log.py (+0/-138)
juju/control/deploy.py (+0/-194)
juju/control/destroy_environment.py (+0/-35)
juju/control/destroy_service.py (+0/-68)
juju/control/expose.py (+0/-48)
juju/control/initialize.py (+0/-46)
juju/control/legacy.py (+0/-41)
juju/control/open_tunnel.py (+0/-32)
juju/control/options.py (+0/-133)
juju/control/remove_relation.py (+0/-92)
juju/control/remove_unit.py (+0/-54)
juju/control/resolved.py (+0/-114)
juju/control/scp.py (+0/-88)
juju/control/ssh.py (+0/-102)
juju/control/status.py (+0/-726)
juju/control/terminate_machine.py (+0/-71)
juju/control/tests/__init__.py (+0/-1)
juju/control/tests/common.py (+0/-60)
juju/control/tests/sample_cluster.yaml (+0/-60)
juju/control/tests/test_add_relation.py (+0/-226)
juju/control/tests/test_add_unit.py (+0/-199)
juju/control/tests/test_admin.py (+0/-53)
juju/control/tests/test_bootstrap.py (+0/-118)
juju/control/tests/test_config_get.py (+0/-98)
juju/control/tests/test_config_set.py (+0/-291)
juju/control/tests/test_constraints_get.py (+0/-115)
juju/control/tests/test_control.py (+0/-319)
juju/control/tests/test_debug_hooks.py (+0/-218)
juju/control/tests/test_debug_log.py (+0/-271)
juju/control/tests/test_deploy.py (+0/-595)
juju/control/tests/test_destroy_environment.py (+0/-137)
juju/control/tests/test_destroy_service.py (+0/-152)
juju/control/tests/test_expose.py (+0/-93)
juju/control/tests/test_initialize.py (+0/-55)
juju/control/tests/test_open_tunnel.py (+0/-45)
juju/control/tests/test_remove_relation.py (+0/-215)
juju/control/tests/test_remove_unit.py (+0/-238)
juju/control/tests/test_resolved.py (+0/-392)
juju/control/tests/test_scp.py (+0/-138)
juju/control/tests/test_ssh.py (+0/-380)
juju/control/tests/test_status.py (+0/-925)
juju/control/tests/test_terminate_machine.py (+0/-200)
juju/control/tests/test_unexpose.py (+0/-93)
juju/control/tests/test_upgrade_charm.py (+0/-523)
juju/control/tests/test_utils.py (+0/-203)
juju/control/unexpose.py (+0/-48)
juju/control/upgrade_charm.py (+0/-141)
juju/control/utils.py (+0/-137)
juju/environment/config.py (+0/-313)
juju/environment/environment.py (+0/-58)
juju/environment/errors.py (+0/-9)
juju/environment/tests/data/lsb-release (+0/-4)
juju/environment/tests/test_config.py (+0/-796)
juju/environment/tests/test_environment.py (+0/-80)
juju/errors.py (+0/-225)
juju/ftests/__init__.py (+0/-1)
juju/ftests/test_aws.py (+0/-117)
juju/ftests/test_connection.py (+0/-91)
juju/ftests/test_ec2_provider.py (+0/-258)
juju/hooks/cli.py (+0/-297)
juju/hooks/commands.py (+0/-286)
juju/hooks/executor.py (+0/-291)
juju/hooks/invoker.py (+0/-376)
juju/hooks/protocol.py (+0/-527)
juju/hooks/scheduler.py (+0/-361)
juju/hooks/tests/hooks/echo-hook (+0/-5)
juju/hooks/tests/hooks/fail-hook (+0/-4)
juju/hooks/tests/hooks/hanging-hook (+0/-3)
juju/hooks/tests/hooks/sleep-hook (+0/-3)
juju/hooks/tests/hooks/success-hook (+0/-3)
juju/hooks/tests/test_arguments.py (+0/-163)
juju/hooks/tests/test_cli.py (+0/-444)
juju/hooks/tests/test_communications.py (+0/-431)
juju/hooks/tests/test_executor.py (+0/-397)
juju/hooks/tests/test_invoker.py (+0/-2142)
juju/hooks/tests/test_scheduler.py (+0/-738)
juju/lib/cache.py (+0/-18)
juju/lib/filehash.py (+0/-18)
juju/lib/format.py (+0/-137)
juju/lib/loader.py (+0/-38)
juju/lib/lxc/__init__.py (+0/-308)
juju/lib/lxc/tests/data/sample_fstab (+0/-2)
juju/lib/lxc/tests/data/sample_fstab_withdevtmpfs (+0/-3)
juju/lib/lxc/tests/test_lxc.py (+0/-300)
juju/lib/mocker.py (+0/-2162)
juju/lib/pick.py (+0/-49)
juju/lib/port.py (+13/-0)
juju/lib/schema.py (+0/-326)
juju/lib/serializer.py (+0/-21)
juju/lib/service.py (+0/-160)
juju/lib/statemachine.py (+0/-417)
juju/lib/testing.py (+0/-230)
juju/lib/tests/test_cache.py (+0/-20)
juju/lib/tests/test_filehash.py (+0/-13)
juju/lib/tests/test_format.py (+0/-308)
juju/lib/tests/test_loader.py (+0/-33)
juju/lib/tests/test_pick.py (+0/-59)
juju/lib/tests/test_port.py (+32/-0)
juju/lib/tests/test_schema.py (+0/-484)
juju/lib/tests/test_service.py (+0/-116)
juju/lib/tests/test_statemachine.py (+0/-473)
juju/lib/tests/test_twistutils.py (+0/-157)
juju/lib/tests/test_under.py (+0/-29)
juju/lib/tests/test_zk.py (+0/-123)
juju/lib/tests/test_zklog.py (+0/-259)
juju/lib/twistutils.py (+0/-69)
juju/lib/under.py (+0/-12)
juju/lib/zk.py (+0/-253)
juju/lib/zklog.py (+0/-187)
juju/machine/__init__.py (+0/-17)
juju/machine/errors.py (+0/-6)
juju/machine/tests/__init__.py (+0/-1)
juju/machine/tests/data/test_get_container (+0/-44)
juju/machine/tests/test_constraints.py (+1/-0)
juju/machine/tests/test_machine.py (+0/-20)
juju/machine/tests/test_unit_deployment.py (+0/-428)
juju/machine/unit.py (+0/-296)
juju/providers/__init__.py (+0/-1)
juju/providers/common/__init__.py (+0/-1)
juju/providers/common/base.py (+0/-224)
juju/providers/common/bootstrap.py (+0/-58)
juju/providers/common/cloudinit.py (+0/-410)
juju/providers/common/connect.py (+0/-86)
juju/providers/common/files.py (+0/-40)
juju/providers/common/findzookeepers.py (+0/-41)
juju/providers/common/instance_type.py (+0/-55)
juju/providers/common/launch.py (+0/-117)
juju/providers/common/state.py (+0/-56)
juju/providers/common/tests/data/cloud_init_bootstrap (+0/-61)
juju/providers/common/tests/data/cloud_init_bootstrap_testing (+64/-0)
juju/providers/common/tests/data/cloud_init_bootstrap_zookeepers (+0/-61)
juju/providers/common/tests/data/cloud_init_branch (+0/-39)
juju/providers/common/tests/data/cloud_init_branch_trunk (+0/-39)
juju/providers/common/tests/data/cloud_init_distro (+0/-36)
juju/providers/common/tests/data/cloud_init_no_machine_id (+0/-10)
juju/providers/common/tests/data/cloud_init_normal (+0/-13)
juju/providers/common/tests/data/cloud_init_ppa (+0/-38)
juju/providers/common/tests/data/cloud_init_ppa_apt_proxy (+0/-39)
juju/providers/common/tests/data/cloud_init_proposed (+0/-37)
juju/providers/common/tests/test_base.py (+0/-155)
juju/providers/common/tests/test_bootstrap.py (+0/-117)
juju/providers/common/tests/test_cloudinit.py (+0/-332)
juju/providers/common/tests/test_connect.py (+0/-239)
juju/providers/common/tests/test_files.py (+0/-62)
juju/providers/common/tests/test_findzookeepers.py (+0/-128)
juju/providers/common/tests/test_instance_type.py (+0/-71)
juju/providers/common/tests/test_launch.py (+0/-105)
juju/providers/common/tests/test_state.py (+0/-108)
juju/providers/common/tests/test_utils.py (+0/-117)
juju/providers/common/utils.py (+0/-148)
juju/providers/dummy.py (+0/-206)
juju/providers/ec2/__init__.py (+0/-225)
juju/providers/ec2/files.py (+0/-122)
juju/providers/ec2/launch.py (+0/-104)
juju/providers/ec2/machine.py (+0/-22)
juju/providers/ec2/securitygroup.py (+0/-136)
juju/providers/ec2/tests/__init__.py (+0/-1)
juju/providers/ec2/tests/common.py (+0/-209)
juju/providers/ec2/tests/data/bootstrap_cloud_init (+0/-61)
juju/providers/ec2/tests/data/launch_cloud_init (+0/-35)
juju/providers/ec2/tests/data/launch_cloud_init_branch (+0/-39)
juju/providers/ec2/tests/data/launch_cloud_init_ppa (+0/-37)
juju/providers/ec2/tests/data/lucid.txt (+0/-16)
juju/providers/ec2/tests/data/natty.txt (+0/-21)
juju/providers/ec2/tests/test_bootstrap.py (+0/-153)
juju/providers/ec2/tests/test_files.py (+0/-237)
juju/providers/ec2/tests/test_findzookeeper.py (+0/-138)
juju/providers/ec2/tests/test_getmachines.py (+0/-177)
juju/providers/ec2/tests/test_launch.py (+0/-313)
juju/providers/ec2/tests/test_machine.py (+0/-21)
juju/providers/ec2/tests/test_provider.py (+0/-288)
juju/providers/ec2/tests/test_securitygroup.py (+0/-164)
juju/providers/ec2/tests/test_shutdown.py (+0/-221)
juju/providers/ec2/tests/test_state.py (+0/-129)
juju/providers/ec2/tests/test_utils.py (+0/-283)
juju/providers/ec2/utils.py (+0/-171)
juju/providers/local/__init__.py (+0/-272)
juju/providers/local/agent.py (+0/-82)
juju/providers/local/files.py (+0/-117)
juju/providers/local/machine.py (+0/-10)
juju/providers/local/network.py (+0/-101)
juju/providers/local/pkg.py (+0/-18)
juju/providers/local/tests/__init__.py (+0/-1)
juju/providers/local/tests/test_agent.py (+0/-97)
juju/providers/local/tests/test_container.py (+0/-37)
juju/providers/local/tests/test_files.py (+0/-142)
juju/providers/local/tests/test_machine.py (+0/-12)
juju/providers/local/tests/test_network.py (+0/-137)
juju/providers/local/tests/test_pkg.py (+0/-27)
juju/providers/local/tests/test_provider.py (+0/-217)
juju/providers/maas/maas.py (+29/-8)
juju/providers/maas/provider.py (+4/-3)
juju/providers/maas/tests/test_launch.py (+23/-4)
juju/providers/maas/tests/test_maas.py (+38/-2)
juju/providers/maas/tests/test_provider.py (+28/-3)
juju/providers/openstack/__init__.py (+0/-5)
juju/providers/openstack/_ssl.py (+0/-21)
juju/providers/openstack/client.py (+0/-526)
juju/providers/openstack/credentials.py (+0/-101)
juju/providers/openstack/files.py (+0/-84)
juju/providers/openstack/launch.py (+0/-152)
juju/providers/openstack/machine.py (+0/-68)
juju/providers/openstack/ports.py (+0/-180)
juju/providers/openstack/provider.py (+0/-218)
juju/providers/openstack/tests/__init__.py (+0/-233)
juju/providers/openstack/tests/test_bootstrap.py (+0/-222)
juju/providers/openstack/tests/test_client.py (+0/-315)
juju/providers/openstack/tests/test_credentials.py (+0/-216)
juju/providers/openstack/tests/test_files.py (+0/-100)
juju/providers/openstack/tests/test_getmachines.py (+0/-115)
juju/providers/openstack/tests/test_launch.py (+0/-188)
juju/providers/openstack/tests/test_machine.py (+0/-110)
juju/providers/openstack/tests/test_ports.py (+0/-408)
juju/providers/openstack/tests/test_provider.py (+0/-194)
juju/providers/openstack/tests/test_shutdown.py (+0/-130)
juju/providers/openstack/tests/test_state.py (+0/-57)
juju/providers/openstack_s3/__init__.py (+0/-58)
juju/providers/openstack_s3/tests/test_provider.py (+0/-89)
juju/providers/orchestra/__init__.py (+0/-132)
juju/providers/orchestra/cobbler.py (+0/-284)
juju/providers/orchestra/digestauth.py (+0/-106)
juju/providers/orchestra/files.py (+0/-84)
juju/providers/orchestra/launch.py (+0/-57)
juju/providers/orchestra/machine.py (+0/-17)
juju/providers/orchestra/tests/__init__.py (+0/-1)
juju/providers/orchestra/tests/common.py (+0/-179)
juju/providers/orchestra/tests/data/bootstrap_user_data (+0/-61)
juju/providers/orchestra/tests/data/launch_user_data (+0/-34)
juju/providers/orchestra/tests/data/server.crt (+0/-12)
juju/providers/orchestra/tests/data/server.key (+0/-15)
juju/providers/orchestra/tests/test_bootstrap.py (+0/-158)
juju/providers/orchestra/tests/test_cobbler.py (+0/-1033)
juju/providers/orchestra/tests/test_digestauth.py (+0/-304)
juju/providers/orchestra/tests/test_files.py (+0/-205)
juju/providers/orchestra/tests/test_findzookeepers.py (+0/-88)
juju/providers/orchestra/tests/test_getmachines.py (+0/-105)
juju/providers/orchestra/tests/test_launch.py (+0/-163)
juju/providers/orchestra/tests/test_machine.py (+0/-25)
juju/providers/orchestra/tests/test_provider.py (+0/-146)
juju/providers/orchestra/tests/test_shutdown.py (+0/-192)
juju/providers/orchestra/tests/test_state.py (+0/-48)
juju/providers/tests/__init__.py (+0/-1)
juju/providers/tests/test_dummy.py (+0/-175)
juju/state/__init__.py (+0/-1)
juju/state/agent.py (+0/-42)
juju/state/auth.py (+0/-56)
juju/state/base.py (+0/-143)
juju/state/charm.py (+0/-116)
juju/state/endpoint.py (+0/-47)
juju/state/environment.py (+0/-204)
juju/state/errors.py (+0/-428)
juju/state/firewall.py (+0/-314)
juju/state/hook.py (+0/-434)
juju/state/initialize.py (+0/-77)
juju/state/machine.py (+0/-282)
juju/state/placement.py (+0/-89)
juju/state/relation.py (+0/-927)
juju/state/security.py (+0/-444)
juju/state/service.py (+0/-1592)
juju/state/sshclient.py (+0/-135)
juju/state/sshforward.py (+0/-160)
juju/state/tests/__init__.py (+0/-1)
juju/state/tests/common.py (+0/-55)
juju/state/tests/test_agent.py (+0/-94)
juju/state/tests/test_auth.py (+0/-89)
juju/state/tests/test_base.py (+0/-435)
juju/state/tests/test_charm.py (+0/-183)
juju/state/tests/test_endpoint.py (+0/-37)
juju/state/tests/test_environment.py (+0/-255)
juju/state/tests/test_errors.py (+0/-283)
juju/state/tests/test_firewall.py (+0/-697)
juju/state/tests/test_hook.py (+0/-896)
juju/state/tests/test_initialize.py (+0/-92)
juju/state/tests/test_machine.py (+0/-784)
juju/state/tests/test_placement.py (+0/-91)
juju/state/tests/test_relation.py (+0/-1916)
juju/state/tests/test_security.py (+0/-546)
juju/state/tests/test_service.py (+0/-2909)
juju/state/tests/test_sshclient.py (+0/-326)
juju/state/tests/test_sshforward.py (+0/-244)
juju/state/tests/test_topology.py (+0/-1266)
juju/state/tests/test_utils.py (+0/-708)
juju/state/topology.py (+0/-574)
juju/state/utils.py (+0/-279)
juju/tests/common.py (+0/-53)
juju/tests/test_errors.py (+0/-165)
juju/unit/__init__.py (+0/-1)
juju/unit/address.py (+0/-124)
juju/unit/charm.py (+0/-40)
juju/unit/deploy.py (+5/-2)
juju/unit/lifecycle.py (+0/-756)
juju/unit/tests/test_address.py (+0/-206)
juju/unit/tests/test_charm.py (+0/-167)
juju/unit/tests/test_deploy.py (+25/-10)
juju/unit/tests/test_lifecycle.py (+0/-1444)
juju/unit/tests/test_workflow.py (+0/-1074)
juju/unit/workflow.py (+0/-598)
misc/bash_completion.d/juju (+0/-107)
misc/devel-tools/juju-inspect-local-provider (+0/-63)
setup.py (+0/-43)
test (+0/-52)
To merge this branch: bzr merge lp:~gz/ubuntu/raring/juju/0.7
Reviewer Review Type Date Requested Status
James Page Needs Fixing
Ubuntu branches 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
31. By Martin Packman

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

Revision history for this message
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
32. By Martin Packman

Unbreak agent tests after quilt mishap

33. By Martin Packman

Collapse debian changelog to one distro relevent update for raring

34. By Mark Mims

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

35. By Martin Packman

Fix get-orig-source rule

36. By Martin Packman

Merge in alternatives support for manpages and bash completion

37. By Martin Packman

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

Revision history for this message
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
38. By Martin Packman

Fixup rules file after binary package rename

Revision history for this message
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.

Revision history for this message
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
39. By Martin Packman

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

40. By Martin Packman

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2013-04-22 11:00:38 +0000
@@ -0,0 +1,12 @@
1/docs/build
2/docs/source/generated
3/_trial_temp
4/_trial_temp-*
5/tags
6zookeeper.log
7.emacs.desktop
8.emacs.desktop.lock
9.coverage
10htmlcov
11/TAGS
12/.testrepository
013
=== modified file '.pc/applied-patches'
--- .pc/applied-patches 2012-11-06 20:44:41 +0000
+++ .pc/applied-patches 2013-04-22 11:00:38 +0000
@@ -1,1 +1,2 @@
1disable-failing-zookeeper-test.patch1disable-failing-zookeeper-test.patch
2workaround-lxc-python-env.patch
23
=== modified file '.pc/disable-failing-zookeeper-test.patch/juju/agents/tests/test_unit.py'
--- .pc/disable-failing-zookeeper-test.patch/juju/agents/tests/test_unit.py 2012-10-02 13:05:17 +0000
+++ .pc/disable-failing-zookeeper-test.patch/juju/agents/tests/test_unit.py 2013-04-22 11:00:38 +0000
@@ -9,7 +9,9 @@
9from juju.charm import get_charm_from_path9from juju.charm import get_charm_from_path
10from juju.charm.url import CharmURL10from juju.charm.url import CharmURL
11from juju.errors import JujuError11from juju.errors import JujuError
12from juju.hooks.executor import HookExecutor
12from juju.lib import serializer13from juju.lib import serializer
14
13from juju.state.environment import GlobalSettingsStateManager15from juju.state.environment import GlobalSettingsStateManager
14from juju.state.errors import ServiceStateNotFound16from juju.state.errors import ServiceStateNotFound
15from juju.state.service import NO_HOOKS, RETRY_HOOKS17from juju.state.service import NO_HOOKS, RETRY_HOOKS
@@ -30,11 +32,15 @@
3032
31 @inlineCallbacks33 @inlineCallbacks
32 def setUp(self):34 def setUp(self):
35 self.patch(HookExecutor,
36 "LOCK_PATH",
37 os.path.join(self.makeDir(), "hook.lock"))
33 yield super(UnitAgentTestBase, self).setUp()38 yield super(UnitAgentTestBase, self).setUp()
34 settings = GlobalSettingsStateManager(self.client)39 settings = GlobalSettingsStateManager(self.client)
35 yield settings.set_provider_type("dummy")40 yield settings.set_provider_type("dummy")
36 self.change_environment(41 self.change_environment(
37 PATH=get_cli_environ_path(),42 PATH=get_cli_environ_path(),
43 JUJU_ENV_UUID="snowflake",
38 JUJU_UNIT_NAME="mysql/0")44 JUJU_UNIT_NAME="mysql/0")
3945
40 @inlineCallbacks46 @inlineCallbacks
4147
=== added directory '.pc/workaround-lxc-python-env.patch'
=== added file '.pc/workaround-lxc-python-env.patch/.timestamp'
=== added directory '.pc/workaround-lxc-python-env.patch/juju'
=== added directory '.pc/workaround-lxc-python-env.patch/juju/lib'
=== added directory '.pc/workaround-lxc-python-env.patch/juju/lib/lxc'
=== added file '.pc/workaround-lxc-python-env.patch/juju/lib/lxc/__init__.py'
--- .pc/workaround-lxc-python-env.patch/juju/lib/lxc/__init__.py 1970-01-01 00:00:00 +0000
+++ .pc/workaround-lxc-python-env.patch/juju/lib/lxc/__init__.py 2013-04-22 11:00:38 +0000
@@ -0,0 +1,308 @@
1import os
2import pipes
3import subprocess
4import sys
5import tempfile
6
7from twisted.internet.defer import inlineCallbacks, returnValue
8from twisted.internet.threads import deferToThread
9
10from juju.errors import JujuError
11
12DATA_PATH = os.path.abspath(
13 os.path.join(os.path.dirname(__file__), "data"))
14
15CONTAINER_OPTIONS_DOC = """
16The following options are expected.
17
18JUJU_CONTAINER_NAME: Applied as the hostname of the machine.
19
20JUJU_ORIGIN: Where to obtain the containers version of juju from.
21 (ppa, distro or branch). When 'branch' JUJU_SOURCE should
22 be set to the location of a bzr(1) accessible branch.
23
24JUJU_PUBLIC_KEY: An SSH public key used by the ubuntu account for
25 interaction with the container.
26
27"""
28
29DEVTMPFS_LINE = """devtmpfs dev devtmpfs mode=0755,nosuid 0 0"""
30
31# Used to specify the name of the default LXC template used
32# for container creation
33DEFAULT_TEMPLATE = "ubuntu-cloud"
34
35
36class LXCError(JujuError):
37 """Indicates a low level error with an LXC container"""
38
39
40def _cmd(args):
41 p = subprocess.Popen(
42 args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
43 stdout_data, _ = p.communicate()
44 r = p.returncode
45 if r != 0:
46 # read the stdout/err streams and show the user
47 print >>sys.stderr, stdout_data
48 raise LXCError(stdout_data)
49 return (r, stdout_data)
50
51
52# Wrapped lxc cli primitives
53def _lxc_create(container_name,
54 template,
55 release,
56 cloud_init_file=None,
57 auth_key=None,
58 release_stream=None):
59 # the -- argument indicates the last parameters are passed
60 # to the template and not handled by lxc-create
61 args = ["sudo", "lxc-create",
62 "-n", container_name,
63 "-t", template,
64 "--",
65 "--debug", # Debug erors / set -x
66 "--hostid", container_name,
67 "-r", release]
68 if cloud_init_file:
69 args.extend(("--userdata", cloud_init_file))
70 if auth_key:
71 args.extend(("-S", auth_key))
72 if release_stream:
73 args.extend(("-s", release_stream))
74 return _cmd(args)
75
76
77def _lxc_start(container_name, debug_log=None, console_log=None):
78 args = ["sudo", "lxc-start", "--daemon", "-n", container_name]
79 if console_log:
80 args.extend(["-c", console_log])
81 if debug_log:
82 args.extend(["-l", "DEBUG", "-o", debug_log])
83 return _cmd(args)
84
85
86def _lxc_stop(container_name):
87 _cmd(["sudo", "lxc-stop", "-n", container_name])
88
89
90def _lxc_destroy(container_name):
91 return _cmd(["sudo", "lxc-destroy", "-n", container_name])
92
93
94def _lxc_ls():
95 _, output = _cmd(["lxc-ls"])
96 output = output.replace("\n", " ")
97 return set([c for c in output.split(" ") if c])
98
99
100def _lxc_wait(container_name, state="RUNNING"):
101 """Wait for container to be in a given state RUNNING|STOPPED."""
102
103 def wait(container_name):
104 rc, _ = _cmd(["sudo", "lxc-wait",
105 "-n", container_name,
106 "-s", state])
107 return rc == 0
108
109 return deferToThread(wait, container_name)
110
111
112def _lxc_clone(existing_container_name, new_container_name):
113 return _cmd(["sudo", "lxc-clone", "-o", existing_container_name, "-n",
114 new_container_name])
115
116
117def _customize_container(customize_script, container_root):
118 if not os.path.isdir(container_root):
119 raise LXCError("Expect container root directory: %s" %
120 container_root)
121
122 # write the container scripts into the container
123 fd, in_path = tempfile.mkstemp(prefix=os.path.basename(customize_script),
124 dir=os.path.join(container_root, "tmp"))
125
126 os.write(fd, open(customize_script, "r").read())
127 os.close(fd)
128 os.chmod(in_path, 0755)
129
130 args = ["sudo", "chroot", container_root,
131 os.path.join("/tmp", os.path.basename(in_path))]
132 return _cmd(args)
133
134
135def validate_path(pathname):
136 if not os.access(pathname, os.R_OK):
137 raise LXCError("Invalid or unreadable file: %s" % pathname)
138
139
140@inlineCallbacks
141def get_containers(prefix):
142 """Return a dictionary of containers key names to runtime boolean value.
143
144 :param prefix: Optionally specify a prefix that the container should
145 match any returned containers.
146 """
147 _, output = yield deferToThread(_cmd, ["lxc-ls"])
148
149 containers = {}
150 for i in filter(None, output.split("\n")):
151 if i in containers:
152 containers[i] = True
153 else:
154 containers[i] = False
155
156 if prefix:
157 remove = [k for k in containers.keys() if not
158 k.startswith(prefix)]
159 map(containers.pop, remove)
160
161 returnValue(containers)
162
163
164def ensure_devtmpfs_fstab(container_home):
165 """ Workaround for bug in older LXC - We need to force mounting devtmpfs
166 if it is not already in the rootfs, before starting.
167 """
168 rootfs = os.path.join(container_home, 'rootfs')
169 devpts = os.path.join(rootfs, 'dev', 'pts')
170 if not os.path.exists(devpts):
171 fstab_path = os.path.join(container_home, 'fstab')
172 if os.path.exists(fstab_path):
173 with open(fstab_path) as fstab:
174 for line in fstab:
175 if line.startswith('devtmpfs'):
176 # Line already there, we are done
177 return
178 mode = 'a'
179 else:
180 mode = 'w'
181 with open(fstab_path, mode) as fstab:
182 print >>fstab, DEVTMPFS_LINE
183
184
185class LXCContainer(object):
186 def __init__(self,
187 container_name,
188 series,
189 cloud_init=None,
190 debug_log=None,
191 console_log=None,
192 release_stream="released"):
193 """Create an LXCContainer
194
195 :param container_name: should be unique within the system
196
197 :param series: distro release series (oneiric, precise, etc)
198
199 :param cloud_init: full string of cloud-init userdata
200
201 See :data CONFIG_OPTIONS_DOC: explain how these values map
202 into the container in more detail.
203 """
204 self.container_name = container_name
205 self.debug_log = debug_log
206 self.console_log = console_log
207 self.cloud_init = cloud_init
208 self.series = series
209 self.release_stream = release_stream
210
211 @property
212 def container_home(self):
213 return "/var/lib/lxc/%s" % self.container_name
214
215 @property
216 def rootfs(self):
217 return "%s/rootfs/" % self.container_home
218
219 def _p(self, path):
220 if path[0] == "/":
221 path = path[1:]
222 return os.path.join(self.rootfs, path)
223
224 def is_constructed(self):
225 """Does the lxc image exist """
226 return os.path.exists(self.rootfs)
227
228 @inlineCallbacks
229 def is_running(self):
230 """Is the lxc image running."""
231 state = yield get_containers(None)
232 returnValue(state.get(self.container_name))
233
234 def execute(self, args):
235 if not isinstance(args, (list, tuple)):
236 args = [args, ]
237
238 args = ["sudo", "chroot", self.rootfs] + args
239 return _cmd(args)
240
241 def _create_wait(self):
242 """Create the container synchronously."""
243 if self.is_constructed():
244 return
245
246 with tempfile.NamedTemporaryFile() as fh:
247 if self.cloud_init:
248 fh.write(self.cloud_init.render())
249 cloud_init_file = fh.name
250 else:
251 cloud_init_file = None
252 fh.flush()
253 _lxc_create(self.container_name,
254 template=DEFAULT_TEMPLATE,
255 cloud_init_file=cloud_init_file,
256 release=self.series)
257
258 ensure_devtmpfs_fstab(self.container_home)
259
260 @inlineCallbacks
261 def create(self):
262 # open the template file and create a new temp processed
263 yield deferToThread(self._create_wait)
264
265 def _clone_wait(self, container_name):
266 """Return a cloned LXCContainer with a the new container name.
267
268 This method is synchronous and will provision the new image
269 blocking till done.
270 """
271 if not self.is_constructed():
272 raise LXCError("Attempted to clone container "
273 "that hasn't been had create() called")
274
275 container = LXCContainer(container_name,
276 series=self.series,
277 cloud_init=self.cloud_init,
278 debug_log=self.debug_log,
279 console_log=self.console_log,
280 release_stream=self.release_stream)
281
282 if not container.is_constructed():
283 _lxc_clone(self.container_name, container_name)
284 return container
285
286 def clone(self, container_name):
287 return deferToThread(self._clone_wait, container_name)
288
289 @inlineCallbacks
290 def run(self):
291 if not self.is_constructed():
292 raise LXCError("Attempting to run a container that "
293 "hasn't been created or cloned.")
294
295 yield deferToThread(
296 _lxc_start, self.container_name,
297 debug_log=self.debug_log, console_log=self.console_log)
298 yield _lxc_wait(self.container_name, "RUNNING")
299
300 @inlineCallbacks
301 def stop(self):
302 yield deferToThread(_lxc_stop, self.container_name)
303 yield _lxc_wait(self.container_name, "STOPPED")
304
305 @inlineCallbacks
306 def destroy(self):
307 yield self.stop()
308 yield deferToThread(_lxc_destroy, self.container_name)
0309
=== added file '.testr.conf'
--- .testr.conf 1970-01-01 00:00:00 +0000
+++ .testr.conf 2013-04-22 11:00:38 +0000
@@ -0,0 +1,4 @@
1[DEFAULT]
2test_command=./test --reporter=subunit $LISTOPT $IDLIST
3test_list_option=-n
4
05
=== removed file '.testr.conf'
--- .testr.conf 2012-07-19 11:01:09 +0000
+++ .testr.conf 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1[DEFAULT]
2test_command=./test --reporter=subunit $LISTOPT $IDLIST
3test_list_option=-n
4
50
=== added file 'COPYING'
--- COPYING 1970-01-01 00:00:00 +0000
+++ COPYING 2013-04-22 11:00:38 +0000
@@ -0,0 +1,661 @@
1 GNU AFFERO GENERAL PUBLIC LICENSE
2 Version 3, 19 November 2007
3
4 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5 Everyone is permitted to copy and distribute verbatim copies
6 of this license document, but changing it is not allowed.
7
8 Preamble
9
10 The GNU Affero General Public License is a free, copyleft license for
11software and other kinds of works, specifically designed to ensure
12cooperation with the community in the case of network server software.
13
14 The licenses for most software and other practical works are designed
15to take away your freedom to share and change the works. By contrast,
16our General Public Licenses are intended to guarantee your freedom to
17share and change all versions of a program--to make sure it remains free
18software for all its users.
19
20 When we speak of free software, we are referring to freedom, not
21price. Our General Public Licenses are designed to make sure that you
22have the freedom to distribute copies of free software (and charge for
23them if you wish), that you receive source code or can get it if you
24want it, that you can change the software or use pieces of it in new
25free programs, and that you know you can do these things.
26
27 Developers that use our General Public Licenses protect your rights
28with two steps: (1) assert copyright on the software, and (2) offer
29you this License which gives you legal permission to copy, distribute
30and/or modify the software.
31
32 A secondary benefit of defending all users' freedom is that
33improvements made in alternate versions of the program, if they
34receive widespread use, become available for other developers to
35incorporate. Many developers of free software are heartened and
36encouraged by the resulting cooperation. However, in the case of
37software used on network servers, this result may fail to come about.
38The GNU General Public License permits making a modified version and
39letting the public access it on a server without ever releasing its
40source code to the public.
41
42 The GNU Affero General Public License is designed specifically to
43ensure that, in such cases, the modified source code becomes available
44to the community. It requires the operator of a network server to
45provide the source code of the modified version running there to the
46users of that server. Therefore, public use of a modified version, on
47a publicly accessible server, gives the public access to the source
48code of the modified version.
49
50 An older license, called the Affero General Public License and
51published by Affero, was designed to accomplish similar goals. This is
52a different license, not a version of the Affero GPL, but Affero has
53released a new version of the Affero GPL which permits relicensing under
54this license.
55
56 The precise terms and conditions for copying, distribution and
57modification follow.
58
59 TERMS AND CONDITIONS
60
61 0. Definitions.
62
63 "This License" refers to version 3 of the GNU Affero General Public License.
64
65 "Copyright" also means copyright-like laws that apply to other kinds of
66works, such as semiconductor masks.
67
68 "The Program" refers to any copyrightable work licensed under this
69License. Each licensee is addressed as "you". "Licensees" and
70"recipients" may be individuals or organizations.
71
72 To "modify" a work means to copy from or adapt all or part of the work
73in a fashion requiring copyright permission, other than the making of an
74exact copy. The resulting work is called a "modified version" of the
75earlier work or a work "based on" the earlier work.
76
77 A "covered work" means either the unmodified Program or a work based
78on the Program.
79
80 To "propagate" a work means to do anything with it that, without
81permission, would make you directly or secondarily liable for
82infringement under applicable copyright law, except executing it on a
83computer or modifying a private copy. Propagation includes copying,
84distribution (with or without modification), making available to the
85public, and in some countries other activities as well.
86
87 To "convey" a work means any kind of propagation that enables other
88parties to make or receive copies. Mere interaction with a user through
89a computer network, with no transfer of a copy, is not conveying.
90
91 An interactive user interface displays "Appropriate Legal Notices"
92to the extent that it includes a convenient and prominently visible
93feature that (1) displays an appropriate copyright notice, and (2)
94tells the user that there is no warranty for the work (except to the
95extent that warranties are provided), that licensees may convey the
96work under this License, and how to view a copy of this License. If
97the interface presents a list of user commands or options, such as a
98menu, a prominent item in the list meets this criterion.
99
100 1. Source Code.
101
102 The "source code" for a work means the preferred form of the work
103for making modifications to it. "Object code" means any non-source
104form of a work.
105
106 A "Standard Interface" means an interface that either is an official
107standard defined by a recognized standards body, or, in the case of
108interfaces specified for a particular programming language, one that
109is widely used among developers working in that language.
110
111 The "System Libraries" of an executable work include anything, other
112than the work as a whole, that (a) is included in the normal form of
113packaging a Major Component, but which is not part of that Major
114Component, and (b) serves only to enable use of the work with that
115Major Component, or to implement a Standard Interface for which an
116implementation is available to the public in source code form. A
117"Major Component", in this context, means a major essential component
118(kernel, window system, and so on) of the specific operating system
119(if any) on which the executable work runs, or a compiler used to
120produce the work, or an object code interpreter used to run it.
121
122 The "Corresponding Source" for a work in object code form means all
123the source code needed to generate, install, and (for an executable
124work) run the object code and to modify the work, including scripts to
125control those activities. However, it does not include the work's
126System Libraries, or general-purpose tools or generally available free
127programs which are used unmodified in performing those activities but
128which are not part of the work. For example, Corresponding Source
129includes interface definition files associated with source files for
130the work, and the source code for shared libraries and dynamically
131linked subprograms that the work is specifically designed to require,
132such as by intimate data communication or control flow between those
133subprograms and other parts of the work.
134
135 The Corresponding Source need not include anything that users
136can regenerate automatically from other parts of the Corresponding
137Source.
138
139 The Corresponding Source for a work in source code form is that
140same work.
141
142 2. Basic Permissions.
143
144 All rights granted under this License are granted for the term of
145copyright on the Program, and are irrevocable provided the stated
146conditions are met. This License explicitly affirms your unlimited
147permission to run the unmodified Program. The output from running a
148covered work is covered by this License only if the output, given its
149content, constitutes a covered work. This License acknowledges your
150rights of fair use or other equivalent, as provided by copyright law.
151
152 You may make, run and propagate covered works that you do not
153convey, without conditions so long as your license otherwise remains
154in force. You may convey covered works to others for the sole purpose
155of having them make modifications exclusively for you, or provide you
156with facilities for running those works, provided that you comply with
157the terms of this License in conveying all material for which you do
158not control copyright. Those thus making or running the covered works
159for you must do so exclusively on your behalf, under your direction
160and control, on terms that prohibit them from making any copies of
161your copyrighted material outside their relationship with you.
162
163 Conveying under any other circumstances is permitted solely under
164the conditions stated below. Sublicensing is not allowed; section 10
165makes it unnecessary.
166
167 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168
169 No covered work shall be deemed part of an effective technological
170measure under any applicable law fulfilling obligations under article
17111 of the WIPO copyright treaty adopted on 20 December 1996, or
172similar laws prohibiting or restricting circumvention of such
173measures.
174
175 When you convey a covered work, you waive any legal power to forbid
176circumvention of technological measures to the extent such circumvention
177is effected by exercising rights under this License with respect to
178the covered work, and you disclaim any intention to limit operation or
179modification of the work as a means of enforcing, against the work's
180users, your or third parties' legal rights to forbid circumvention of
181technological measures.
182
183 4. Conveying Verbatim Copies.
184
185 You may convey verbatim copies of the Program's source code as you
186receive it, in any medium, provided that you conspicuously and
187appropriately publish on each copy an appropriate copyright notice;
188keep intact all notices stating that this License and any
189non-permissive terms added in accord with section 7 apply to the code;
190keep intact all notices of the absence of any warranty; and give all
191recipients a copy of this License along with the Program.
192
193 You may charge any price or no price for each copy that you convey,
194and you may offer support or warranty protection for a fee.
195
196 5. Conveying Modified Source Versions.
197
198 You may convey a work based on the Program, or the modifications to
199produce it from the Program, in the form of source code under the
200terms of section 4, provided that you also meet all of these conditions:
201
202 a) The work must carry prominent notices stating that you modified
203 it, and giving a relevant date.
204
205 b) The work must carry prominent notices stating that it is
206 released under this License and any conditions added under section
207 7. This requirement modifies the requirement in section 4 to
208 "keep intact all notices".
209
210 c) You must license the entire work, as a whole, under this
211 License to anyone who comes into possession of a copy. This
212 License will therefore apply, along with any applicable section 7
213 additional terms, to the whole of the work, and all its parts,
214 regardless of how they are packaged. This License gives no
215 permission to license the work in any other way, but it does not
216 invalidate such permission if you have separately received it.
217
218 d) If the work has interactive user interfaces, each must display
219 Appropriate Legal Notices; however, if the Program has interactive
220 interfaces that do not display Appropriate Legal Notices, your
221 work need not make them do so.
222
223 A compilation of a covered work with other separate and independent
224works, which are not by their nature extensions of the covered work,
225and which are not combined with it such as to form a larger program,
226in or on a volume of a storage or distribution medium, is called an
227"aggregate" if the compilation and its resulting copyright are not
228used to limit the access or legal rights of the compilation's users
229beyond what the individual works permit. Inclusion of a covered work
230in an aggregate does not cause this License to apply to the other
231parts of the aggregate.
232
233 6. Conveying Non-Source Forms.
234
235 You may convey a covered work in object code form under the terms
236of sections 4 and 5, provided that you also convey the
237machine-readable Corresponding Source under the terms of this License,
238in one of these ways:
239
240 a) Convey the object code in, or embodied in, a physical product
241 (including a physical distribution medium), accompanied by the
242 Corresponding Source fixed on a durable physical medium
243 customarily used for software interchange.
244
245 b) Convey the object code in, or embodied in, a physical product
246 (including a physical distribution medium), accompanied by a
247 written offer, valid for at least three years and valid for as
248 long as you offer spare parts or customer support for that product
249 model, to give anyone who possesses the object code either (1) a
250 copy of the Corresponding Source for all the software in the
251 product that is covered by this License, on a durable physical
252 medium customarily used for software interchange, for a price no
253 more than your reasonable cost of physically performing this
254 conveying of source, or (2) access to copy the
255 Corresponding Source from a network server at no charge.
256
257 c) Convey individual copies of the object code with a copy of the
258 written offer to provide the Corresponding Source. This
259 alternative is allowed only occasionally and noncommercially, and
260 only if you received the object code with such an offer, in accord
261 with subsection 6b.
262
263 d) Convey the object code by offering access from a designated
264 place (gratis or for a charge), and offer equivalent access to the
265 Corresponding Source in the same way through the same place at no
266 further charge. You need not require recipients to copy the
267 Corresponding Source along with the object code. If the place to
268 copy the object code is a network server, the Corresponding Source
269 may be on a different server (operated by you or a third party)
270 that supports equivalent copying facilities, provided you maintain
271 clear directions next to the object code saying where to find the
272 Corresponding Source. Regardless of what server hosts the
273 Corresponding Source, you remain obligated to ensure that it is
274 available for as long as needed to satisfy these requirements.
275
276 e) Convey the object code using peer-to-peer transmission, provided
277 you inform other peers where the object code and Corresponding
278 Source of the work are being offered to the general public at no
279 charge under subsection 6d.
280
281 A separable portion of the object code, whose source code is excluded
282from the Corresponding Source as a System Library, need not be
283included in conveying the object code work.
284
285 A "User Product" is either (1) a "consumer product", which means any
286tangible personal property which is normally used for personal, family,
287or household purposes, or (2) anything designed or sold for incorporation
288into a dwelling. In determining whether a product is a consumer product,
289doubtful cases shall be resolved in favor of coverage. For a particular
290product received by a particular user, "normally used" refers to a
291typical or common use of that class of product, regardless of the status
292of the particular user or of the way in which the particular user
293actually uses, or expects or is expected to use, the product. A product
294is a consumer product regardless of whether the product has substantial
295commercial, industrial or non-consumer uses, unless such uses represent
296the only significant mode of use of the product.
297
298 "Installation Information" for a User Product means any methods,
299procedures, authorization keys, or other information required to install
300and execute modified versions of a covered work in that User Product from
301a modified version of its Corresponding Source. The information must
302suffice to ensure that the continued functioning of the modified object
303code is in no case prevented or interfered with solely because
304modification has been made.
305
306 If you convey an object code work under this section in, or with, or
307specifically for use in, a User Product, and the conveying occurs as
308part of a transaction in which the right of possession and use of the
309User Product is transferred to the recipient in perpetuity or for a
310fixed term (regardless of how the transaction is characterized), the
311Corresponding Source conveyed under this section must be accompanied
312by the Installation Information. But this requirement does not apply
313if neither you nor any third party retains the ability to install
314modified object code on the User Product (for example, the work has
315been installed in ROM).
316
317 The requirement to provide Installation Information does not include a
318requirement to continue to provide support service, warranty, or updates
319for a work that has been modified or installed by the recipient, or for
320the User Product in which it has been modified or installed. Access to a
321network may be denied when the modification itself materially and
322adversely affects the operation of the network or violates the rules and
323protocols for communication across the network.
324
325 Corresponding Source conveyed, and Installation Information provided,
326in accord with this section must be in a format that is publicly
327documented (and with an implementation available to the public in
328source code form), and must require no special password or key for
329unpacking, reading or copying.
330
331 7. Additional Terms.
332
333 "Additional permissions" are terms that supplement the terms of this
334License by making exceptions from one or more of its conditions.
335Additional permissions that are applicable to the entire Program shall
336be treated as though they were included in this License, to the extent
337that they are valid under applicable law. If additional permissions
338apply only to part of the Program, that part may be used separately
339under those permissions, but the entire Program remains governed by
340this License without regard to the additional permissions.
341
342 When you convey a copy of a covered work, you may at your option
343remove any additional permissions from that copy, or from any part of
344it. (Additional permissions may be written to require their own
345removal in certain cases when you modify the work.) You may place
346additional permissions on material, added by you to a covered work,
347for which you have or can give appropriate copyright permission.
348
349 Notwithstanding any other provision of this License, for material you
350add to a covered work, you may (if authorized by the copyright holders of
351that material) supplement the terms of this License with terms:
352
353 a) Disclaiming warranty or limiting liability differently from the
354 terms of sections 15 and 16 of this License; or
355
356 b) Requiring preservation of specified reasonable legal notices or
357 author attributions in that material or in the Appropriate Legal
358 Notices displayed by works containing it; or
359
360 c) Prohibiting misrepresentation of the origin of that material, or
361 requiring that modified versions of such material be marked in
362 reasonable ways as different from the original version; or
363
364 d) Limiting the use for publicity purposes of names of licensors or
365 authors of the material; or
366
367 e) Declining to grant rights under trademark law for use of some
368 trade names, trademarks, or service marks; or
369
370 f) Requiring indemnification of licensors and authors of that
371 material by anyone who conveys the material (or modified versions of
372 it) with contractual assumptions of liability to the recipient, for
373 any liability that these contractual assumptions directly impose on
374 those licensors and authors.
375
376 All other non-permissive additional terms are considered "further
377restrictions" within the meaning of section 10. If the Program as you
378received it, or any part of it, contains a notice stating that it is
379governed by this License along with a term that is a further
380restriction, you may remove that term. If a license document contains
381a further restriction but permits relicensing or conveying under this
382License, you may add to a covered work material governed by the terms
383of that license document, provided that the further restriction does
384not survive such relicensing or conveying.
385
386 If you add terms to a covered work in accord with this section, you
387must place, in the relevant source files, a statement of the
388additional terms that apply to those files, or a notice indicating
389where to find the applicable terms.
390
391 Additional terms, permissive or non-permissive, may be stated in the
392form of a separately written license, or stated as exceptions;
393the above requirements apply either way.
394
395 8. Termination.
396
397 You may not propagate or modify a covered work except as expressly
398provided under this License. Any attempt otherwise to propagate or
399modify it is void, and will automatically terminate your rights under
400this License (including any patent licenses granted under the third
401paragraph of section 11).
402
403 However, if you cease all violation of this License, then your
404license from a particular copyright holder is reinstated (a)
405provisionally, unless and until the copyright holder explicitly and
406finally terminates your license, and (b) permanently, if the copyright
407holder fails to notify you of the violation by some reasonable means
408prior to 60 days after the cessation.
409
410 Moreover, your license from a particular copyright holder is
411reinstated permanently if the copyright holder notifies you of the
412violation by some reasonable means, this is the first time you have
413received notice of violation of this License (for any work) from that
414copyright holder, and you cure the violation prior to 30 days after
415your receipt of the notice.
416
417 Termination of your rights under this section does not terminate the
418licenses of parties who have received copies or rights from you under
419this License. If your rights have been terminated and not permanently
420reinstated, you do not qualify to receive new licenses for the same
421material under section 10.
422
423 9. Acceptance Not Required for Having Copies.
424
425 You are not required to accept this License in order to receive or
426run a copy of the Program. Ancillary propagation of a covered work
427occurring solely as a consequence of using peer-to-peer transmission
428to receive a copy likewise does not require acceptance. However,
429nothing other than this License grants you permission to propagate or
430modify any covered work. These actions infringe copyright if you do
431not accept this License. Therefore, by modifying or propagating a
432covered work, you indicate your acceptance of this License to do so.
433
434 10. Automatic Licensing of Downstream Recipients.
435
436 Each time you convey a covered work, the recipient automatically
437receives a license from the original licensors, to run, modify and
438propagate that work, subject to this License. You are not responsible
439for enforcing compliance by third parties with this License.
440
441 An "entity transaction" is a transaction transferring control of an
442organization, or substantially all assets of one, or subdividing an
443organization, or merging organizations. If propagation of a covered
444work results from an entity transaction, each party to that
445transaction who receives a copy of the work also receives whatever
446licenses to the work the party's predecessor in interest had or could
447give under the previous paragraph, plus a right to possession of the
448Corresponding Source of the work from the predecessor in interest, if
449the predecessor has it or can get it with reasonable efforts.
450
451 You may not impose any further restrictions on the exercise of the
452rights granted or affirmed under this License. For example, you may
453not impose a license fee, royalty, or other charge for exercise of
454rights granted under this License, and you may not initiate litigation
455(including a cross-claim or counterclaim in a lawsuit) alleging that
456any patent claim is infringed by making, using, selling, offering for
457sale, or importing the Program or any portion of it.
458
459 11. Patents.
460
461 A "contributor" is a copyright holder who authorizes use under this
462License of the Program or a work on which the Program is based. The
463work thus licensed is called the contributor's "contributor version".
464
465 A contributor's "essential patent claims" are all patent claims
466owned or controlled by the contributor, whether already acquired or
467hereafter acquired, that would be infringed by some manner, permitted
468by this License, of making, using, or selling its contributor version,
469but do not include claims that would be infringed only as a
470consequence of further modification of the contributor version. For
471purposes of this definition, "control" includes the right to grant
472patent sublicenses in a manner consistent with the requirements of
473this License.
474
475 Each contributor grants you a non-exclusive, worldwide, royalty-free
476patent license under the contributor's essential patent claims, to
477make, use, sell, offer for sale, import and otherwise run, modify and
478propagate the contents of its contributor version.
479
480 In the following three paragraphs, a "patent license" is any express
481agreement or commitment, however denominated, not to enforce a patent
482(such as an express permission to practice a patent or covenant not to
483sue for patent infringement). To "grant" such a patent license to a
484party means to make such an agreement or commitment not to enforce a
485patent against the party.
486
487 If you convey a covered work, knowingly relying on a patent license,
488and the Corresponding Source of the work is not available for anyone
489to copy, free of charge and under the terms of this License, through a
490publicly available network server or other readily accessible means,
491then you must either (1) cause the Corresponding Source to be so
492available, or (2) arrange to deprive yourself of the benefit of the
493patent license for this particular work, or (3) arrange, in a manner
494consistent with the requirements of this License, to extend the patent
495license to downstream recipients. "Knowingly relying" means you have
496actual knowledge that, but for the patent license, your conveying the
497covered work in a country, or your recipient's use of the covered work
498in a country, would infringe one or more identifiable patents in that
499country that you have reason to believe are valid.
500
501 If, pursuant to or in connection with a single transaction or
502arrangement, you convey, or propagate by procuring conveyance of, a
503covered work, and grant a patent license to some of the parties
504receiving the covered work authorizing them to use, propagate, modify
505or convey a specific copy of the covered work, then the patent license
506you grant is automatically extended to all recipients of the covered
507work and works based on it.
508
509 A patent license is "discriminatory" if it does not include within
510the scope of its coverage, prohibits the exercise of, or is
511conditioned on the non-exercise of one or more of the rights that are
512specifically granted under this License. You may not convey a covered
513work if you are a party to an arrangement with a third party that is
514in the business of distributing software, under which you make payment
515to the third party based on the extent of your activity of conveying
516the work, and under which the third party grants, to any of the
517parties who would receive the covered work from you, a discriminatory
518patent license (a) in connection with copies of the covered work
519conveyed by you (or copies made from those copies), or (b) primarily
520for and in connection with specific products or compilations that
521contain the covered work, unless you entered into that arrangement,
522or that patent license was granted, prior to 28 March 2007.
523
524 Nothing in this License shall be construed as excluding or limiting
525any implied license or other defenses to infringement that may
526otherwise be available to you under applicable patent law.
527
528 12. No Surrender of Others' Freedom.
529
530 If conditions are imposed on you (whether by court order, agreement or
531otherwise) that contradict the conditions of this License, they do not
532excuse you from the conditions of this License. If you cannot convey a
533covered work so as to satisfy simultaneously your obligations under this
534License and any other pertinent obligations, then as a consequence you may
535not convey it at all. For example, if you agree to terms that obligate you
536to collect a royalty for further conveying from those to whom you convey
537the Program, the only way you could satisfy both those terms and this
538License would be to refrain entirely from conveying the Program.
539
540 13. Remote Network Interaction; Use with the GNU General Public License.
541
542 Notwithstanding any other provision of this License, if you modify the
543Program, your modified version must prominently offer all users
544interacting with it remotely through a computer network (if your version
545supports such interaction) an opportunity to receive the Corresponding
546Source of your version by providing access to the Corresponding Source
547from a network server at no charge, through some standard or customary
548means of facilitating copying of software. This Corresponding Source
549shall include the Corresponding Source for any work covered by version 3
550of the GNU General Public License that is incorporated pursuant to the
551following paragraph.
552
553 Notwithstanding any other provision of this License, you have
554permission to link or combine any covered work with a work licensed
555under version 3 of the GNU General Public License into a single
556combined work, and to convey the resulting work. The terms of this
557License will continue to apply to the part which is the covered work,
558but the work with which it is combined will remain governed by version
5593 of the GNU General Public License.
560
561 14. Revised Versions of this License.
562
563 The Free Software Foundation may publish revised and/or new versions of
564the GNU Affero General Public License from time to time. Such new versions
565will be similar in spirit to the present version, but may differ in detail to
566address new problems or concerns.
567
568 Each version is given a distinguishing version number. If the
569Program specifies that a certain numbered version of the GNU Affero General
570Public License "or any later version" applies to it, you have the
571option of following the terms and conditions either of that numbered
572version or of any later version published by the Free Software
573Foundation. If the Program does not specify a version number of the
574GNU Affero General Public License, you may choose any version ever published
575by the Free Software Foundation.
576
577 If the Program specifies that a proxy can decide which future
578versions of the GNU Affero General Public License can be used, that proxy's
579public statement of acceptance of a version permanently authorizes you
580to choose that version for the Program.
581
582 Later license versions may give you additional or different
583permissions. However, no additional obligations are imposed on any
584author or copyright holder as a result of your choosing to follow a
585later version.
586
587 15. Disclaimer of Warranty.
588
589 THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597
598 16. Limitation of Liability.
599
600 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608SUCH DAMAGES.
609
610 17. Interpretation of Sections 15 and 16.
611
612 If the disclaimer of warranty and limitation of liability provided
613above cannot be given local legal effect according to their terms,
614reviewing courts shall apply local law that most closely approximates
615an absolute waiver of all civil liability in connection with the
616Program, unless a warranty or assumption of liability accompanies a
617copy of the Program in return for a fee.
618
619 END OF TERMS AND CONDITIONS
620
621 How to Apply These Terms to Your New Programs
622
623 If you develop a new program, and you want it to be of the greatest
624possible use to the public, the best way to achieve this is to make it
625free software which everyone can redistribute and change under these terms.
626
627 To do so, attach the following notices to the program. It is safest
628to attach them to the start of each source file to most effectively
629state the exclusion of warranty; and each file should have at least
630the "copyright" line and a pointer to where the full notice is found.
631
632 <one line to give the program's name and a brief idea of what it does.>
633 Copyright (C) <year> <name of author>
634
635 This program is free software: you can redistribute it and/or modify
636 it under the terms of the GNU Affero General Public License as published by
637 the Free Software Foundation, either version 3 of the License, or
638 (at your option) any later version.
639
640 This program is distributed in the hope that it will be useful,
641 but WITHOUT ANY WARRANTY; without even the implied warranty of
642 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 GNU Affero General Public License for more details.
644
645 You should have received a copy of the GNU Affero General Public License
646 along with this program. If not, see <http://www.gnu.org/licenses/>.
647
648Also add information on how to contact you by electronic and paper mail.
649
650 If your software can interact with users remotely through a computer
651network, you should also make sure that it provides a way for users to
652get its source. For example, if your program is a web application, its
653interface could display a "Source" link that leads users to an archive
654of the code. There are many ways you could offer source, and different
655solutions will be better for different programs; see section 13 for the
656specific requirements.
657
658 You should also get your employer (if you work as a programmer) or school,
659if any, to sign a "copyright disclaimer" for the program, if necessary.
660For more information on this, and how to apply and follow the GNU AGPL, see
661<http://www.gnu.org/licenses/>.
0662
=== removed file 'COPYING'
--- COPYING 2012-06-11 15:40:48 +0000
+++ COPYING 1970-01-01 00:00:00 +0000
@@ -1,661 +0,0 @@
1 GNU AFFERO GENERAL PUBLIC LICENSE
2 Version 3, 19 November 2007
3
4 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5 Everyone is permitted to copy and distribute verbatim copies
6 of this license document, but changing it is not allowed.
7
8 Preamble
9
10 The GNU Affero General Public License is a free, copyleft license for
11software and other kinds of works, specifically designed to ensure
12cooperation with the community in the case of network server software.
13
14 The licenses for most software and other practical works are designed
15to take away your freedom to share and change the works. By contrast,
16our General Public Licenses are intended to guarantee your freedom to
17share and change all versions of a program--to make sure it remains free
18software for all its users.
19
20 When we speak of free software, we are referring to freedom, not
21price. Our General Public Licenses are designed to make sure that you
22have the freedom to distribute copies of free software (and charge for
23them if you wish), that you receive source code or can get it if you
24want it, that you can change the software or use pieces of it in new
25free programs, and that you know you can do these things.
26
27 Developers that use our General Public Licenses protect your rights
28with two steps: (1) assert copyright on the software, and (2) offer
29you this License which gives you legal permission to copy, distribute
30and/or modify the software.
31
32 A secondary benefit of defending all users' freedom is that
33improvements made in alternate versions of the program, if they
34receive widespread use, become available for other developers to
35incorporate. Many developers of free software are heartened and
36encouraged by the resulting cooperation. However, in the case of
37software used on network servers, this result may fail to come about.
38The GNU General Public License permits making a modified version and
39letting the public access it on a server without ever releasing its
40source code to the public.
41
42 The GNU Affero General Public License is designed specifically to
43ensure that, in such cases, the modified source code becomes available
44to the community. It requires the operator of a network server to
45provide the source code of the modified version running there to the
46users of that server. Therefore, public use of a modified version, on
47a publicly accessible server, gives the public access to the source
48code of the modified version.
49
50 An older license, called the Affero General Public License and
51published by Affero, was designed to accomplish similar goals. This is
52a different license, not a version of the Affero GPL, but Affero has
53released a new version of the Affero GPL which permits relicensing under
54this license.
55
56 The precise terms and conditions for copying, distribution and
57modification follow.
58
59 TERMS AND CONDITIONS
60
61 0. Definitions.
62
63 "This License" refers to version 3 of the GNU Affero General Public License.
64
65 "Copyright" also means copyright-like laws that apply to other kinds of
66works, such as semiconductor masks.
67
68 "The Program" refers to any copyrightable work licensed under this
69License. Each licensee is addressed as "you". "Licensees" and
70"recipients" may be individuals or organizations.
71
72 To "modify" a work means to copy from or adapt all or part of the work
73in a fashion requiring copyright permission, other than the making of an
74exact copy. The resulting work is called a "modified version" of the
75earlier work or a work "based on" the earlier work.
76
77 A "covered work" means either the unmodified Program or a work based
78on the Program.
79
80 To "propagate" a work means to do anything with it that, without
81permission, would make you directly or secondarily liable for
82infringement under applicable copyright law, except executing it on a
83computer or modifying a private copy. Propagation includes copying,
84distribution (with or without modification), making available to the
85public, and in some countries other activities as well.
86
87 To "convey" a work means any kind of propagation that enables other
88parties to make or receive copies. Mere interaction with a user through
89a computer network, with no transfer of a copy, is not conveying.
90
91 An interactive user interface displays "Appropriate Legal Notices"
92to the extent that it includes a convenient and prominently visible
93feature that (1) displays an appropriate copyright notice, and (2)
94tells the user that there is no warranty for the work (except to the
95extent that warranties are provided), that licensees may convey the
96work under this License, and how to view a copy of this License. If
97the interface presents a list of user commands or options, such as a
98menu, a prominent item in the list meets this criterion.
99
100 1. Source Code.
101
102 The "source code" for a work means the preferred form of the work
103for making modifications to it. "Object code" means any non-source
104form of a work.
105
106 A "Standard Interface" means an interface that either is an official
107standard defined by a recognized standards body, or, in the case of
108interfaces specified for a particular programming language, one that
109is widely used among developers working in that language.
110
111 The "System Libraries" of an executable work include anything, other
112than the work as a whole, that (a) is included in the normal form of
113packaging a Major Component, but which is not part of that Major
114Component, and (b) serves only to enable use of the work with that
115Major Component, or to implement a Standard Interface for which an
116implementation is available to the public in source code form. A
117"Major Component", in this context, means a major essential component
118(kernel, window system, and so on) of the specific operating system
119(if any) on which the executable work runs, or a compiler used to
120produce the work, or an object code interpreter used to run it.
121
122 The "Corresponding Source" for a work in object code form means all
123the source code needed to generate, install, and (for an executable
124work) run the object code and to modify the work, including scripts to
125control those activities. However, it does not include the work's
126System Libraries, or general-purpose tools or generally available free
127programs which are used unmodified in performing those activities but
128which are not part of the work. For example, Corresponding Source
129includes interface definition files associated with source files for
130the work, and the source code for shared libraries and dynamically
131linked subprograms that the work is specifically designed to require,
132such as by intimate data communication or control flow between those
133subprograms and other parts of the work.
134
135 The Corresponding Source need not include anything that users
136can regenerate automatically from other parts of the Corresponding
137Source.
138
139 The Corresponding Source for a work in source code form is that
140same work.
141
142 2. Basic Permissions.
143
144 All rights granted under this License are granted for the term of
145copyright on the Program, and are irrevocable provided the stated
146conditions are met. This License explicitly affirms your unlimited
147permission to run the unmodified Program. The output from running a
148covered work is covered by this License only if the output, given its
149content, constitutes a covered work. This License acknowledges your
150rights of fair use or other equivalent, as provided by copyright law.
151
152 You may make, run and propagate covered works that you do not
153convey, without conditions so long as your license otherwise remains
154in force. You may convey covered works to others for the sole purpose
155of having them make modifications exclusively for you, or provide you
156with facilities for running those works, provided that you comply with
157the terms of this License in conveying all material for which you do
158not control copyright. Those thus making or running the covered works
159for you must do so exclusively on your behalf, under your direction
160and control, on terms that prohibit them from making any copies of
161your copyrighted material outside their relationship with you.
162
163 Conveying under any other circumstances is permitted solely under
164the conditions stated below. Sublicensing is not allowed; section 10
165makes it unnecessary.
166
167 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168
169 No covered work shall be deemed part of an effective technological
170measure under any applicable law fulfilling obligations under article
17111 of the WIPO copyright treaty adopted on 20 December 1996, or
172similar laws prohibiting or restricting circumvention of such
173measures.
174
175 When you convey a covered work, you waive any legal power to forbid
176circumvention of technological measures to the extent such circumvention
177is effected by exercising rights under this License with respect to
178the covered work, and you disclaim any intention to limit operation or
179modification of the work as a means of enforcing, against the work's
180users, your or third parties' legal rights to forbid circumvention of
181technological measures.
182
183 4. Conveying Verbatim Copies.
184
185 You may convey verbatim copies of the Program's source code as you
186receive it, in any medium, provided that you conspicuously and
187appropriately publish on each copy an appropriate copyright notice;
188keep intact all notices stating that this License and any
189non-permissive terms added in accord with section 7 apply to the code;
190keep intact all notices of the absence of any warranty; and give all
191recipients a copy of this License along with the Program.
192
193 You may charge any price or no price for each copy that you convey,
194and you may offer support or warranty protection for a fee.
195
196 5. Conveying Modified Source Versions.
197
198 You may convey a work based on the Program, or the modifications to
199produce it from the Program, in the form of source code under the
200terms of section 4, provided that you also meet all of these conditions:
201
202 a) The work must carry prominent notices stating that you modified
203 it, and giving a relevant date.
204
205 b) The work must carry prominent notices stating that it is
206 released under this License and any conditions added under section
207 7. This requirement modifies the requirement in section 4 to
208 "keep intact all notices".
209
210 c) You must license the entire work, as a whole, under this
211 License to anyone who comes into possession of a copy. This
212 License will therefore apply, along with any applicable section 7
213 additional terms, to the whole of the work, and all its parts,
214 regardless of how they are packaged. This License gives no
215 permission to license the work in any other way, but it does not
216 invalidate such permission if you have separately received it.
217
218 d) If the work has interactive user interfaces, each must display
219 Appropriate Legal Notices; however, if the Program has interactive
220 interfaces that do not display Appropriate Legal Notices, your
221 work need not make them do so.
222
223 A compilation of a covered work with other separate and independent
224works, which are not by their nature extensions of the covered work,
225and which are not combined with it such as to form a larger program,
226in or on a volume of a storage or distribution medium, is called an
227"aggregate" if the compilation and its resulting copyright are not
228used to limit the access or legal rights of the compilation's users
229beyond what the individual works permit. Inclusion of a covered work
230in an aggregate does not cause this License to apply to the other
231parts of the aggregate.
232
233 6. Conveying Non-Source Forms.
234
235 You may convey a covered work in object code form under the terms
236of sections 4 and 5, provided that you also convey the
237machine-readable Corresponding Source under the terms of this License,
238in one of these ways:
239
240 a) Convey the object code in, or embodied in, a physical product
241 (including a physical distribution medium), accompanied by the
242 Corresponding Source fixed on a durable physical medium
243 customarily used for software interchange.
244
245 b) Convey the object code in, or embodied in, a physical product
246 (including a physical distribution medium), accompanied by a
247 written offer, valid for at least three years and valid for as
248 long as you offer spare parts or customer support for that product
249 model, to give anyone who possesses the object code either (1) a
250 copy of the Corresponding Source for all the software in the
251 product that is covered by this License, on a durable physical
252 medium customarily used for software interchange, for a price no
253 more than your reasonable cost of physically performing this
254 conveying of source, or (2) access to copy the
255 Corresponding Source from a network server at no charge.
256
257 c) Convey individual copies of the object code with a copy of the
258 written offer to provide the Corresponding Source. This
259 alternative is allowed only occasionally and noncommercially, and
260 only if you received the object code with such an offer, in accord
261 with subsection 6b.
262
263 d) Convey the object code by offering access from a designated
264 place (gratis or for a charge), and offer equivalent access to the
265 Corresponding Source in the same way through the same place at no
266 further charge. You need not require recipients to copy the
267 Corresponding Source along with the object code. If the place to
268 copy the object code is a network server, the Corresponding Source
269 may be on a different server (operated by you or a third party)
270 that supports equivalent copying facilities, provided you maintain
271 clear directions next to the object code saying where to find the
272 Corresponding Source. Regardless of what server hosts the
273 Corresponding Source, you remain obligated to ensure that it is
274 available for as long as needed to satisfy these requirements.
275
276 e) Convey the object code using peer-to-peer transmission, provided
277 you inform other peers where the object code and Corresponding
278 Source of the work are being offered to the general public at no
279 charge under subsection 6d.
280
281 A separable portion of the object code, whose source code is excluded
282from the Corresponding Source as a System Library, need not be
283included in conveying the object code work.
284
285 A "User Product" is either (1) a "consumer product", which means any
286tangible personal property which is normally used for personal, family,
287or household purposes, or (2) anything designed or sold for incorporation
288into a dwelling. In determining whether a product is a consumer product,
289doubtful cases shall be resolved in favor of coverage. For a particular
290product received by a particular user, "normally used" refers to a
291typical or common use of that class of product, regardless of the status
292of the particular user or of the way in which the particular user
293actually uses, or expects or is expected to use, the product. A product
294is a consumer product regardless of whether the product has substantial
295commercial, industrial or non-consumer uses, unless such uses represent
296the only significant mode of use of the product.
297
298 "Installation Information" for a User Product means any methods,
299procedures, authorization keys, or other information required to install
300and execute modified versions of a covered work in that User Product from
301a modified version of its Corresponding Source. The information must
302suffice to ensure that the continued functioning of the modified object
303code is in no case prevented or interfered with solely because
304modification has been made.
305
306 If you convey an object code work under this section in, or with, or
307specifically for use in, a User Product, and the conveying occurs as
308part of a transaction in which the right of possession and use of the
309User Product is transferred to the recipient in perpetuity or for a
310fixed term (regardless of how the transaction is characterized), the
311Corresponding Source conveyed under this section must be accompanied
312by the Installation Information. But this requirement does not apply
313if neither you nor any third party retains the ability to install
314modified object code on the User Product (for example, the work has
315been installed in ROM).
316
317 The requirement to provide Installation Information does not include a
318requirement to continue to provide support service, warranty, or updates
319for a work that has been modified or installed by the recipient, or for
320the User Product in which it has been modified or installed. Access to a
321network may be denied when the modification itself materially and
322adversely affects the operation of the network or violates the rules and
323protocols for communication across the network.
324
325 Corresponding Source conveyed, and Installation Information provided,
326in accord with this section must be in a format that is publicly
327documented (and with an implementation available to the public in
328source code form), and must require no special password or key for
329unpacking, reading or copying.
330
331 7. Additional Terms.
332
333 "Additional permissions" are terms that supplement the terms of this
334License by making exceptions from one or more of its conditions.
335Additional permissions that are applicable to the entire Program shall
336be treated as though they were included in this License, to the extent
337that they are valid under applicable law. If additional permissions
338apply only to part of the Program, that part may be used separately
339under those permissions, but the entire Program remains governed by
340this License without regard to the additional permissions.
341
342 When you convey a copy of a covered work, you may at your option
343remove any additional permissions from that copy, or from any part of
344it. (Additional permissions may be written to require their own
345removal in certain cases when you modify the work.) You may place
346additional permissions on material, added by you to a covered work,
347for which you have or can give appropriate copyright permission.
348
349 Notwithstanding any other provision of this License, for material you
350add to a covered work, you may (if authorized by the copyright holders of
351that material) supplement the terms of this License with terms:
352
353 a) Disclaiming warranty or limiting liability differently from the
354 terms of sections 15 and 16 of this License; or
355
356 b) Requiring preservation of specified reasonable legal notices or
357 author attributions in that material or in the Appropriate Legal
358 Notices displayed by works containing it; or
359
360 c) Prohibiting misrepresentation of the origin of that material, or
361 requiring that modified versions of such material be marked in
362 reasonable ways as different from the original version; or
363
364 d) Limiting the use for publicity purposes of names of licensors or
365 authors of the material; or
366
367 e) Declining to grant rights under trademark law for use of some
368 trade names, trademarks, or service marks; or
369
370 f) Requiring indemnification of licensors and authors of that
371 material by anyone who conveys the material (or modified versions of
372 it) with contractual assumptions of liability to the recipient, for
373 any liability that these contractual assumptions directly impose on
374 those licensors and authors.
375
376 All other non-permissive additional terms are considered "further
377restrictions" within the meaning of section 10. If the Program as you
378received it, or any part of it, contains a notice stating that it is
379governed by this License along with a term that is a further
380restriction, you may remove that term. If a license document contains
381a further restriction but permits relicensing or conveying under this
382License, you may add to a covered work material governed by the terms
383of that license document, provided that the further restriction does
384not survive such relicensing or conveying.
385
386 If you add terms to a covered work in accord with this section, you
387must place, in the relevant source files, a statement of the
388additional terms that apply to those files, or a notice indicating
389where to find the applicable terms.
390
391 Additional terms, permissive or non-permissive, may be stated in the
392form of a separately written license, or stated as exceptions;
393the above requirements apply either way.
394
395 8. Termination.
396
397 You may not propagate or modify a covered work except as expressly
398provided under this License. Any attempt otherwise to propagate or
399modify it is void, and will automatically terminate your rights under
400this License (including any patent licenses granted under the third
401paragraph of section 11).
402
403 However, if you cease all violation of this License, then your
404license from a particular copyright holder is reinstated (a)
405provisionally, unless and until the copyright holder explicitly and
406finally terminates your license, and (b) permanently, if the copyright
407holder fails to notify you of the violation by some reasonable means
408prior to 60 days after the cessation.
409
410 Moreover, your license from a particular copyright holder is
411reinstated permanently if the copyright holder notifies you of the
412violation by some reasonable means, this is the first time you have
413received notice of violation of this License (for any work) from that
414copyright holder, and you cure the violation prior to 30 days after
415your receipt of the notice.
416
417 Termination of your rights under this section does not terminate the
418licenses of parties who have received copies or rights from you under
419this License. If your rights have been terminated and not permanently
420reinstated, you do not qualify to receive new licenses for the same
421material under section 10.
422
423 9. Acceptance Not Required for Having Copies.
424
425 You are not required to accept this License in order to receive or
426run a copy of the Program. Ancillary propagation of a covered work
427occurring solely as a consequence of using peer-to-peer transmission
428to receive a copy likewise does not require acceptance. However,
429nothing other than this License grants you permission to propagate or
430modify any covered work. These actions infringe copyright if you do
431not accept this License. Therefore, by modifying or propagating a
432covered work, you indicate your acceptance of this License to do so.
433
434 10. Automatic Licensing of Downstream Recipients.
435
436 Each time you convey a covered work, the recipient automatically
437receives a license from the original licensors, to run, modify and
438propagate that work, subject to this License. You are not responsible
439for enforcing compliance by third parties with this License.
440
441 An "entity transaction" is a transaction transferring control of an
442organization, or substantially all assets of one, or subdividing an
443organization, or merging organizations. If propagation of a covered
444work results from an entity transaction, each party to that
445transaction who receives a copy of the work also receives whatever
446licenses to the work the party's predecessor in interest had or could
447give under the previous paragraph, plus a right to possession of the
448Corresponding Source of the work from the predecessor in interest, if
449the predecessor has it or can get it with reasonable efforts.
450
451 You may not impose any further restrictions on the exercise of the
452rights granted or affirmed under this License. For example, you may
453not impose a license fee, royalty, or other charge for exercise of
454rights granted under this License, and you may not initiate litigation
455(including a cross-claim or counterclaim in a lawsuit) alleging that
456any patent claim is infringed by making, using, selling, offering for
457sale, or importing the Program or any portion of it.
458
459 11. Patents.
460
461 A "contributor" is a copyright holder who authorizes use under this
462License of the Program or a work on which the Program is based. The
463work thus licensed is called the contributor's "contributor version".
464
465 A contributor's "essential patent claims" are all patent claims
466owned or controlled by the contributor, whether already acquired or
467hereafter acquired, that would be infringed by some manner, permitted
468by this License, of making, using, or selling its contributor version,
469but do not include claims that would be infringed only as a
470consequence of further modification of the contributor version. For
471purposes of this definition, "control" includes the right to grant
472patent sublicenses in a manner consistent with the requirements of
473this License.
474
475 Each contributor grants you a non-exclusive, worldwide, royalty-free
476patent license under the contributor's essential patent claims, to
477make, use, sell, offer for sale, import and otherwise run, modify and
478propagate the contents of its contributor version.
479
480 In the following three paragraphs, a "patent license" is any express
481agreement or commitment, however denominated, not to enforce a patent
482(such as an express permission to practice a patent or covenant not to
483sue for patent infringement). To "grant" such a patent license to a
484party means to make such an agreement or commitment not to enforce a
485patent against the party.
486
487 If you convey a covered work, knowingly relying on a patent license,
488and the Corresponding Source of the work is not available for anyone
489to copy, free of charge and under the terms of this License, through a
490publicly available network server or other readily accessible means,
491then you must either (1) cause the Corresponding Source to be so
492available, or (2) arrange to deprive yourself of the benefit of the
493patent license for this particular work, or (3) arrange, in a manner
494consistent with the requirements of this License, to extend the patent
495license to downstream recipients. "Knowingly relying" means you have
496actual knowledge that, but for the patent license, your conveying the
497covered work in a country, or your recipient's use of the covered work
498in a country, would infringe one or more identifiable patents in that
499country that you have reason to believe are valid.
500
501 If, pursuant to or in connection with a single transaction or
502arrangement, you convey, or propagate by procuring conveyance of, a
503covered work, and grant a patent license to some of the parties
504receiving the covered work authorizing them to use, propagate, modify
505or convey a specific copy of the covered work, then the patent license
506you grant is automatically extended to all recipients of the covered
507work and works based on it.
508
509 A patent license is "discriminatory" if it does not include within
510the scope of its coverage, prohibits the exercise of, or is
511conditioned on the non-exercise of one or more of the rights that are
512specifically granted under this License. You may not convey a covered
513work if you are a party to an arrangement with a third party that is
514in the business of distributing software, under which you make payment
515to the third party based on the extent of your activity of conveying
516the work, and under which the third party grants, to any of the
517parties who would receive the covered work from you, a discriminatory
518patent license (a) in connection with copies of the covered work
519conveyed by you (or copies made from those copies), or (b) primarily
520for and in connection with specific products or compilations that
521contain the covered work, unless you entered into that arrangement,
522or that patent license was granted, prior to 28 March 2007.
523
524 Nothing in this License shall be construed as excluding or limiting
525any implied license or other defenses to infringement that may
526otherwise be available to you under applicable patent law.
527
528 12. No Surrender of Others' Freedom.
529
530 If conditions are imposed on you (whether by court order, agreement or
531otherwise) that contradict the conditions of this License, they do not
532excuse you from the conditions of this License. If you cannot convey a
533covered work so as to satisfy simultaneously your obligations under this
534License and any other pertinent obligations, then as a consequence you may
535not convey it at all. For example, if you agree to terms that obligate you
536to collect a royalty for further conveying from those to whom you convey
537the Program, the only way you could satisfy both those terms and this
538License would be to refrain entirely from conveying the Program.
539
540 13. Remote Network Interaction; Use with the GNU General Public License.
541
542 Notwithstanding any other provision of this License, if you modify the
543Program, your modified version must prominently offer all users
544interacting with it remotely through a computer network (if your version
545supports such interaction) an opportunity to receive the Corresponding
546Source of your version by providing access to the Corresponding Source
547from a network server at no charge, through some standard or customary
548means of facilitating copying of software. This Corresponding Source
549shall include the Corresponding Source for any work covered by version 3
550of the GNU General Public License that is incorporated pursuant to the
551following paragraph.
552
553 Notwithstanding any other provision of this License, you have
554permission to link or combine any covered work with a work licensed
555under version 3 of the GNU General Public License into a single
556combined work, and to convey the resulting work. The terms of this
557License will continue to apply to the part which is the covered work,
558but the work with which it is combined will remain governed by version
5593 of the GNU General Public License.
560
561 14. Revised Versions of this License.
562
563 The Free Software Foundation may publish revised and/or new versions of
564the GNU Affero General Public License from time to time. Such new versions
565will be similar in spirit to the present version, but may differ in detail to
566address new problems or concerns.
567
568 Each version is given a distinguishing version number. If the
569Program specifies that a certain numbered version of the GNU Affero General
570Public License "or any later version" applies to it, you have the
571option of following the terms and conditions either of that numbered
572version or of any later version published by the Free Software
573Foundation. If the Program does not specify a version number of the
574GNU Affero General Public License, you may choose any version ever published
575by the Free Software Foundation.
576
577 If the Program specifies that a proxy can decide which future
578versions of the GNU Affero General Public License can be used, that proxy's
579public statement of acceptance of a version permanently authorizes you
580to choose that version for the Program.
581
582 Later license versions may give you additional or different
583permissions. However, no additional obligations are imposed on any
584author or copyright holder as a result of your choosing to follow a
585later version.
586
587 15. Disclaimer of Warranty.
588
589 THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597
598 16. Limitation of Liability.
599
600 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608SUCH DAMAGES.
609
610 17. Interpretation of Sections 15 and 16.
611
612 If the disclaimer of warranty and limitation of liability provided
613above cannot be given local legal effect according to their terms,
614reviewing courts shall apply local law that most closely approximates
615an absolute waiver of all civil liability in connection with the
616Program, unless a warranty or assumption of liability accompanies a
617copy of the Program in return for a fee.
618
619 END OF TERMS AND CONDITIONS
620
621 How to Apply These Terms to Your New Programs
622
623 If you develop a new program, and you want it to be of the greatest
624possible use to the public, the best way to achieve this is to make it
625free software which everyone can redistribute and change under these terms.
626
627 To do so, attach the following notices to the program. It is safest
628to attach them to the start of each source file to most effectively
629state the exclusion of warranty; and each file should have at least
630the "copyright" line and a pointer to where the full notice is found.
631
632 <one line to give the program's name and a brief idea of what it does.>
633 Copyright (C) <year> <name of author>
634
635 This program is free software: you can redistribute it and/or modify
636 it under the terms of the GNU Affero General Public License as published by
637 the Free Software Foundation, either version 3 of the License, or
638 (at your option) any later version.
639
640 This program is distributed in the hope that it will be useful,
641 but WITHOUT ANY WARRANTY; without even the implied warranty of
642 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 GNU Affero General Public License for more details.
644
645 You should have received a copy of the GNU Affero General Public License
646 along with this program. If not, see <http://www.gnu.org/licenses/>.
647
648Also add information on how to contact you by electronic and paper mail.
649
650 If your software can interact with users remotely through a computer
651network, you should also make sure that it provides a way for users to
652get its source. For example, if your program is a web application, its
653interface could display a "Source" link that leads users to an archive
654of the code. There are many ways you could offer source, and different
655solutions will be better for different programs; see section 13 for the
656specific requirements.
657
658 You should also get your employer (if you work as a programmer) or school,
659if any, to sign a "copyright disclaimer" for the program, if necessary.
660For more information on this, and how to apply and follow the GNU AGPL, see
661<http://www.gnu.org/licenses/>.
6620
=== added file 'Makefile'
--- Makefile 1970-01-01 00:00:00 +0000
+++ Makefile 2013-04-22 11:00:38 +0000
@@ -0,0 +1,54 @@
1PEP8=pep8
2COVERAGE_FILES=`find juju -name "*py" | grep -v "tests\|lib/mocker.py\|lib/testing.py"`
3
4all:
5 @echo "You've just watched the fastest build on earth."
6
7tests:
8 ./test
9
10coverage:
11 python -c "import coverage as c; c.main()" run ./test
12 python -c "import coverage as c; c.main()" html -d htmlcov $(COVERAGE_FILES)
13 gnome-open htmlcov/index.html
14
15ftests:
16 ./test --functional
17
18tags:
19 @ctags --python-kinds=-iv -R juju
20
21etags:
22 @ctags -e --python-kinds=-iv -R juju
23
24
25present_pep8=$(shell which $(PEP8))
26present_pyflakes=$(shell which pyflakes)
27warn_missing_linters:
28 @test -n "$(present_pep8)" || echo "WARNING: $(PEP8) not installed."
29 @test -n "$(present_pyflakes)" || echo "WARNING: pyflakes not installed."
30
31
32# "check": Check uncommitted changes for lint.
33check_changes=$(shell bzr status -S | grep '^[ +]*[MN]' | awk '{print $$2;}' | grep "\\.py$$")
34check: warn_missing_linters
35 @test -z $(present_pep8) || (echo $(check_changes) | xargs -r $(PEP8) --repeat)
36 @test -z $(present_pyflakes) || (echo $(check_changes) | xargs -r pyflakes)
37
38
39# "review": Check all changes compared to trunk for lint.
40review_changes=$(shell bzr status -S -r ancestor:$(JUJU_TRUNK) | grep '^[ +]*[MN]' | awk '{print $$2;}' | grep "\\.py$$")
41review: warn_missing_linters
42 #@test -z $(present_pep8) || (echo $(review_changes) | xargs -r $(PEP8) --repeat)
43 @test -z $(present_pyflakes) || (echo $(review_changes) | xargs -r pyflakes)
44
45
46ptests_changes=$(shell bzr status -S -r branch::prev | grep -P '^[ +]*[MN]' | awk '{print $$2;}'| grep "test_.*\\.py$$")
47ptests:
48 @echo $(ptests_changes) | xargs -r ./test
49
50btests_changes=$(shell bzr status -S -r ancestor:$(JUJU_TRUNK)/ | grep "test.*\\.py$$" | awk '{print $$2;}')
51btests:
52 @./test $(btests_changes)
53
54.PHONY: tags check review warn_missing_linters
055
=== removed file 'Makefile'
--- Makefile 2012-10-09 12:35:35 +0000
+++ Makefile 1970-01-01 00:00:00 +0000
@@ -1,44 +0,0 @@
1PEP8=pep8
2COVERAGE_FILES=`find juju -name "*py" | grep -v "tests\|lib/mocker.py\|lib/testing.py"`
3
4all:
5 @echo "You've just watched the fastest build on earth."
6
7tests:
8 ./test
9
10coverage:
11 python -c "import coverage as c; c.main()" run ./test
12 python -c "import coverage as c; c.main()" html -d htmlcov $(COVERAGE_FILES)
13 gnome-open htmlcov/index.html
14
15ftests:
16 ./test --functional
17
18tags:
19 @ctags --python-kinds=-iv -R juju
20
21etags:
22 @ctags -e --python-kinds=-iv -R juju
23
24modified=$(shell bzr status -S |grep -P '^\s*M' | awk '{print $$2;}'| grep -P ".py$$")
25check:
26 @test -n "$(modified)" && echo $(modified) | xargs $(PEP8) --repeat
27 @test -n "$(modified)" && echo $(modified) | xargs pyflakes
28
29
30modified=$(shell bzr status -S -r ancestor:$(JUJU_TRUNK) |grep -P '^\s*M' | awk '{print $$2;}'| grep -P ".py$$")
31review:
32 #@test -n "$(modified)" && echo $(modified) | xargs $(PEP8) --repeat
33 @test -n "$(modified)" && echo $(modified) | xargs pyflakes
34
35
36modified=$(shell bzr status -S -r branch::prev |grep -P '^\s*\+?[MN]' | awk '{print $$2;}'| grep -P "test_.*\.py$$")
37ptests:
38 @test -n "$(modified)" && echo $(modified) | xargs ./test
39
40modified=$(shell bzr status -S -r ancestor:$(JUJU_TRUNK)/|grep -P 'test.*\.py' |awk '{print $$2;}')
41btests:
42 @./test $(modified)
43
44.PHONY: tags check review
450
=== added file 'README'
--- README 1970-01-01 00:00:00 +0000
+++ README 2013-04-22 11:00:38 +0000
@@ -0,0 +1,34 @@
1juju
2====
3
4Welcome to juju, we hope you enjoy your stay.
5
6You can always get the latest juju code by running::
7
8 $ bzr branch lp:juju
9
10The juju bug tracker is at https://bugs.launchpad.net/juju
11
12Documentation for getting setup and running juju can be found at
13http://juju.ubuntu.com/docs
14
15====
16Juju Developers <juju@lists.ubuntu.com>
17
18Except where stated otherwise, the files contained within this source
19code tree are covered under the following copyright and license:
20
21Copyright 2010, 2011 Canonical Ltd. All Rights Reserved.
22
23This program is free software: you can redistribute it and/or modify
24it under the terms of the GNU Affero General Public License as published by
25the Free Software Foundation, either version 3 of the License, or
26(at your option) any later version.
27
28This package is distributed in the hope that it will be useful,
29but WITHOUT ANY WARRANTY; without even the implied warranty of
30MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31GNU General Public License for more details.
32
33You should have received a copy of the GNU Affero General Public License
34along with this program. If not, see <http://www.gnu.org/licenses/>.
035
=== removed file 'README'
--- README 2012-07-19 11:01:09 +0000
+++ README 1970-01-01 00:00:00 +0000
@@ -1,34 +0,0 @@
1juju
2====
3
4Welcome to juju, we hope you enjoy your stay.
5
6You can always get the latest juju code by running::
7
8 $ bzr branch lp:juju
9
10The juju bug tracker is at https://bugs.launchpad.net/juju
11
12Documentation for getting setup and running juju can be found at
13http://juju.ubuntu.com/docs
14
15====
16Juju Developers <juju@lists.ubuntu.com>
17
18Except where stated otherwise, the files contained within this source
19code tree are covered under the following copyright and license:
20
21Copyright 2010, 2011 Canonical Ltd. All Rights Reserved.
22
23This program is free software: you can redistribute it and/or modify
24it under the terms of the GNU Affero General Public License as published by
25the Free Software Foundation, either version 3 of the License, or
26(at your option) any later version.
27
28This package is distributed in the hope that it will be useful,
29but WITHOUT ANY WARRANTY; without even the implied warranty of
30MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31GNU General Public License for more details.
32
33You should have received a copy of the GNU Affero General Public License
34along with this program. If not, see <http://www.gnu.org/licenses/>.
350
=== added directory 'bin'
=== removed directory 'bin'
=== added file 'bin/close-port'
--- bin/close-port 1970-01-01 00:00:00 +0000
+++ bin/close-port 2013-04-22 11:00:38 +0000
@@ -0,0 +1,11 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5if "JUJU_PYTHONPATH" in os.environ:
6 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
7
8from juju.hooks.commands import close_port
9
10if __name__ == '__main__':
11 close_port()
012
=== removed file 'bin/close-port'
--- bin/close-port 2012-06-11 15:40:48 +0000
+++ bin/close-port 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5if "JUJU_PYTHONPATH" in os.environ:
6 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
7
8from juju.hooks.commands import close_port
9
10if __name__ == '__main__':
11 close_port()
120
=== added file 'bin/config-get'
--- bin/config-get 1970-01-01 00:00:00 +0000
+++ bin/config-get 2013-04-22 11:00:38 +0000
@@ -0,0 +1,12 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5
6if "JUJU_PYTHONPATH" in os.environ:
7 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
8
9from juju.hooks.commands import config_get
10
11if __name__ == '__main__':
12 config_get()
013
=== removed file 'bin/config-get'
--- bin/config-get 2012-06-11 15:40:48 +0000
+++ bin/config-get 1970-01-01 00:00:00 +0000
@@ -1,12 +0,0 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5
6if "JUJU_PYTHONPATH" in os.environ:
7 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
8
9from juju.hooks.commands import config_get
10
11if __name__ == '__main__':
12 config_get()
130
=== added file 'bin/juju'
--- bin/juju 1970-01-01 00:00:00 +0000
+++ bin/juju 2013-04-22 11:00:38 +0000
@@ -0,0 +1,10 @@
1#!/usr/bin/env python
2import sys
3
4from juju.control import main
5from juju.errors import JujuError
6
7try:
8 main(sys.argv[1:])
9except JujuError, error:
10 sys.exit("error: %s" % (error,))
011
=== removed file 'bin/juju'
--- bin/juju 2012-06-11 15:40:48 +0000
+++ bin/juju 1970-01-01 00:00:00 +0000
@@ -1,10 +0,0 @@
1#!/usr/bin/env python
2import sys
3
4from juju.control import main
5from juju.errors import JujuError
6
7try:
8 main(sys.argv[1:])
9except JujuError, error:
10 sys.exit("error: %s" % (error,))
110
=== added file 'bin/juju-admin'
--- bin/juju-admin 1970-01-01 00:00:00 +0000
+++ bin/juju-admin 2013-04-22 11:00:38 +0000
@@ -0,0 +1,10 @@
1#!/usr/bin/env python
2import sys
3
4from juju.control import admin
5from juju.errors import JujuError
6
7try:
8 admin(sys.argv[1:])
9except JujuError, error:
10 sys.exit("error: %s" % (error,))
011
=== removed file 'bin/juju-admin'
--- bin/juju-admin 2012-06-11 15:40:48 +0000
+++ bin/juju-admin 1970-01-01 00:00:00 +0000
@@ -1,10 +0,0 @@
1#!/usr/bin/env python
2import sys
3
4from juju.control import admin
5from juju.errors import JujuError
6
7try:
8 admin(sys.argv[1:])
9except JujuError, error:
10 sys.exit("error: %s" % (error,))
110
=== added file 'bin/juju-log'
--- bin/juju-log 1970-01-01 00:00:00 +0000
+++ bin/juju-log 2013-04-22 11:00:38 +0000
@@ -0,0 +1,12 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4
5import os, sys
6if "JUJU_PYTHONPATH" in os.environ:
7 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
8
9from juju.hooks.commands import log
10
11if __name__ == '__main__':
12 log()
013
=== removed file 'bin/juju-log'
--- bin/juju-log 2012-06-11 15:40:48 +0000
+++ bin/juju-log 1970-01-01 00:00:00 +0000
@@ -1,12 +0,0 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4
5import os, sys
6if "JUJU_PYTHONPATH" in os.environ:
7 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
8
9from juju.hooks.commands import log
10
11if __name__ == '__main__':
12 log()
130
=== added file 'bin/open-port'
--- bin/open-port 1970-01-01 00:00:00 +0000
+++ bin/open-port 2013-04-22 11:00:38 +0000
@@ -0,0 +1,11 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5if "JUJU_PYTHONPATH" in os.environ:
6 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
7
8from juju.hooks.commands import open_port
9
10if __name__ == '__main__':
11 open_port()
012
=== removed file 'bin/open-port'
--- bin/open-port 2012-06-11 15:40:48 +0000
+++ bin/open-port 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5if "JUJU_PYTHONPATH" in os.environ:
6 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
7
8from juju.hooks.commands import open_port
9
10if __name__ == '__main__':
11 open_port()
120
=== added file 'bin/relation-get'
--- bin/relation-get 1970-01-01 00:00:00 +0000
+++ bin/relation-get 2013-04-22 11:00:38 +0000
@@ -0,0 +1,12 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5
6if "JUJU_PYTHONPATH" in os.environ:
7 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
8
9from juju.hooks.commands import relation_get
10
11if __name__ == '__main__':
12 relation_get()
013
=== removed file 'bin/relation-get'
--- bin/relation-get 2012-06-11 15:40:48 +0000
+++ bin/relation-get 1970-01-01 00:00:00 +0000
@@ -1,12 +0,0 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5
6if "JUJU_PYTHONPATH" in os.environ:
7 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
8
9from juju.hooks.commands import relation_get
10
11if __name__ == '__main__':
12 relation_get()
130
=== added file 'bin/relation-ids'
--- bin/relation-ids 1970-01-01 00:00:00 +0000
+++ bin/relation-ids 2013-04-22 11:00:38 +0000
@@ -0,0 +1,11 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5if "JUJU_PYTHONPATH" in os.environ:
6 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
7
8from juju.hooks.commands import relation_ids
9
10if __name__ == '__main__':
11 relation_ids()
012
=== removed file 'bin/relation-ids'
--- bin/relation-ids 2012-06-17 15:39:48 +0000
+++ bin/relation-ids 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5if "JUJU_PYTHONPATH" in os.environ:
6 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
7
8from juju.hooks.commands import relation_ids
9
10if __name__ == '__main__':
11 relation_ids()
120
=== added file 'bin/relation-list'
--- bin/relation-list 1970-01-01 00:00:00 +0000
+++ bin/relation-list 2013-04-22 11:00:38 +0000
@@ -0,0 +1,13 @@
1#!/usr/bin/env python
2
3
4# We avoid using PYTHONPATH because it can cause side effects on hook execution
5import os, sys
6
7if "JUJU_PYTHONPATH" in os.environ:
8 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
9
10from juju.hooks.commands import relation_list
11
12if __name__ == '__main__':
13 relation_list()
014
=== removed file 'bin/relation-list'
--- bin/relation-list 2012-06-11 15:40:48 +0000
+++ bin/relation-list 1970-01-01 00:00:00 +0000
@@ -1,13 +0,0 @@
1#!/usr/bin/env python
2
3
4# We avoid using PYTHONPATH because it can cause side effects on hook execution
5import os, sys
6
7if "JUJU_PYTHONPATH" in os.environ:
8 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
9
10from juju.hooks.commands import relation_list
11
12if __name__ == '__main__':
13 relation_list()
140
=== added file 'bin/relation-set'
--- bin/relation-set 1970-01-01 00:00:00 +0000
+++ bin/relation-set 2013-04-22 11:00:38 +0000
@@ -0,0 +1,13 @@
1#!/usr/bin/env python
2
3
4# We avoid using PYTHONPATH because it can cause side effects on hook execution
5import os, sys
6
7if "JUJU_PYTHONPATH" in os.environ:
8 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
9
10from juju.hooks.commands import relation_set
11
12if __name__ == '__main__':
13 relation_set()
014
=== removed file 'bin/relation-set'
--- bin/relation-set 2012-06-11 15:40:48 +0000
+++ bin/relation-set 1970-01-01 00:00:00 +0000
@@ -1,13 +0,0 @@
1#!/usr/bin/env python
2
3
4# We avoid using PYTHONPATH because it can cause side effects on hook execution
5import os, sys
6
7if "JUJU_PYTHONPATH" in os.environ:
8 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
9
10from juju.hooks.commands import relation_set
11
12if __name__ == '__main__':
13 relation_set()
140
=== added file 'bin/unit-get'
--- bin/unit-get 1970-01-01 00:00:00 +0000
+++ bin/unit-get 2013-04-22 11:00:38 +0000
@@ -0,0 +1,12 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5
6if "JUJU_PYTHONPATH" in os.environ:
7 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
8
9from juju.hooks.commands import unit_get
10
11if __name__ == '__main__':
12 unit_get()
013
=== removed file 'bin/unit-get'
--- bin/unit-get 2012-06-11 15:40:48 +0000
+++ bin/unit-get 1970-01-01 00:00:00 +0000
@@ -1,12 +0,0 @@
1#!/usr/bin/env python
2
3# We avoid using PYTHONPATH because it can cause side effects on hook execution
4import os, sys
5
6if "JUJU_PYTHONPATH" in os.environ:
7 sys.path[:0] = filter(None, os.environ["JUJU_PYTHONPATH"].split(":"))
8
9from juju.hooks.commands import unit_get
10
11if __name__ == '__main__':
12 unit_get()
130
=== modified file 'debian/changelog'
--- debian/changelog 2012-12-03 15:59:21 +0000
+++ debian/changelog 2013-04-22 11:00:38 +0000
@@ -1,10 +1,21 @@
1juju (0.6-1ubuntu2) UNRELEASED; urgency=low1juju (0.7-0ubuntu1) raring; urgency=low
22
3 [ Clint Byrum ]
3 * d/p/fix-tests-do-not-use-etc-lsb-release: Dropped, patch applied upstream.4 * d/p/fix-tests-do-not-use-etc-lsb-release: Dropped, patch applied upstream.
4 * d/p/maas-tag-conversion.patch: Dropped, patch applied upstream.5 * d/p/maas-tag-conversion.patch: Dropped, patch applied upstream.
5 * d/juju.docs: Remove examples as they have been dropped upstream.6 * d/juju.docs: Remove examples as they have been dropped upstream.
67
7 -- Clint Byrum <clint@ubuntu.com> Tue, 06 Nov 2012 12:14:23 -08008 [ Mark Mims ]
9 * Add postinst and prerm scripts using update-alternatives to install into
10 a versioned location, enabling co-installability with the go juju port.
11
12 [ Martin Packman ]
13 * New upstream release. (lp: #1167921)
14 * d/p/workaround-lxc-python-env.patch: Workaround regression with local
15 provider failing with SyntaxError on running lxc scripts when user has
16 Python 3 specific environment variables set. (lp: #1130809)
17
18 -- Martin Packman <martin.packman@canonical.com> Wed, 10 Apr 2013 13:57:21 +0000
819
9juju (0.6-1ubuntu1) quantal; urgency=low20juju (0.6-1ubuntu1) quantal; urgency=low
1021
1122
=== modified file 'debian/control'
--- debian/control 2012-10-10 14:41:08 +0000
+++ debian/control 2013-04-22 11:00:38 +0000
@@ -17,11 +17,13 @@
17 lsb-release,17 lsb-release,
18 procps18 procps
19Homepage: https://launchpad.net/juju19Homepage: https://launchpad.net/juju
20Standards-Version: 3.9.320Standards-Version: 3.9.4
2121
22Package: juju22Package: juju-0.7
23Architecture: all23Architecture: all
24Depends: ${python:Depends}, ${misc:Depends}, python-twisted, python-txzookeeper (>= 0.9.5~), python-txaws, python-yaml, openssh-client, tmux, python-oauth, procps24Depends: ${python:Depends}, ${misc:Depends}, python-twisted, python-txzookeeper (>= 0.9.5~), python-txaws, python-yaml, openssh-client, tmux, python-oauth, procps
25Breaks: juju (<< 0.7-0ubuntu1~)
26Replaces: juju (<< 0.7-0ubuntu1~)
25Recommends: python-pydot, byobu27Recommends: python-pydot, byobu
26Suggests: apt-cacher-ng, lxc, zookeeper28Suggests: apt-cacher-ng, lxc, zookeeper
27Provides: ${python:Provides}, ${misc:Depends}29Provides: ${python:Provides}, ${misc:Depends}
@@ -32,3 +34,10 @@
32 create service formulas, called charms, independently, and make those34 create service formulas, called charms, independently, and make those
33 services coordinate their communication and configuration through a35 services coordinate their communication and configuration through a
34 simple protocol.36 simple protocol.
37
38Package: juju
39Architecture: all
40Depends: ${misc:Depends}, juju-0.7
41Description: next generation service orchestration system
42 Virtual dependency package providing Juju, a next generation service
43 orchestration framework.
3544
=== removed file 'debian/juju.install'
--- debian/juju.install 2012-06-11 15:40:48 +0000
+++ debian/juju.install 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1misc/bash_completion.d/juju /etc/bash_completion.d/
20
=== removed file 'debian/juju.manpages'
--- debian/juju.manpages 2012-06-11 15:40:48 +0000
+++ debian/juju.manpages 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1debian/manpages/*.1
20
=== added file 'debian/juju.postinst.in'
--- debian/juju.postinst.in 1970-01-01 00:00:00 +0000
+++ debian/juju.postinst.in 2013-04-22 11:00:38 +0000
@@ -0,0 +1,43 @@
1#!/bin/sh
2set -e
3
4VER="__NEW_VERSION__"
5base_dir="/usr/lib"
6
7case "$1" in
8 configure)
9 update-alternatives --force --install /usr/bin/juju juju $base_dir/juju-$VER/bin/juju 20 \
10 --slave /usr/share/man/man1/juju.1.gz juju.1.gz $base_dir/juju-$VER/man/man1/juju.1.gz \
11 --slave /usr/bin/open-port open-port $base_dir/juju-$VER/bin/open-port \
12 --slave /usr/share/man/man1/open-port.1.gz open-port.1.gz $base_dir/juju-$VER/man/man1/open-port.1.gz \
13 --slave /usr/bin/close-port close-port $base_dir/juju-$VER/bin/close-port \
14 --slave /usr/share/man/man1/close-port.1.gz close-port.1.gz $base_dir/juju-$VER/man/man1/close-port.1.gz \
15 --slave /usr/bin/config-get config-get $base_dir/juju-$VER/bin/config-get \
16 --slave /usr/share/man/man1/config-get.1.gz config-get.1.gz $base_dir/juju-$VER/man/man1/config-get.1.gz \
17 --slave /usr/bin/relation-list relation-list $base_dir/juju-$VER/bin/relation-list \
18 --slave /usr/share/man/man1/relation-list.1.gz relation-list.1.gz $base_dir/juju-$VER/man/man1/relation-list.1.gz \
19 --slave /usr/bin/unit-get unit-get $base_dir/juju-$VER/bin/unit-get \
20 --slave /usr/share/man/man1/unit-get.1.gz unit-get.1.gz $base_dir/juju-$VER/man/man1/unit-get.1.gz \
21 --slave /usr/bin/juju-admin juju-admin $base_dir/juju-$VER/bin/juju-admin \
22 --slave /usr/share/man/man1/juju-admin.1.gz juju-admin.1.gz $base_dir/juju-$VER/man/man1/juju-admin.1.gz \
23 --slave /usr/bin/relation-ids relation-ids $base_dir/juju-$VER/bin/relation-ids \
24 --slave /usr/share/man/man1/relation-ids.1.gz relation-ids.1.gz $base_dir/juju-$VER/man/man1/relation-ids.1.gz \
25 --slave /usr/bin/juju-log juju-log $base_dir/juju-$VER/bin/juju-log \
26 --slave /usr/share/man/man1/juju-log.1.gz juju-log.1.gz $base_dir/juju-$VER/man/man1/juju-log.1.gz \
27 --slave /usr/bin/relation-get relation-get $base_dir/juju-$VER/bin/relation-get \
28 --slave /usr/share/man/man1/relation-get.1.gz relation-get.1.gz $base_dir/juju-$VER/man/man1/relation-get.1.gz \
29 --slave /usr/bin/relation-set relation-set $base_dir/juju-$VER/bin/relation-set \
30 --slave /usr/share/man/man1/relation-set.1.gz relation-set.1.gz $base_dir/juju-$VER/man/man1/relation-set.1.gz \
31 --slave /etc/bash_completion.d/juju juju-bash-completion $base_dir/juju-$VER/etc/bash_completion.d/juju
32 ;;
33 abort-upgrade|abort-remove|abort-deconfigure)
34 echo "$1"
35 ;;
36 *) echo "$0: didn't understand being called with \`$1'" 1>&2
37 exit 0
38 ;;
39esac
40
41#DEBHELPER#
42
43exit 0
044
=== added file 'debian/juju.prerm.in'
--- debian/juju.prerm.in 1970-01-01 00:00:00 +0000
+++ debian/juju.prerm.in 2013-04-22 11:00:38 +0000
@@ -0,0 +1,23 @@
1#!/bin/sh
2set -e
3
4VER="__NEW_VERSION__"
5
6case "$1" in
7 remove|upgrade|deconfigure)
8 update-alternatives --remove juju /usr/lib/juju-$VER/bin/juju
9 ;;
10
11 failed-upgrade)
12 echo "$1"
13 ;;
14
15 *)
16 echo "prerm called with unknown argument \`$1'" >&2
17 exit 0
18 ;;
19esac
20
21#DEBHELPER#
22
23exit 0
024
=== modified file 'debian/patches/series'
--- debian/patches/series 2012-11-06 20:44:41 +0000
+++ debian/patches/series 2013-04-22 11:00:38 +0000
@@ -1,1 +1,2 @@
1disable-failing-zookeeper-test.patch1disable-failing-zookeeper-test.patch
2workaround-lxc-python-env.patch
23
=== added file 'debian/patches/workaround-lxc-python-env.patch'
--- debian/patches/workaround-lxc-python-env.patch 1970-01-01 00:00:00 +0000
+++ debian/patches/workaround-lxc-python-env.patch 2013-04-22 11:00:38 +0000
@@ -0,0 +1,26 @@
1From: Martin Packman <martin.packman@canonical.com>
2Description: The various lxc scripts installed in /usr/bin are Python 3 only.
3 If the user has PYTHONPATH or other similar environment variables set, these
4 scripts can incorrectly inherit details from the user's Python 2 setup when
5 run. Workaround the problem by sanitising the environment before calling them.
6Forwarded: N/A
7
8Index: juju/juju/lib/lxc/__init__.py
9===================================================================
10--- juju.orig/juju/lib/lxc/__init__.py 2013-04-10 12:11:45.583012000 +0000
11+++ juju/juju/lib/lxc/__init__.py 2013-04-10 12:12:29.543012000 +0000
12@@ -38,8 +38,13 @@
13
14
15 def _cmd(args):
16+ # GZ 2013-04-10: Cleanup the user's python environment to avoid issues now
17+ # the lxc scripts require Python 3, see lp:1130809.
18+ env = dict((k, os.environ[k]) for k in os.environ
19+ if not k.startswith("PYTHON"))
20+ env["PYTHONNOUSERSITE"] = "1"
21 p = subprocess.Popen(
22- args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
23+ args, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
24 stdout_data, _ = p.communicate()
25 r = p.returncode
26 if r != 0:
027
=== modified file 'debian/rules'
--- debian/rules 2012-07-19 11:01:09 +0000
+++ debian/rules 2013-04-22 11:00:38 +0000
@@ -2,9 +2,14 @@
22
3REV=$(shell dpkg-parsechangelog | sed -rne 's,^Version: .*[+~]bzr([0-9]+).*,\1,p')3REV=$(shell dpkg-parsechangelog | sed -rne 's,^Version: .*[+~]bzr([0-9]+).*,\1,p')
4VER=$(shell dpkg-parsechangelog | sed -rne 's,^Version: ([^-]+).*,\1,p')4VER=$(shell dpkg-parsechangelog | sed -rne 's,^Version: ([^-]+).*,\1,p')
5MAJOR_VER=0.7
6ifeq "$(REV)" ""
7 REV=tag:$(VER)
8endif
9MANDIR=debian/manpages
510
6%:11%:
7 dh $@ --buildsystem=python_distutils --with python212 dh $@ --with python2
813
9override_dh_auto_test:14override_dh_auto_test:
10ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))15ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
@@ -13,17 +18,16 @@
1318
14get-orig-source:19get-orig-source:
15 bzr export -r $(REV) --root=juju-$(VER).orig \20 bzr export -r $(REV) --root=juju-$(VER).orig \
16 juju_$(VER).orig.tar.gz lp:juju21 juju_$(VER).orig.tar.gz lp:juju/$(MAJOR_VER)
1722
18override_dh_auto_clean::23override_dh_auto_clean::
19 rm -f debian/manpages/*.partial24 rm -f debian/manpages/*.partial
20 rm -f debian/manpages/*.125 rm -f debian/manpages/*.1
2126
22override_dh_auto_build:: debian/manpages/juju.1
23
24HELP2MAN=PYTHONPATH=`pwd` help2man -N --version-string $(VER)27HELP2MAN=PYTHONPATH=`pwd` help2man -N --version-string $(VER)
2528
26debian/manpages/juju.1:29generate_juju_manpages:
30 mkdir -p $(MANDIR)
27 $(HELP2MAN) --name "juju service orchestration system admin client" \31 $(HELP2MAN) --name "juju service orchestration system admin client" \
28 --include debian/manpages/juju.includes.in bin/juju \32 --include debian/manpages/juju.includes.in bin/juju \
29 > debian/manpages/juju.1.partial33 > debian/manpages/juju.1.partial
@@ -34,7 +38,31 @@
34 $(HELP2MAN) --name "Juju charm API commands" \38 $(HELP2MAN) --name "Juju charm API commands" \
35 --include debian/manpages/charm_api.in \39 --include debian/manpages/charm_api.in \
36 bin/$$i > debian/manpages/$$i.1.partial ;\40 bin/$$i > debian/manpages/$$i.1.partial ;\
37 mv debian/manpages/$$i.1.partial debian/manpages/$$i.1 ;\41 mv debian/manpages/$$i.1.partial $(MANDIR)/$$i.1 ;\
38 done42 done
39 mv -f debian/manpages/juju-admin.1.partial debian/manpages/juju-admin.143 mv -f debian/manpages/juju-admin.1.partial $(MANDIR)/juju-admin.1
40 mv -f debian/manpages/juju.1.partial debian/manpages/juju.144 mv -f debian/manpages/juju.1.partial $(MANDIR)/juju.1
45
46override_dh_auto_build: generate_juju_manpages
47
48install_juju_manpages:
49 mkdir -p debian/juju-$(MAJOR_VER)/usr/lib/juju-$(VER)/man/man1
50 cp -a debian/manpages/*.1 debian/juju-$(MAJOR_VER)/usr/lib/juju-$(VER)/man/man1/
51 gzip debian/juju-$(MAJOR_VER)/usr/lib/juju-$(VER)/man/man1/*.1
52
53install_bash_completion:
54 mkdir -p debian/juju-$(MAJOR_VER)/usr/lib/juju-$(VER)/etc/bash_completion.d
55 cp -a misc/bash_completion.d/juju debian/juju-$(MAJOR_VER)/usr/lib/juju-$(VER)/etc/bash_completion.d/juju
56
57override_dh_auto_install: install_juju_manpages install_bash_completion
58 set -e && python setup.py install --root=debian/juju-$(MAJOR_VER) --install-layout=deb --install-scripts=/usr/lib/juju-$(VER)/bin --compile
59
60debian/juju-$(VER).postinst: debian/juju.postinst.in
61 sed -e "s/__NEW_VERSION__/$(VER)/g" $< > $@
62
63debian/juju-$(VER).prerm: debian/juju.prerm.in
64 sed -e "s/__NEW_VERSION__/$(VER)/g" $< > $@
65
66override_dh_installdeb: debian/juju-$(VER).postinst debian/juju-$(VER).prerm
67 dh_installdeb
68
4169
=== removed directory 'examples'
=== removed file 'examples/README'
--- examples/README 2012-06-11 15:40:48 +0000
+++ examples/README 1970-01-01 00:00:00 +0000
@@ -1,24 +0,0 @@
1Examples
2========
3
4
5These are some example charms that can be deployed together to create some connected services.
6
7Many more charms of greater functionality and utility exist in the Principia project, which
8currently serves as a clearinghouse for juju charms.
9
10http://launchpad.net/principia
11
12
13Deploying
14=========
15
16As an example of deploying these sample charms as services and connectinng them.
17
18 $ juju bootstrap
19 $ juju deploy --repository=examples local:mysql
20 $ juju deploy --repository=examples local:wordpress
21 $ juju add-relation wordpress mysql
22 $ juju status
23
24The status command will show the address of the newly deployed wordpress service.
250
=== removed directory 'examples/precise'
=== removed directory 'examples/precise/mysql'
=== removed file 'examples/precise/mysql/config.yaml'
--- examples/precise/mysql/config.yaml 2012-06-17 15:39:48 +0000
+++ examples/precise/mysql/config.yaml 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1options: {}
20
=== removed directory 'examples/precise/mysql/hooks'
=== removed file 'examples/precise/mysql/hooks/db-relation-joined'
--- examples/precise/mysql/hooks/db-relation-joined 2012-06-17 15:39:48 +0000
+++ examples/precise/mysql/hooks/db-relation-joined 1970-01-01 00:00:00 +0000
@@ -1,40 +0,0 @@
1#!/bin/bash
2
3set -eu # -x for verbose logging to juju debug-log
4
5hostname=`unit-get private-address`
6
7# Get the mysql password that was generated by the install hook
8password=`cat /var/lib/juju/mysql.passwd`
9
10# Get the database name; this takes a service unit (ex: wordpress/0)
11# and extracts just 'wordpress'
12service=`echo $JUJU_REMOTE_UNIT | cut -d/ -f1` # split on /
13
14# Determine if a database needs to be created for this service
15existing_databases=`mysql --password="$password" --silent --execute 'show databases'`
16for db in $existing_databases; do
17 if [ "$db" = "$service" ] ; then
18 juju-log "Database already exists, publishing details and exiting"
19 service_password=`cat /var/lib/juju/$service.passwd`
20 # Save these settings on the relation; this will trigger the remote
21 # service unit
22 relation-set database="$service" user="$service" password="$service_password"
23 exit 0 # database already exists
24 fi
25done
26
27# Generate a strong password for the database, using /dev/urandom
28service_password=`pwgen 10 1`
29# Store service password, new service units of same service would need it
30echo $service_password >> /var/lib/juju/$service.passwd
31
32# Create new database and corresponding security settings
33juju-log "Creating new database and corresponding security settings"
34mysqladmin --password="$password" create "$service"
35echo "grant all on $service.* to $service identified by '$service_password'" | mysql --password="$password" --database="$service"
36mysqladmin --password="$password" flush-privileges
37
38# Save these settings on the relation; this will trigger the remote
39# service unit
40relation-set database="$service" user="$service" password="$service_password"
410
=== removed file 'examples/precise/mysql/hooks/install'
--- examples/precise/mysql/hooks/install 2012-08-24 16:02:42 +0000
+++ examples/precise/mysql/hooks/install 1970-01-01 00:00:00 +0000
@@ -1,32 +0,0 @@
1#!/bin/bash
2
3set -eu # -x for verbose logging to juju debug-log
4
5apt-get install -qqy debconf-utils python-mysqldb pwgen
6
7# Generate a strong password for the mysql service, using /dev/urandom
8PASSWORD=`pwgen 10 1`
9
10# Store the password for later use by the db-relation-changed hook for
11# this service unit. As a general note, for data that service units do
12# not need to share, simply use the machine's local file store.
13PASSFILE=/var/lib/juju/mysql.passwd
14if ! [ -f $PASSFILE ] ; then
15 touch $PASSFILE
16fi
17chmod 0600 $PASSFILE
18if ! [ -s $PASSFILE ] ; then
19 echo $PASSWORD >> /var/lib/juju/mysql.passwd
20fi
21
22echo mysql-server-5.1 mysql-server/root_password password $PASSWORD | debconf-set-selections
23echo mysql-server-5.1 mysql-server/root_password_again password $PASSWORD | debconf-set-selections
24
25juju-log "mysql-server settings preseeded, now installing via apt-get"
26DEBIAN_FRONTEND=noninteractive apt-get -y install -qq mysql-server
27
28juju-log "Editing my.cnf to allow listening on all interfaces"
29sed --in-place=old 's/127\.0\.0\.1/0.0.0.0/' /etc/mysql/my.cnf
30
31juju-log "Stopping mysql service"
32service mysql stop
330
=== removed file 'examples/precise/mysql/hooks/start'
--- examples/precise/mysql/hooks/start 2012-06-17 15:39:48 +0000
+++ examples/precise/mysql/hooks/start 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
1#!/bin/bash
2juju-log "Starting mysql service"
3service mysql start || service mysql restart
40
=== removed file 'examples/precise/mysql/hooks/stop'
--- examples/precise/mysql/hooks/stop 2012-06-17 15:39:48 +0000
+++ examples/precise/mysql/hooks/stop 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
1#!/bin/bash
2juju-log "Stopping mysql service"
3service mysql stop || true
40
=== removed file 'examples/precise/mysql/metadata.yaml'
--- examples/precise/mysql/metadata.yaml 2012-06-17 15:39:48 +0000
+++ examples/precise/mysql/metadata.yaml 1970-01-01 00:00:00 +0000
@@ -1,17 +0,0 @@
1name: mysql
2summary: "MySQL relational database provider"
3description: |
4 Installs and configures the MySQL package (mysqldb), then runs it.
5
6 Upon a consuming service establishing a relation, creates a new
7 database for that service, if the database does not yet
8 exist. Publishes the following relation settings for consuming
9 services:
10
11 database: database name
12 user: user name to access database
13 password: password to access the database
14 host: local hostname
15provides:
16 db:
17 interface: mysql
180
=== removed file 'examples/precise/mysql/revision'
--- examples/precise/mysql/revision 2012-06-17 15:39:48 +0000
+++ examples/precise/mysql/revision 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
111
20
=== removed directory 'examples/precise/php'
=== removed file 'examples/precise/php/config.yaml'
--- examples/precise/php/config.yaml 2012-06-17 15:39:48 +0000
+++ examples/precise/php/config.yaml 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1options:
2 application_file:
3 description: An application file to push.
4 type: string
5
60
=== removed directory 'examples/precise/php/hooks'
=== removed file 'examples/precise/php/hooks/config-changed'
--- examples/precise/php/hooks/config-changed 2012-06-17 15:39:48 +0000
+++ examples/precise/php/hooks/config-changed 1970-01-01 00:00:00 +0000
@@ -1,14 +0,0 @@
1#!/bin/bash
2
3hostname=`unit-get public-address`
4
5app_dir="/var/www/$hostname/"
6app_file=`config-get application_file`
7
8if [ -z $app_file ]; then
9 exit 0
10fi
11
12echo "$app_file" > $app_dir/index.php
13chmod a+r $app_dir/index.php
14
150
=== removed file 'examples/precise/php/hooks/install'
--- examples/precise/php/hooks/install 2012-06-17 15:39:48 +0000
+++ examples/precise/php/hooks/install 1970-01-01 00:00:00 +0000
@@ -1,49 +0,0 @@
1#!/bin/bash
2
3juju-log "Installing php5 via apt-get"
4set -eu # -x for verbose logging to juju debug-log
5
6apt-get -y install php5
7
8# Create an internal secret key for wordpress; this is unrelated to
9# the password generated for the admin user of wordpress
10hostname=`unit-get public-address`
11
12SITE_PATH="/var/www/$hostname"
13
14
15juju-log "Creating appropriate upload paths and directories"
16# Setup appropriate upload paths and directories
17mkdir -p $SITE_PATH
18chown -R root:www-data $SITE_PATH
19chmod -R a+rw $SITE_PATH
20chmod a+x $SITE_PATH
21
22# Write the apache config
23# XXX a future branch will change this to use augtool
24apache_config_file_path="/etc/apache2/sites-available/$hostname"
25juju-log "Writing apache config file $apache_config_file_path"
26cat > $apache_config_file_path <<EOF
27<VirtualHost *:80>
28 ServerName $hostname
29 DocumentRoot /var/www/$hostname
30 Options All
31 ErrorLog /var/log/apache2/$hostname-error.log
32 TransferLog /var/log/apache2/$hostname-access.log
33 <FilesMatch \.php$>
34 SetHandler application/x-httpd-php
35 </FilesMatch>
36</VirtualHost>
37EOF
38chmod 0644 $apache_config_file_path
39
40# Configure apache
41juju-log "Enabling apache modules: rewrite, vhost_alias"
42a2enmod rewrite
43a2enmod vhost_alias
44juju-log "Enabling apache site: $hostname"
45a2ensite $hostname
46
47# Restart apache
48juju-log "Restarting apache2 service"
49/etc/init.d/apache2 restart
500
=== removed file 'examples/precise/php/hooks/start'
--- examples/precise/php/hooks/start 2012-06-17 15:39:48 +0000
+++ examples/precise/php/hooks/start 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1#!/bin/bash
20
=== removed file 'examples/precise/php/hooks/stop'
--- examples/precise/php/hooks/stop 2012-06-17 15:39:48 +0000
+++ examples/precise/php/hooks/stop 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
1#!/bin/bash
2juju-log "Stopping apache"
3/etc/init.d/apache2 stop
40
=== removed file 'examples/precise/php/metadata.yaml'
--- examples/precise/php/metadata.yaml 2012-06-17 15:39:48 +0000
+++ examples/precise/php/metadata.yaml 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1name: php
2summary: "php container"
3description: |
4 PHP environment for your code.
50
=== removed file 'examples/precise/php/revision'
--- examples/precise/php/revision 2012-06-17 15:39:48 +0000
+++ examples/precise/php/revision 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
15
20
=== removed directory 'examples/precise/recorder'
=== removed directory 'examples/precise/recorder/hooks'
=== removed file 'examples/precise/recorder/hooks/install'
--- examples/precise/recorder/hooks/install 2012-07-19 11:01:09 +0000
+++ examples/precise/recorder/hooks/install 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
1#!/bin/bash
2# for testing
3open-port 8080
4\ No newline at end of file0\ No newline at end of file
51
=== removed file 'examples/precise/recorder/hooks/juju-info-relation-changed'
--- examples/precise/recorder/hooks/juju-info-relation-changed 2012-06-17 15:39:48 +0000
+++ examples/precise/recorder/hooks/juju-info-relation-changed 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1#!/bin/bash
2set -x
3
4juju-log "Changed relation with $JUJU_REMOTE_UNIT"
5juju-log `relation-list`
60
=== removed file 'examples/precise/recorder/hooks/juju-info-relation-departed'
--- examples/precise/recorder/hooks/juju-info-relation-departed 2012-06-17 15:39:48 +0000
+++ examples/precise/recorder/hooks/juju-info-relation-departed 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
1#!/bin/bash
2
3juju-log "Departed from $JUJU_REMOTE_UNIT"
40
=== removed file 'examples/precise/recorder/hooks/juju-info-relation-joined'
--- examples/precise/recorder/hooks/juju-info-relation-joined 2012-06-17 15:39:48 +0000
+++ examples/precise/recorder/hooks/juju-info-relation-joined 1970-01-01 00:00:00 +0000
@@ -1,6 +0,0 @@
1#!/bin/bash
2
3set -x
4
5juju-log "Joined with $JUJU_REMOTE_UNIT"
6juju-log `relation-list`
70
=== removed file 'examples/precise/recorder/metadata.yaml'
--- examples/precise/recorder/metadata.yaml 2012-06-17 15:39:48 +0000
+++ examples/precise/recorder/metadata.yaml 1970-01-01 00:00:00 +0000
@@ -1,9 +0,0 @@
1name: recorder
2summary: "Subordinate test charm"
3description: |
4 Records which units can see each other to show how subordinates work.
5subordinate: true
6requires:
7 juju-info:
8 interface: juju-info
9 scope: container
100
=== removed file 'examples/precise/recorder/revision'
--- examples/precise/recorder/revision 2012-06-17 15:39:48 +0000
+++ examples/precise/recorder/revision 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
11
2\ No newline at end of file0\ No newline at end of file
31
=== removed directory 'examples/precise/wordpress'
=== removed file 'examples/precise/wordpress/config.yaml'
--- examples/precise/wordpress/config.yaml 2012-06-17 15:39:48 +0000
+++ examples/precise/wordpress/config.yaml 1970-01-01 00:00:00 +0000
@@ -1,7 +0,0 @@
1options:
2 plugin:
3 description: |
4 The URL of a wordpress plugin which will be installed and made available in the Admin UI.
5 http://downloads.wordpress.org/plugin/buddypress.1.2.8.zip is a valid example.
6 type: string
7
80
=== removed directory 'examples/precise/wordpress/hooks'
=== removed file 'examples/precise/wordpress/hooks/config-changed'
--- examples/precise/wordpress/hooks/config-changed 2012-06-17 15:39:48 +0000
+++ examples/precise/wordpress/hooks/config-changed 1970-01-01 00:00:00 +0000
@@ -1,19 +0,0 @@
1#!/bin/bash
2
3hostname=`unit-get public-address`
4plugin_dir="/var/www/$hostname/wp-content/plugins"
5plugin_url=`config-get plugin`
6
7base_plugin=`basename ${plugin_url}`
8
9cd /tmp
10
11curl -O ${plugin_url}
12
13if [ $? = 0 ]; then
14 cd ${plugin_dir}
15 unzip /tmp/${base_plugin}
16else
17 juju-log -l WARNING "Unable to fetch ${plugin_url}"
18
19fi
200
=== removed file 'examples/precise/wordpress/hooks/db-relation-changed'
--- examples/precise/wordpress/hooks/db-relation-changed 2012-06-17 15:39:48 +0000
+++ examples/precise/wordpress/hooks/db-relation-changed 1970-01-01 00:00:00 +0000
@@ -1,100 +0,0 @@
1#!/bin/bash
2
3set -eu # -x for verbose logging to juju debug-log
4
5UPLOAD_PATH="/var/www/wp-uploads"
6
7hostname=`unit-get public-address`
8juju-log "Retrieved hostname: $hostname"
9
10# Check we haven't already been setup.
11config_file_path="/etc/wordpress/config-$hostname.php"
12if [ -f "$config_file_path" ] ; then
13 juju-log "Wordpress for site $config_file_path already Configured, exiting"
14 echo "Already Configured, Exiting"
15 exit 0 # already setup
16fi
17
18# Get the database settings; if not set, wait for this hook to be
19# invoked again
20database=`relation-get database`
21if [ -z "$database" ] ; then
22 exit 0 # wait for future handshake from database service unit
23fi
24
25# Our protocol on this interface ensures that all or none of the
26# settings are set. But we can verify the setting or the values if
27# more error checking if desired.
28user=`relation-get user`
29password=`relation-get password`
30host=`relation-get private-address`
31
32# Create an internal secret key for wordpress; this is unrelated to
33# the password generated for the admin user of wordpress
34secret_key=`pwgen 10 1`
35
36juju-log "Creating appropriate upload paths and directories"
37# Setup appropriate upload paths and directories
38ln -s /usr/share/wordpress "/var/www/$hostname"
39mkdir -p $UPLOAD_PATH
40mkdir -p "$UPLOAD_PATH/$hostname"
41chown -R root:www-data $UPLOAD_PATH
42chmod 0744 $UPLOAD_PATH
43chmod 0770 "$UPLOAD_PATH/$hostname"
44chown -R root:www-data "/var/www/$hostname/wp-content"
45
46juju-log "Writing wordpress config file $config_file_path"
47# Write the wordpress config
48cat > $config_file_path <<EOF
49<?php
50define('DB_NAME', '$database');
51define('DB_USER', '$user');
52define('DB_PASSWORD', '$password');
53define('DB_HOST', '$host');
54define('SECRET_KEY', '$secret_key');
55
56#This will disable the update notification.
57define('WP_CORE_UPDATE', false);
58
59\$table_prefix = 'wp_';
60\$server = '$host';
61\$loginsql = '$user';
62\$passsql = '$password';
63\$base = '$database';
64\$upload_path = '/var/www/wp-uploads/$hostname';
65\$upload_url_path = 'http://$hostname/wp-uploads';
66?>
67EOF
68chmod 0644 $config_file_path
69
70# Write the apache config
71# XXX a future branch will change this to use augtool
72apache_config_file_path="/etc/apache2/sites-available/$hostname"
73juju-log "Writing apache config file $apache_config_file_path"
74cat > $apache_config_file_path <<EOF
75<VirtualHost *:80>
76 ServerName $hostname
77 DocumentRoot /var/www/$hostname
78 Options All
79 ErrorLog /var/log/apache2/wp-error.log
80 TransferLog /var/log/apache2/wp-access.log
81 # Store uploads in /var/www/wp-uploads/$0
82 RewriteEngine On
83 RewriteRule ^/wp-uploads/(.*)$ /var/www/wp-uploads/%%{HTTP_HOST}/\$1
84</VirtualHost>
85EOF
86chmod 0644 $apache_config_file_path
87
88# Configure apache
89juju-log "Enabling apache modules: rewrite, vhost_alias"
90a2enmod rewrite
91a2enmod vhost_alias
92juju-log "Enabling apache site: $hostname"
93a2ensite $hostname
94
95# Restart apache
96juju-log "Restarting apache2 service"
97/etc/init.d/apache2 restart
98
99# Make it publicly visible, once the wordpress service is exposed
100open-port 80/tcp
1010
=== removed file 'examples/precise/wordpress/hooks/install'
--- examples/precise/wordpress/hooks/install 2012-06-17 15:39:48 +0000
+++ examples/precise/wordpress/hooks/install 1970-01-01 00:00:00 +0000
@@ -1,6 +0,0 @@
1#!/bin/bash
2
3juju-log "Installing wordpress, pwgen via apt-get"
4set -eu # -x for verbose logging to juju debug-log
5
6apt-get -y install wordpress pwgen unzip
70
=== removed file 'examples/precise/wordpress/hooks/start'
--- examples/precise/wordpress/hooks/start 2012-06-17 15:39:48 +0000
+++ examples/precise/wordpress/hooks/start 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1#!/bin/bash
20
=== removed file 'examples/precise/wordpress/hooks/stop'
--- examples/precise/wordpress/hooks/stop 2012-06-17 15:39:48 +0000
+++ examples/precise/wordpress/hooks/stop 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
1#!/bin/bash
2juju-log "Stopping apache"
3/etc/init.d/apache2 stop
40
=== removed file 'examples/precise/wordpress/metadata.yaml'
--- examples/precise/wordpress/metadata.yaml 2012-06-17 15:39:48 +0000
+++ examples/precise/wordpress/metadata.yaml 1970-01-01 00:00:00 +0000
@@ -1,10 +0,0 @@
1name: wordpress
2summary: "WordPress blog"
3description: |
4 Installs WordPress package (wordpress). Upon the database provider
5 providing the required database, and the relation settings
6 necessary to access it, completes the configuration of WordPress
7 and makes it available on the web.
8requires:
9 db:
10 interface: mysql
110
=== removed file 'examples/precise/wordpress/revision'
--- examples/precise/wordpress/revision 2012-06-17 15:39:48 +0000
+++ examples/precise/wordpress/revision 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
131
20
=== added directory 'juju'
=== removed directory 'juju'
=== added file 'juju/__init__.py'
--- juju/__init__.py 1970-01-01 00:00:00 +0000
+++ juju/__init__.py 2013-04-22 11:00:38 +0000
@@ -0,0 +1,2 @@
1#
2__version__ = '0.7'
03
=== removed file 'juju/__init__.py'
--- juju/__init__.py 2012-10-09 12:35:35 +0000
+++ juju/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,2 +0,0 @@
1#
2__version__ = '0.6'
30
=== added directory 'juju/agents'
=== removed directory 'juju/agents'
=== added file 'juju/agents/__init__.py'
--- juju/agents/__init__.py 1970-01-01 00:00:00 +0000
+++ juju/agents/__init__.py 2013-04-22 11:00:38 +0000
@@ -0,0 +1,1 @@
1#
02
=== removed file 'juju/agents/__init__.py'
--- juju/agents/__init__.py 2012-06-11 15:40:48 +0000
+++ juju/agents/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1#
20
=== added file 'juju/agents/base.py'
--- juju/agents/base.py 1970-01-01 00:00:00 +0000
+++ juju/agents/base.py 2013-04-22 11:00:38 +0000
@@ -0,0 +1,347 @@
1import argparse
2import os
3import logging
4import stat
5import sys
6import yaml
7
8import zookeeper
9
10from twisted.application import service
11from twisted.internet.defer import inlineCallbacks, returnValue
12from twisted.scripts._twistd_unix import UnixApplicationRunner, UnixAppLogger
13from twisted.python.log import PythonLoggingObserver
14
15from txzookeeper import ZookeeperClient
16from txzookeeper.managed import ManagedClient
17
18from juju.control.options import setup_twistd_options
19from juju.errors import NoConnection, JujuError
20from juju.lib.zklog import ZookeeperHandler
21from juju.lib.zk import CLIENT_SESSION_TIMEOUT
22from juju.state.environment import GlobalSettingsStateManager
23
24
25def load_client_id(path):
26 try:
27 with open(path) as f:
28 return yaml.load(f.read())
29 except IOError:
30 return None
31
32
33def save_client_id(path, client_id):
34 parent = os.path.dirname(path)
35 if not os.path.exists(parent):
36 os.makedirs(parent)
37 with open(path, "w") as f:
38 f.write(yaml.dump(client_id))
39 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
40
41
42class TwistedOptionNamespace(object):
43 """
44 An argparse namespace implementation that is compatible with twisted
45 config dictionary usage.
46 """
47
48 def __getitem__(self, key):
49 return self.__dict__[key]
50
51 def __setitem__(self, key, value):
52 self.__dict__[key] = value
53
54 def get(self, key, default=None):
55 return self.__dict__.get(key, default)
56
57 def has_key(self, key):
58 return key in self.__dict__
59
60
61class AgentLogger(UnixAppLogger):
62
63 def __init__(self, options):
64 super(AgentLogger, self).__init__(options)
65 self._loglevel = options.get("loglevel", logging.DEBUG)
66
67 def _getLogObserver(self):
68
69 if self._logfilename == "-":
70 log_file = sys.stdout
71 else:
72 log_file = open(self._logfilename, "a")
73
74 # Setup file logger
75 log_handler = logging.StreamHandler(log_file)
76 formatter = logging.Formatter(
77 "%(asctime)s: %(name)s@%(levelname)s: %(message)s")
78 log_handler.setFormatter(formatter)
79
80 # Also capture zookeeper logs (XXX not compatible with rotation)
81 zookeeper.set_log_stream(log_file)
82 zookeeper.set_debug_level(0)
83
84 # Configure logging.
85 root = logging.getLogger()
86 root.addHandler(log_handler)
87 root.setLevel(logging.getLevelName(self._loglevel))
88
89 # Twisted logging is painfully verbose on twisted.web, and
90 # there isn't a good way to distinguish different channels
91 # within twisted, so just utlize error level logging only for
92 # all of twisted.
93 twisted_log = logging.getLogger("twisted")
94 twisted_log.setLevel(logging.ERROR)
95
96 observer = PythonLoggingObserver()
97 return observer.emit
98
99
100class AgentRunner(UnixApplicationRunner):
101
102 application = None
103 loggerFactory = AgentLogger
104
105 def createOrGetApplication(self):
106 return self.application
107
108
109class BaseAgent(object, service.Service):
110
111 name = "juju-agent-unknown"
112 client = None
113
114 # Flag when enabling persistent topology watches, testing aid.
115 _watch_enabled = True
116
117 # Distributed debug log handler
118 _debug_log_handler = None
119
120 @classmethod
121 def run(cls):
122 """Runs the agent as a unix daemon.
123
124 Main entry point for starting an agent, parses cli options, and setups
125 a daemon using twistd as per options.
126 """
127 parser = argparse.ArgumentParser()
128 cls.setup_options(parser)
129 config = parser.parse_args(namespace=TwistedOptionNamespace())
130 runner = AgentRunner(config)
131 agent = cls()
132 agent.configure(config)
133 runner.application = agent.as_app()
134 runner.run()
135
136 @classmethod
137 def setup_options(cls, parser):
138 """Configure the argparse cli parser for the agent."""
139 return cls.setup_default_options(parser)
140
141 @classmethod
142 def setup_default_options(cls, parser):
143 """Setup default twistd daemon and agent options.
144
145 This method is intended as a utility for subclasses.
146
147 @param parser an argparse instance.
148 @type C{argparse.ArgumentParser}
149 """
150 setup_twistd_options(parser, cls)
151 setup_default_agent_options(parser, cls)
152
153 def as_app(self):
154 """
155 Return the agent as a C{twisted.application.service.Application}
156 """
157 app = service.Application(self.name)
158 self.setServiceParent(app)
159 return app
160
161 def configure(self, options):
162 """Configure the agent to handle its cli options.
163
164 Invoked called before the service is started.
165
166 @param options
167 @type C{TwistedOptionNamespace} an argparse namespace corresponding
168 to a dict.
169 """
170 if not options.get("zookeeper_servers"):
171 raise NoConnection("No zookeeper connection configured.")
172
173 if not os.path.exists(options.get("juju_directory", "")):
174 raise JujuError(
175 "Invalid juju-directory %r, does not exist." % (
176 options.get("juju_directory")))
177
178 if options["session_file"] is None:
179 raise JujuError("No session file specified")
180
181 self.config = options
182
183 @inlineCallbacks
184 def _kill_existing_session(self):
185 try:
186 # We might have died suddenly, in which case the session may
187 # still be alive. If this is the case, shoot it in the head, so
188 # it doesn't interfere with our attempts to recreate our state.
189 # (We need to be able to recreate our state *anyway*, and it's
190 # much simpler to force ourselves to recreate it every time than
191 # it is to mess around partially recreating partial state.)
192 client_id = load_client_id(self.config["session_file"])
193 if client_id is None:
194 return
195 temp_client = yield ZookeeperClient().connect(
196 self.config["zookeeper_servers"], client_id=client_id)
197 yield temp_client.close()
198 except zookeeper.ZooKeeperException:
199 # We don't really care what went wrong; just that we're not able
200 # to connect using the old session, and therefore we should be ok
201 # to start a fresh one without transient state hanging around.
202 pass
203
204 @inlineCallbacks
205 def connect(self):
206 """Return an authenticated connection to the juju zookeeper."""
207 yield self._kill_existing_session()
208 self.client = yield ManagedClient(
209 session_timeout=CLIENT_SESSION_TIMEOUT).connect(
210 self.config["zookeeper_servers"])
211 save_client_id(
212 self.config["session_file"], self.client.client_id)
213
214 principals = self.config.get("principals", ())
215 for principal in principals:
216 self.client.add_auth("digest", principal)
217
218 # bug work around to keep auth fast
219 if principals:
220 yield self.client.exists("/")
221
222 returnValue(self.client)
223
224 def start(self):
225 """Callback invoked on the agent's startup.
226
227 The agent will already be connected to zookeeper. Subclasses are
228 responsible for implementing.
229 """
230 raise NotImplementedError
231
232 def stop(self):
233 """Callback invoked on when the agent is shutting down."""
234 pass
235
236 # Twisted IService implementation, used for delegates to maintain naming
237 # conventions.
238 @inlineCallbacks
239 def startService(self):
240 yield self.connect()
241
242 # Start the global settings watch prior to starting the agent.
243 # Allows for debug log to be enabled early.
244 if self.get_watch_enabled():
245 yield self.start_global_settings_watch()
246
247 yield self.start()
248
249 @inlineCallbacks
250 def stopService(self):
251 try:
252 yield self.stop()
253 finally:
254 if self.client and self.client.connected:
255 self.client.close()
256 session_file = self.config["session_file"]
257 if os.path.exists(session_file):
258 os.unlink(session_file)
259
260 def set_watch_enabled(self, flag):
261 """Set boolean flag for whether this agent should watching zookeeper.
262
263 This is mainly used for testing, to allow for setting up the
264 various data scenarios, before enabling an agent watch which will
265 be observing state.
266 """
267 self._watch_enabled = bool(flag)
268
269 def get_watch_enabled(self):
270 """Returns a boolean if the agent should be settings state watches.
271
272 The meaning of this flag is typically agent specific, as each
273 agent has separate watches they'd like to establish on agent specific
274 state within zookeeper. In general if this flag is False, the agent
275 should refrain from establishing a watch on startup. This flag is
276 typically used by tests to isolate and test the watch behavior
277 independent of the agent startup, via construction of test data.
278 """
279 return self._watch_enabled
280
281 def start_global_settings_watch(self):
282 """Start watching the runtime state for configuration changes."""
283 self.global_settings_state = GlobalSettingsStateManager(self.client)
284 return self.global_settings_state.watch_settings_changes(
285 self.on_global_settings_change)
286
287 @inlineCallbacks
288 def on_global_settings_change(self, change):
289 """On global settings change, take action.
290 """
291 if (yield self.global_settings_state.is_debug_log_enabled()):
292 yield self.start_debug_log()
293 else:
294 self.stop_debug_log()
295
296 @inlineCallbacks
297 def start_debug_log(self):
298 """Enable the distributed debug log handler.
299 """
300 if self._debug_log_handler is not None:
301 returnValue(None)
302 context_name = self.get_agent_name()
303 self._debug_log_handler = ZookeeperHandler(
304 self.client, context_name)
305 yield self._debug_log_handler.open()
306 log_root = logging.getLogger()
307 log_root.addHandler(self._debug_log_handler)
308
309 def stop_debug_log(self):
310 """Disable any configured debug log handler.
311 """
312 if self._debug_log_handler is None:
313 return
314 handler, self._debug_log_handler = self._debug_log_handler, None
315 log_root = logging.getLogger()
316 log_root.removeHandler(handler)
317
318 def get_agent_name(self):
319 """Return the agent's name and context such that it can be identified.
320
321 Subclasses should override this to provide additional context and
322 unique naming.
323 """
324 return self.__class__.__name__
325
326
327def setup_default_agent_options(parser, cls):
328
329 principals_default = os.environ.get("JUJU_PRINCIPALS", "").split()
330 parser.add_argument(
331 "--principal", "-e",
332 action="append", dest="principals", default=principals_default,
333 help="Agent principals to utilize for the zookeeper connection")
334
335 servers_default = os.environ.get("JUJU_ZOOKEEPER", "")
336 parser.add_argument(
337 "--zookeeper-servers", "-z", default=servers_default,
338 help="juju Zookeeper servers to connect to ($JUJU_ZOOKEEPER)")
339
340 juju_home = os.environ.get("JUJU_HOME", "/var/lib/juju")
341 parser.add_argument(
342 "--juju-directory", default=juju_home, type=os.path.abspath,
343 help="juju working directory ($JUJU_HOME)")
344
345 parser.add_argument(
346 "--session-file", default=None, type=os.path.abspath,
347 help="like a pidfile, but for the zookeeper session id")
0348
=== removed file 'juju/agents/base.py'
--- juju/agents/base.py 2012-06-11 15:40:48 +0000
+++ juju/agents/base.py 1970-01-01 00:00:00 +0000
@@ -1,345 +0,0 @@
1import argparse
2import os
3import logging
4import stat
5import sys
6import yaml
7
8import zookeeper
9
10from twisted.application import service
11from twisted.internet.defer import inlineCallbacks, returnValue
12from twisted.scripts._twistd_unix import UnixApplicationRunner, UnixAppLogger
13from twisted.python.log import PythonLoggingObserver
14
15from txzookeeper import ZookeeperClient
16from txzookeeper.managed import ManagedClient
17
18from juju.control.options import setup_twistd_options
19from juju.errors import NoConnection, JujuError
20from juju.lib.zklog import ZookeeperHandler
21from juju.state.environment import GlobalSettingsStateManager
22
23
24def load_client_id(path):
25 try:
26 with open(path) as f:
27 return yaml.load(f.read())
28 except IOError:
29 return None
30
31
32def save_client_id(path, client_id):
33 parent = os.path.dirname(path)
34 if not os.path.exists(parent):
35 os.makedirs(parent)
36 with open(path, "w") as f:
37 f.write(yaml.dump(client_id))
38 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
39
40
41class TwistedOptionNamespace(object):
42 """
43 An argparse namespace implementation that is compatible with twisted
44 config dictionary usage.
45 """
46
47 def __getitem__(self, key):
48 return self.__dict__[key]
49
50 def __setitem__(self, key, value):
51 self.__dict__[key] = value
52
53 def get(self, key, default=None):
54 return self.__dict__.get(key, default)
55
56 def has_key(self, key):
57 return key in self.__dict__
58
59
60class AgentLogger(UnixAppLogger):
61
62 def __init__(self, options):
63 super(AgentLogger, self).__init__(options)
64 self._loglevel = options.get("loglevel", logging.DEBUG)
65
66 def _getLogObserver(self):
67
68 if self._logfilename == "-":
69 log_file = sys.stdout
70 else:
71 log_file = open(self._logfilename, "a")
72
73 # Setup file logger
74 log_handler = logging.StreamHandler(log_file)
75 formatter = logging.Formatter(
76 "%(asctime)s: %(name)s@%(levelname)s: %(message)s")
77 log_handler.setFormatter(formatter)
78
79 # Also capture zookeeper logs (XXX not compatible with rotation)
80 zookeeper.set_log_stream(log_file)
81 zookeeper.set_debug_level(0)
82
83 # Configure logging.
84 root = logging.getLogger()
85 root.addHandler(log_handler)
86 root.setLevel(logging.getLevelName(self._loglevel))
87
88 # Twisted logging is painfully verbose on twisted.web, and
89 # there isn't a good way to distinguish different channels
90 # within twisted, so just utlize error level logging only for
91 # all of twisted.
92 twisted_log = logging.getLogger("twisted")
93 twisted_log.setLevel(logging.ERROR)
94
95 observer = PythonLoggingObserver()
96 return observer.emit
97
98
99class AgentRunner(UnixApplicationRunner):
100
101 application = None
102 loggerFactory = AgentLogger
103
104 def createOrGetApplication(self):
105 return self.application
106
107
108class BaseAgent(object, service.Service):
109
110 name = "juju-agent-unknown"
111 client = None
112
113 # Flag when enabling persistent topology watches, testing aid.
114 _watch_enabled = True
115
116 # Distributed debug log handler
117 _debug_log_handler = None
118
119 @classmethod
120 def run(cls):
121 """Runs the agent as a unix daemon.
122
123 Main entry point for starting an agent, parses cli options, and setups
124 a daemon using twistd as per options.
125 """
126 parser = argparse.ArgumentParser()
127 cls.setup_options(parser)
128 config = parser.parse_args(namespace=TwistedOptionNamespace())
129 runner = AgentRunner(config)
130 agent = cls()
131 agent.configure(config)
132 runner.application = agent.as_app()
133 runner.run()
134
135 @classmethod
136 def setup_options(cls, parser):
137 """Configure the argparse cli parser for the agent."""
138 return cls.setup_default_options(parser)
139
140 @classmethod
141 def setup_default_options(cls, parser):
142 """Setup default twistd daemon and agent options.
143
144 This method is intended as a utility for subclasses.
145
146 @param parser an argparse instance.
147 @type C{argparse.ArgumentParser}
148 """
149 setup_twistd_options(parser, cls)
150 setup_default_agent_options(parser, cls)
151
152 def as_app(self):
153 """
154 Return the agent as a C{twisted.application.service.Application}
155 """
156 app = service.Application(self.name)
157 self.setServiceParent(app)
158 return app
159
160 def configure(self, options):
161 """Configure the agent to handle its cli options.
162
163 Invoked called before the service is started.
164
165 @param options
166 @type C{TwistedOptionNamespace} an argparse namespace corresponding
167 to a dict.
168 """
169 if not options.get("zookeeper_servers"):
170 raise NoConnection("No zookeeper connection configured.")
171
172 if not os.path.exists(options.get("juju_directory", "")):
173 raise JujuError(
174 "Invalid juju-directory %r, does not exist." % (
175 options.get("juju_directory")))
176
177 if options["session_file"] is None:
178 raise JujuError("No session file specified")
179
180 self.config = options
181
182 @inlineCallbacks
183 def _kill_existing_session(self):
184 try:
185 # We might have died suddenly, in which case the session may
186 # still be alive. If this is the case, shoot it in the head, so
187 # it doesn't interfere with our attempts to recreate our state.
188 # (We need to be able to recreate our state *anyway*, and it's
189 # much simpler to force ourselves to recreate it every time than
190 # it is to mess around partially recreating partial state.)
191 client_id = load_client_id(self.config["session_file"])
192 if client_id is None:
193 return
194 temp_client = yield ZookeeperClient().connect(
195 self.config["zookeeper_servers"], client_id=client_id)
196 yield temp_client.close()
197 except zookeeper.ZooKeeperException:
198 # We don't really care what went wrong; just that we're not able
199 # to connect using the old session, and therefore we should be ok
200 # to start a fresh one without transient state hanging around.
201 pass
202
203 @inlineCallbacks
204 def connect(self):
205 """Return an authenticated connection to the juju zookeeper."""
206 yield self._kill_existing_session()
207 self.client = yield ManagedClient().connect(
208 self.config["zookeeper_servers"])
209 save_client_id(
210 self.config["session_file"], self.client.client_id)
211
212 principals = self.config.get("principals", ())
213 for principal in principals:
214 self.client.add_auth("digest", principal)
215
216 # bug work around to keep auth fast
217 if principals:
218 yield self.client.exists("/")
219
220 returnValue(self.client)
221
222 def start(self):
223 """Callback invoked on the agent's startup.
224
225 The agent will already be connected to zookeeper. Subclasses are
226 responsible for implementing.
227 """
228 raise NotImplementedError
229
230 def stop(self):
231 """Callback invoked on when the agent is shutting down."""
232 pass
233
234 # Twisted IService implementation, used for delegates to maintain naming
235 # conventions.
236 @inlineCallbacks
237 def startService(self):
238 yield self.connect()
239
240 # Start the global settings watch prior to starting the agent.
241 # Allows for debug log to be enabled early.
242 if self.get_watch_enabled():
243 yield self.start_global_settings_watch()
244
245 yield self.start()
246
247 @inlineCallbacks
248 def stopService(self):
249 try:
250 yield self.stop()
251 finally:
252 if self.client and self.client.connected:
253 self.client.close()
254 session_file = self.config["session_file"]
255 if os.path.exists(session_file):
256 os.unlink(session_file)
257
258 def set_watch_enabled(self, flag):
259 """Set boolean flag for whether this agent should watching zookeeper.
260
261 This is mainly used for testing, to allow for setting up the
262 various data scenarios, before enabling an agent watch which will
263 be observing state.
264 """
265 self._watch_enabled = bool(flag)
266
267 def get_watch_enabled(self):
268 """Returns a boolean if the agent should be settings state watches.
269
270 The meaning of this flag is typically agent specific, as each
271 agent has separate watches they'd like to establish on agent specific
272 state within zookeeper. In general if this flag is False, the agent
273 should refrain from establishing a watch on startup. This flag is
274 typically used by tests to isolate and test the watch behavior
275 independent of the agent startup, via construction of test data.
276 """
277 return self._watch_enabled
278
279 def start_global_settings_watch(self):
280 """Start watching the runtime state for configuration changes."""
281 self.global_settings_state = GlobalSettingsStateManager(self.client)
282 return self.global_settings_state.watch_settings_changes(
283 self.on_global_settings_change)
284
285 @inlineCallbacks
286 def on_global_settings_change(self, change):
287 """On global settings change, take action.
288 """
289 if (yield self.global_settings_state.is_debug_log_enabled()):
290 yield self.start_debug_log()
291 else:
292 self.stop_debug_log()
293
294 @inlineCallbacks
295 def start_debug_log(self):
296 """Enable the distributed debug log handler.
297 """
298 if self._debug_log_handler is not None:
299 returnValue(None)
300 context_name = self.get_agent_name()
301 self._debug_log_handler = ZookeeperHandler(
302 self.client, context_name)
303 yield self._debug_log_handler.open()
304 log_root = logging.getLogger()
305 log_root.addHandler(self._debug_log_handler)
306
307 def stop_debug_log(self):
308 """Disable any configured debug log handler.
309 """
310 if self._debug_log_handler is None:
311 return
312 handler, self._debug_log_handler = self._debug_log_handler, None
313 log_root = logging.getLogger()
314 log_root.removeHandler(handler)
315
316 def get_agent_name(self):
317 """Return the agent's name and context such that it can be identified.
318
319 Subclasses should override this to provide additional context and
320 unique naming.
321 """
322 return self.__class__.__name__
323
324
325def setup_default_agent_options(parser, cls):
326
327 principals_default = os.environ.get("JUJU_PRINCIPALS", "").split()
328 parser.add_argument(
329 "--principal", "-e",
330 action="append", dest="principals", default=principals_default,
331 help="Agent principals to utilize for the zookeeper connection")
332
333 servers_default = os.environ.get("JUJU_ZOOKEEPER", "")
334 parser.add_argument(
335 "--zookeeper-servers", "-z", default=servers_default,
336 help="juju Zookeeper servers to connect to ($JUJU_ZOOKEEPER)")
337
338 juju_home = os.environ.get("JUJU_HOME", "/var/lib/juju")
339 parser.add_argument(
340 "--juju-directory", default=juju_home, type=os.path.abspath,
341 help="juju working directory ($JUJU_HOME)")
342
343 parser.add_argument(
344 "--session-file", default=None, type=os.path.abspath,
345 help="like a pidfile, but for the zookeeper session id")
3460
=== added file 'juju/agents/dummy.py'
--- juju/agents/dummy.py 1970-01-01 00:00:00 +0000
+++ juju/agents/dummy.py 2013-04-22 11:00:38 +0000
@@ -0,0 +1,16 @@
1
2from .base import BaseAgent
3
4
5class DummyAgent(BaseAgent):
6 """A do nothing juju agent.
7
8 A bit like a dog, it just lies around basking in the sun,
9 doing nothing, nonetheless its quite content. :-)
10 """
11
12 def start(self):
13 """nothing to see here, move along."""
14
15if __name__ == '__main__':
16 DummyAgent.run()
017
=== removed file 'juju/agents/dummy.py'
--- juju/agents/dummy.py 2012-06-11 15:40:48 +0000
+++ juju/agents/dummy.py 1970-01-01 00:00:00 +0000
@@ -1,16 +0,0 @@
1
2from .base import BaseAgent
3
4
5class DummyAgent(BaseAgent):
6 """A do nothing juju agent.
7
8 A bit like a dog, it just lies around basking in the sun,
9 doing nothing, nonetheless its quite content. :-)
10 """
11
12 def start(self):
13 """nothing to see here, move along."""
14
15if __name__ == '__main__':
16 DummyAgent.run()
170
=== added file 'juju/agents/machine.py'
--- juju/agents/machine.py 1970-01-01 00:00:00 +0000
+++ juju/agents/machine.py 2013-04-22 11:00:38 +0000
@@ -0,0 +1,126 @@
1import logging
2import os
3
4from twisted.internet.defer import inlineCallbacks
5
6from juju.errors import JujuError
7from juju.state.machine import MachineStateManager
8from juju.state.service import ServiceStateManager
9from juju.unit.deploy import UnitDeployer
10
11from .base import BaseAgent
12
13
14log = logging.getLogger("juju.agents.machine")
15
16
17class MachineAgent(BaseAgent):
18 """A juju machine agent.
19
20 The machine agent is responsible for monitoring service units
21 assigned to a machine. If a new unit is assigned to machine, the
22 machine agent will download the charm, create a working
23 space for the service unit agent, and then launch it.
24
25 Additionally the machine agent will monitor the running service
26 unit agents on the machine, via their ephemeral nodes, and
27 restart them if they die.
28 """
29
30 name = "juju-machine-agent"
31 unit_agent_module = "juju.agents.unit"
32
33 @property
34 def units_directory(self):
35 return os.path.join(self.config["juju_directory"], "units")
36
37 @property
38 def unit_state_directory(self):
39 return os.path.join(self.config["juju_directory"], "state")
40
41 @inlineCallbacks
42 def start(self):
43 """Start the machine agent.
44
45 Creates state directories on the machine, retrieves the machine state,
46 and enables watch on assigned units.
47 """
48 if not os.path.exists(self.units_directory):
49 os.makedirs(self.units_directory)
50
51 if not os.path.exists(self.unit_state_directory):
52 os.makedirs(self.unit_state_directory)
53
54 # Get state managers we'll be utilizing.
55 self.service_state_manager = ServiceStateManager(self.client)
56 self.unit_deployer = UnitDeployer(
57 self.client, self.get_machine_id(), self.config["juju_directory"])
58 yield self.unit_deployer.start()
59
60 # Retrieve the machine state for the machine we represent.
61 machine_manager = MachineStateManager(self.client)
62 self.machine_state = yield machine_manager.get_machine_state(
63 self.get_machine_id())
64
65 # Watch assigned units for the machine.
66 if self.get_watch_enabled():
67 self.machine_state.watch_assigned_units(
68 self.watch_service_units)
69
70 # Connect the machine agent, broadcasting presence to the world.
71 yield self.machine_state.connect_agent()
72 log.info("Machine agent started id:%s" % self.get_machine_id())
73
74 @inlineCallbacks
75 def watch_service_units(self, old_units, new_units):
76 """Callback invoked when the assigned service units change.
77 """
78 if old_units is None:
79 old_units = set()
80
81 log.debug(
82 "Units changed old:%s new:%s", old_units, new_units)
83
84 stopped = old_units - new_units
85 started = new_units - old_units
86
87 for unit_name in stopped:
88 log.debug("Stopping service unit: %s ...", unit_name)
89 try:
90 yield self.unit_deployer.kill_service_unit(unit_name)
91 except Exception:
92 log.exception("Error stopping unit: %s", unit_name)
93
94 for unit_name in started:
95 log.debug("Starting service unit: %s ...", unit_name)
96 try:
97 yield self.unit_deployer.start_service_unit(unit_name)
98 except Exception:
99 log.exception("Error starting unit: %s", unit_name)
100
101 def get_machine_id(self):
102 """Get the id of the machine as known within the zk state."""
103 return self.config["machine_id"]
104
105 def get_agent_name(self):
106 return "Machine:%s" % (self.get_machine_id())
107
108 def configure(self, options):
109 super(MachineAgent, self).configure(options)
110 if not options.get("machine_id"):
111 msg = ("--machine-id must be provided in the command line, "
112 "or $JUJU_MACHINE_ID in the environment")
113 raise JujuError(msg)
114
115 @classmethod
116 def setup_options(cls, parser):
117 super(MachineAgent, cls).setup_options(parser)
118
119 machine_id = os.environ.get("JUJU_MACHINE_ID", "")
120 parser.add_argument(
121 "--machine-id", default=machine_id)
122 return parser
123
124
125if __name__ == "__main__":
126 MachineAgent().run()
0127
=== removed file 'juju/agents/machine.py'
--- juju/agents/machine.py 2012-08-24 16:02:42 +0000
+++ juju/agents/machine.py 1970-01-01 00:00:00 +0000
@@ -1,126 +0,0 @@
1import logging
2import os
3
4from twisted.internet.defer import inlineCallbacks
5
6from juju.errors import JujuError
7from juju.state.machine import MachineStateManager
8from juju.state.service import ServiceStateManager
9from juju.unit.deploy import UnitDeployer
10
11from .base import BaseAgent
12
13
14log = logging.getLogger("juju.agents.machine")
15
16
17class MachineAgent(BaseAgent):
18 """A juju machine agent.
19
20 The machine agent is responsible for monitoring service units
21 assigned to a machine. If a new unit is assigned to machine, the
22 machine agent will download the charm, create a working
23 space for the service unit agent, and then launch it.
24
25 Additionally the machine agent will monitor the running service
26 unit agents on the machine, via their ephemeral nodes, and
27 restart them if they die.
28 """
29
30 name = "juju-machine-agent"
31 unit_agent_module = "juju.agents.unit"
32
33 @property
34 def units_directory(self):
35 return os.path.join(self.config["juju_directory"], "units")
36
37 @property
38 def unit_state_directory(self):
39 return os.path.join(self.config["juju_directory"], "state")
40
41 @inlineCallbacks
42 def start(self):
43 """Start the machine agent.
44
45 Creates state directories on the machine, retrieves the machine state,
46 and enables watch on assigned units.
47 """
48 if not os.path.exists(self.units_directory):
49 os.makedirs(self.units_directory)
50
51 if not os.path.exists(self.unit_state_directory):
52 os.makedirs(self.unit_state_directory)
53
54 # Get state managers we'll be utilizing.
55 self.service_state_manager = ServiceStateManager(self.client)
56 self.unit_deployer = UnitDeployer(
57 self.client, self.get_machine_id(), self.config["juju_directory"])
58 yield self.unit_deployer.start()
59
60 # Retrieve the machine state for the machine we represent.
61 machine_manager = MachineStateManager(self.client)
62 self.machine_state = yield machine_manager.get_machine_state(
63 self.get_machine_id())
64
65 # Watch assigned units for the machine.
66 if self.get_watch_enabled():
67 self.machine_state.watch_assigned_units(
68 self.watch_service_units)
69
70 # Connect the machine agent, broadcasting presence to the world.
71 yield self.machine_state.connect_agent()
72 log.info("Machine agent started id:%s" % self.get_machine_id())
73
74 @inlineCallbacks
75 def watch_service_units(self, old_units, new_units):
76 """Callback invoked when the assigned service units change.
77 """
78 if old_units is None:
79 old_units = set()
80
81 log.debug(
82 "Units changed old:%s new:%s", old_units, new_units)
83
84 stopped = old_units - new_units
85 started = new_units - old_units
86
87 for unit_name in stopped:
88 log.debug("Stopping service unit: %s ...", unit_name)
89 try:
90 yield self.unit_deployer.kill_service_unit(unit_name)
91 except Exception:
92 log.exception("Error stopping unit: %s", unit_name)
93
94 for unit_name in started:
95 log.debug("Starting service unit: %s ...", unit_name)
96 try:
97 yield self.unit_deployer.start_service_unit(unit_name)
98 except Exception:
99 log.exception("Error starting unit: %s", unit_name)
100
101 def get_machine_id(self):
102 """Get the id of the machine as known within the zk state."""
103 return self.config["machine_id"]
104
105 def get_agent_name(self):
106 return "Machine:%s" % (self.get_machine_id())
107
108 def configure(self, options):
109 super(MachineAgent, self).configure(options)
110 if not options.get("machine_id"):
111 msg = ("--machine-id must be provided in the command line, "
112 "or $JUJU_MACHINE_ID in the environment")
113 raise JujuError(msg)
114
115 @classmethod
116 def setup_options(cls, parser):
117 super(MachineAgent, cls).setup_options(parser)
118
119 machine_id = os.environ.get("JUJU_MACHINE_ID", "")
120 parser.add_argument(
121 "--machine-id", default=machine_id)
122 return parser
123
124
125if __name__ == "__main__":
126 MachineAgent().run()
1270
=== added file 'juju/agents/provision.py'
--- juju/agents/provision.py 1970-01-01 00:00:00 +0000
+++ juju/agents/provision.py 2013-04-22 11:00:38 +0000
@@ -0,0 +1,239 @@
1import logging
2
3from twisted.internet.defer import inlineCallbacks, returnValue, succeed
4from zookeeper import NoNodeException
5
6from juju.environment.config import EnvironmentsConfig
7from juju.errors import ProviderError
8from juju.lib.twistutils import concurrent_execution_guard
9from juju.state.errors import MachineStateNotFound, StopWatcher
10from juju.state.firewall import FirewallManager
11from juju.state.machine import MachineStateManager
12from juju.state.service import ServiceStateManager
13
14from .base import BaseAgent
15
16
17log = logging.getLogger("juju.agents.provision")
18
19
20class ProvisioningAgent(BaseAgent):
21
22 name = "juju-provisoning-agent"
23
24 _current_machines = ()
25
26 # time in seconds
27 machine_check_period = 60
28
29 def get_agent_name(self):
30 return "provision:%s" % (self.environment.type)
31
32 @inlineCallbacks
33 def start(self):
34 self._running = True
35
36 self.environment = yield self.configure_environment()
37 self.provider = self.environment.get_machine_provider()
38 self.machine_state_manager = MachineStateManager(self.client)
39 self.service_state_manager = ServiceStateManager(self.client)
40 self.firewall_manager = FirewallManager(
41 self.client, self.is_running, self.provider)
42
43 if self.get_watch_enabled():
44 self.machine_state_manager.watch_machine_states(
45 self.watch_machine_changes)
46 self.service_state_manager.watch_service_states(
47 self.firewall_manager.watch_service_changes)
48 from twisted.internet import reactor
49 reactor.callLater(
50 self.machine_check_period, self.periodic_machine_check)
51 log.info("Started provisioning agent")
52 else:
53 log.info("Started provisioning agent without watches enabled")
54
55 def stop(self):
56 log.info("Stopping provisioning agent")
57 self._running = False
58 return succeed(True)
59
60 def is_running(self):
61 """Whether this agent is running or not."""
62 return self._running
63
64 @inlineCallbacks
65 def configure_environment(self):
66 """The provisioning agent configure its environment on start or change.
67
68 The environment contains the configuration th agent needs to interact
69 with its machine provider, in order to do its work. This configuration
70 data is deployed lazily over an encrypted connection upon first usage.
71
72 The agent waits for this data to exist before completing its startup.
73 """
74 try:
75 get_d, watch_d = self.client.get_and_watch("/environment")
76 environment_data, stat = yield get_d
77 watch_d.addCallback(self._on_environment_changed)
78 except NoNodeException:
79 # Wait till the environment node appears. play twisted gymnastics
80 exists_d, watch_d = self.client.exists_and_watch("/environment")
81 stat = yield exists_d
82 if stat:
83 environment = yield self.configure_environment()
84 else:
85 watch_d.addCallback(
86 lambda result: self.configure_environment())
87 if not stat:
88 environment = yield watch_d
89 returnValue(environment)
90
91 config = EnvironmentsConfig()
92 config.parse(environment_data)
93 returnValue(config.get_default())
94
95 @inlineCallbacks
96 def _on_environment_changed(self, event):
97 """Reload the environment if its data changes."""
98
99 if event.type_name == "deleted":
100 return
101
102 self.environment = yield self.configure_environment()
103 self.provider = self.environment.get_machine_provider()
104
105 def periodic_machine_check(self):
106 """A periodic checking of machine states and provider machines.
107
108 In addition to the on demand changes to zookeeper states that are
109 monitored by L{watch_machine_changes}, the periodic machine check
110 performs non zookeeper state related verification by periodically
111 checking the last current provider machine states against the
112 last known zookeeper state.
113
114 Primarily this helps in recovering from transient error conditions
115 which may have prevent processing of an individual machine state, as
116 well as verifying the current state of the provider's running machines
117 against the zk state, thus pruning unused resources.
118 """
119 from twisted.internet import reactor
120 d = self.process_machines(self._current_machines)
121 d.addBoth(
122 lambda result: reactor.callLater(
123 self.machine_check_period, self.periodic_machine_check))
124 return d
125
126 @inlineCallbacks
127 def watch_machine_changes(self, old_machines, new_machines):
128 """Watches and processes machine state changes.
129
130 This function is used to subscribe to topology changes, and
131 specifically changes to machines within the topology. It performs
132 work against the machine provider to ensure that the currently
133 running state of the juju cluster corresponds to the topology
134 via creation and deletion of machines.
135
136 The subscription utilized is a permanent one, meaning that this
137 function will automatically be rescheduled to run whenever a topology
138 state change happens that involves machines.
139
140 This functional also caches the current set of machines as an agent
141 instance attribute.
142
143 @param old_machines machine ids as existed in the previous topology.
144 @param new_machines machine ids as exist in the current topology.
145 """
146 if not self._running:
147 raise StopWatcher()
148 log.debug("Machines changed old:%s new:%s", old_machines, new_machines)
149 self._current_machines = new_machines
150 try:
151 yield self.process_machines(self._current_machines)
152 except Exception:
153 # Log and effectively retry later in periodic_machine_check
154 log.exception(
155 "Got unexpected exception in processing machines,"
156 " will retry")
157
158 @concurrent_execution_guard("_processing_machines")
159 @inlineCallbacks
160 def process_machines(self, current_machines):
161 """Ensure the currently running machines correspond to state.
162
163 At the end of each process_machines execution, verify that all
164 running machines within the provider correspond to machine_ids within
165 the topology. If they don't then shut them down.
166
167 Utilizes concurrent execution guard, to ensure that this is only being
168 executed at most once per process.
169 """
170 # XXX this is obviously broken, but the margins of 80 columns prevent
171 # me from describing. hint think concurrent agents, and use a lock.
172
173 # map of instance_id -> machine
174 try:
175 provider_machines = yield self.provider.get_machines()
176 except ProviderError:
177 log.exception("Cannot get machine list")
178 return
179
180 provider_machines = dict(
181 [(m.instance_id, m) for m in provider_machines])
182
183 instance_ids = []
184 for machine_state_id in current_machines:
185 try:
186 instance_id = yield self.process_machine(
187 machine_state_id, provider_machines)
188 except (MachineStateNotFound, ProviderError):
189 log.exception("Cannot process machine %s", machine_state_id)
190 continue
191 instance_ids.append(instance_id)
192
193 # Terminate all unused juju machines running within the cluster.
194 unused = set(provider_machines.keys()) - set(instance_ids)
195 for instance_id in unused:
196 log.info("Shutting down machine id:%s ...", instance_id)
197 machine = provider_machines[instance_id]
198
199 try:
200 yield self.provider.shutdown_machine(machine)
201 except ProviderError:
202 log.exception("Cannot shutdown machine %s", instance_id)
203 continue
204
205 @inlineCallbacks
206 def process_machine(self, machine_state_id, provider_machine_map):
207 """Ensure a provider machine for a machine state id.
208
209 For each machine_id in new machines which represents the current state
210 of the topology:
211
212 * Check to ensure its state reflects that it has been
213 launched. If it hasn't then create the machine and update
214 the state.
215
216 * Watch the machine's assigned services so that changes can
217 be applied to the firewall for service exposing support.
218 """
219 # fetch the machine state
220 machine_state = yield self.machine_state_manager.get_machine_state(
221 machine_state_id)
222 instance_id = yield machine_state.get_instance_id()
223
224 # Verify a machine id has state and is running, else launch it.
225 if instance_id is None or not instance_id in provider_machine_map:
226 log.info("Starting machine id:%s ...", machine_state.id)
227 constraints = yield machine_state.get_constraints()
228 machines = yield self.provider.start_machine(
229 {"machine-id": machine_state.id, "constraints": constraints})
230 instance_id = machines[0].instance_id
231 yield machine_state.set_instance_id(instance_id)
232
233 # The firewall manager also needs to be checked for any
234 # outstanding retries on this machine
235 yield self.firewall_manager.process_machine(machine_state)
236 returnValue(instance_id)
237
238if __name__ == '__main__':
239 ProvisioningAgent().run()
0240
=== removed file 'juju/agents/provision.py'
--- juju/agents/provision.py 2012-07-19 11:01:09 +0000
+++ juju/agents/provision.py 1970-01-01 00:00:00 +0000
@@ -1,239 +0,0 @@
1import logging
2
3from twisted.internet.defer import inlineCallbacks, returnValue, succeed
4from zookeeper import NoNodeException
5
6from juju.environment.config import EnvironmentsConfig
7from juju.errors import ProviderError
8from juju.lib.twistutils import concurrent_execution_guard
9from juju.state.errors import MachineStateNotFound, StopWatcher
10from juju.state.firewall import FirewallManager
11from juju.state.machine import MachineStateManager
12from juju.state.service import ServiceStateManager
13
14from .base import BaseAgent
15
16
17log = logging.getLogger("juju.agents.provision")
18
19
20class ProvisioningAgent(BaseAgent):
21
22 name = "juju-provisoning-agent"
23
24 _current_machines = ()
25
26 # time in seconds
27 machine_check_period = 60
28
29 def get_agent_name(self):
30 return "provision:%s" % (self.environment.type)
31
32 @inlineCallbacks
33 def start(self):
34 self._running = True
35
36 self.environment = yield self.configure_environment()
37 self.provider = self.environment.get_machine_provider()
38 self.machine_state_manager = MachineStateManager(self.client)
39 self.service_state_manager = ServiceStateManager(self.client)
40 self.firewall_manager = FirewallManager(
41 self.client, self.is_running, self.provider)
42
43 if self.get_watch_enabled():
44 self.machine_state_manager.watch_machine_states(
45 self.watch_machine_changes)
46 self.service_state_manager.watch_service_states(
47 self.firewall_manager.watch_service_changes)
48 from twisted.internet import reactor
49 reactor.callLater(
50 self.machine_check_period, self.periodic_machine_check)
51 log.info("Started provisioning agent")
52 else:
53 log.info("Started provisioning agent without watches enabled")
54
55 def stop(self):
56 log.info("Stopping provisioning agent")
57 self._running = False
58 return succeed(True)
59
60 def is_running(self):
61 """Whether this agent is running or not."""
62 return self._running
63
64 @inlineCallbacks
65 def configure_environment(self):
66 """The provisioning agent configure its environment on start or change.
67
68 The environment contains the configuration th agent needs to interact
69 with its machine provider, in order to do its work. This configuration
70 data is deployed lazily over an encrypted connection upon first usage.
71
72 The agent waits for this data to exist before completing its startup.
73 """
74 try:
75 get_d, watch_d = self.client.get_and_watch("/environment")
76 environment_data, stat = yield get_d
77 watch_d.addCallback(self._on_environment_changed)
78 except NoNodeException:
79 # Wait till the environment node appears. play twisted gymnastics
80 exists_d, watch_d = self.client.exists_and_watch("/environment")
81 stat = yield exists_d
82 if stat:
83 environment = yield self.configure_environment()
84 else:
85 watch_d.addCallback(
86 lambda result: self.configure_environment())
87 if not stat:
88 environment = yield watch_d
89 returnValue(environment)
90
91 config = EnvironmentsConfig()
92 config.parse(environment_data)
93 returnValue(config.get_default())
94
95 @inlineCallbacks
96 def _on_environment_changed(self, event):
97 """Reload the environment if its data changes."""
98
99 if event.type_name == "deleted":
100 return
101
102 self.environment = yield self.configure_environment()
103 self.provider = self.environment.get_machine_provider()
104
105 def periodic_machine_check(self):
106 """A periodic checking of machine states and provider machines.
107
108 In addition to the on demand changes to zookeeper states that are
109 monitored by L{watch_machine_changes}, the periodic machine check
110 performs non zookeeper state related verification by periodically
111 checking the last current provider machine states against the
112 last known zookeeper state.
113
114 Primarily this helps in recovering from transient error conditions
115 which may have prevent processing of an individual machine state, as
116 well as verifying the current state of the provider's running machines
117 against the zk state, thus pruning unused resources.
118 """
119 from twisted.internet import reactor
120 d = self.process_machines(self._current_machines)
121 d.addBoth(
122 lambda result: reactor.callLater(
123 self.machine_check_period, self.periodic_machine_check))
124 return d
125
126 @inlineCallbacks
127 def watch_machine_changes(self, old_machines, new_machines):
128 """Watches and processes machine state changes.
129
130 This function is used to subscribe to topology changes, and
131 specifically changes to machines within the topology. It performs
132 work against the machine provider to ensure that the currently
133 running state of the juju cluster corresponds to the topology
134 via creation and deletion of machines.
135
136 The subscription utilized is a permanent one, meaning that this
137 function will automatically be rescheduled to run whenever a topology
138 state change happens that involves machines.
139
140 This functional also caches the current set of machines as an agent
141 instance attribute.
142
143 @param old_machines machine ids as existed in the previous topology.
144 @param new_machines machine ids as exist in the current topology.
145 """
146 if not self._running:
147 raise StopWatcher()
148 log.debug("Machines changed old:%s new:%s", old_machines, new_machines)
149 self._current_machines = new_machines
150 try:
151 yield self.process_machines(self._current_machines)
152 except Exception:
153 # Log and effectively retry later in periodic_machine_check
154 log.exception(
155 "Got unexpected exception in processing machines,"
156 " will retry")
157
158 @concurrent_execution_guard("_processing_machines")
159 @inlineCallbacks
160 def process_machines(self, current_machines):
161 """Ensure the currently running machines correspond to state.
162
163 At the end of each process_machines execution, verify that all
164 running machines within the provider correspond to machine_ids within
165 the topology. If they don't then shut them down.
166
167 Utilizes concurrent execution guard, to ensure that this is only being
168 executed at most once per process.
169 """
170 # XXX this is obviously broken, but the margins of 80 columns prevent
171 # me from describing. hint think concurrent agents, and use a lock.
172
173 # map of instance_id -> machine
174 try:
175 provider_machines = yield self.provider.get_machines()
176 except ProviderError:
177 log.exception("Cannot get machine list")
178 return
179
180 provider_machines = dict(
181 [(m.instance_id, m) for m in provider_machines])
182
183 instance_ids = []
184 for machine_state_id in current_machines:
185 try:
186 instance_id = yield self.process_machine(
187 machine_state_id, provider_machines)
188 except (MachineStateNotFound, ProviderError):
189 log.exception("Cannot process machine %s", machine_state_id)
190 continue
191 instance_ids.append(instance_id)
192
193 # Terminate all unused juju machines running within the cluster.
194 unused = set(provider_machines.keys()) - set(instance_ids)
195 for instance_id in unused:
196 log.info("Shutting down machine id:%s ...", instance_id)
197 machine = provider_machines[instance_id]
198
199 try:
200 yield self.provider.shutdown_machine(machine)
201 except ProviderError:
202 log.exception("Cannot shutdown machine %s", instance_id)
203 continue
204
205 @inlineCallbacks
206 def process_machine(self, machine_state_id, provider_machine_map):
207 """Ensure a provider machine for a machine state id.
208
209 For each machine_id in new machines which represents the current state
210 of the topology:
211
212 * Check to ensure its state reflects that it has been
213 launched. If it hasn't then create the machine and update
214 the state.
215
216 * Watch the machine's assigned services so that changes can
217 be applied to the firewall for service exposing support.
218 """
219 # fetch the machine state
220 machine_state = yield self.machine_state_manager.get_machine_state(
221 machine_state_id)
222 instance_id = yield machine_state.get_instance_id()
223
224 # Verify a machine id has state and is running, else launch it.
225 if instance_id is None or not instance_id in provider_machine_map:
226 log.info("Starting machine id:%s ...", machine_state.id)
227 constraints = yield machine_state.get_constraints()
228 machines = yield self.provider.start_machine(
229 {"machine-id": machine_state.id, "constraints": constraints})
230 instance_id = machines[0].instance_id
231 yield machine_state.set_instance_id(instance_id)
232
233 # The firewall manager also needs to be checked for any
234 # outstanding retries on this machine
235 yield self.firewall_manager.process_machine(machine_state)
236 returnValue(instance_id)
237
238if __name__ == '__main__':
239 ProvisioningAgent().run()
2400
=== added directory 'juju/agents/tests'
=== removed directory 'juju/agents/tests'
=== added file 'juju/agents/tests/__init__.py'
--- juju/agents/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ juju/agents/tests/__init__.py 2013-04-22 11:00:38 +0000
@@ -0,0 +1,1 @@
1#
02
=== removed file 'juju/agents/tests/__init__.py'
--- juju/agents/tests/__init__.py 2012-06-11 15:40:48 +0000
+++ juju/agents/tests/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1#
20
=== added file 'juju/agents/tests/common.py'
--- juju/agents/tests/common.py 1970-01-01 00:00:00 +0000
+++ juju/agents/tests/common.py 2013-04-22 11:00:38 +0000
@@ -0,0 +1,52 @@
1import os
2
3from twisted.internet.defer import inlineCallbacks, succeed
4
5from txzookeeper.tests.utils import deleteTree
6
7from juju.agents.base import TwistedOptionNamespace
8from juju.state.tests.common import StateTestBase
9from juju.tests.common import get_test_zookeeper_address
10
11
12class AgentTestBase(StateTestBase):
13
14 agent_class = None
15 juju_directory = None
16 setup_environment = True
17
18 @inlineCallbacks
19 def setUp(self):
20 self.juju_directory = self.makeDir()
21 yield super(AgentTestBase, self).setUp()
22 assert self.agent_class, "Agent Class must be specified on test"
23 if self.setup_environment:
24 yield self.push_default_config()
25 self.agent = self.agent_class()
26 self.options = yield self.get_agent_config()
27 self.agent.configure(self.options)
28 self.agent.set_watch_enabled(False)
29
30 def tearDown(self):
31 if self.agent.client and self.agent.client.connected:
32 self.agent.client.close()
33
34 if self.client.connected:
35 deleteTree("/", self.client.handle)
36 self.client.close()
37
38 def get_agent_config(self):
39 options = TwistedOptionNamespace()
40 options["juju_directory"] = self.juju_directory
41 options["zookeeper_servers"] = get_test_zookeeper_address()
42 options["session_file"] = self.makeFile()
43 return succeed(options)
44
45 @inlineCallbacks
46 def debug_pprint_tree(self, path="/", indent=1):
47 children = yield self.client.get_children(path)
48 for n in children:
49 print " " * indent, "/" + n
50 yield self.debug_pprint_tree(
51 os.path.join(path, n),
52 indent + 1)
053
=== removed file 'juju/agents/tests/common.py'
--- juju/agents/tests/common.py 2012-06-11 15:40:48 +0000
+++ juju/agents/tests/common.py 1970-01-01 00:00:00 +0000
@@ -1,52 +0,0 @@
1import os
2
3from twisted.internet.defer import inlineCallbacks, succeed
4
5from txzookeeper.tests.utils import deleteTree
6
7from juju.agents.base import TwistedOptionNamespace
8from juju.state.tests.common import StateTestBase
9from juju.tests.common import get_test_zookeeper_address
10
11
12class AgentTestBase(StateTestBase):
13
14 agent_class = None
15 juju_directory = None
16 setup_environment = True
17
18 @inlineCallbacks
19 def setUp(self):
20 self.juju_directory = self.makeDir()
21 yield super(AgentTestBase, self).setUp()
22 assert self.agent_class, "Agent Class must be specified on test"
23 if self.setup_environment:
24 yield self.push_default_config()
25 self.agent = self.agent_class()
26 self.options = yield self.get_agent_config()
27 self.agent.configure(self.options)
28 self.agent.set_watch_enabled(False)
29
30 def tearDown(self):
31 if self.agent.client and self.agent.client.connected:
32 self.agent.client.close()
33
34 if self.client.connected:
35 deleteTree("/", self.client.handle)
36 self.client.close()
37
38 def get_agent_config(self):
39 options = TwistedOptionNamespace()
40 options["juju_directory"] = self.juju_directory
41 options["zookeeper_servers"] = get_test_zookeeper_address()
42 options["session_file"] = self.makeFile()
43 return succeed(options)
44
45 @inlineCallbacks
46 def debug_pprint_tree(self, path="/", indent=1):
47 children = yield self.client.get_children(path)
48 for n in children:
49 print " " * indent, "/" + n
50 yield self.debug_pprint_tree(
51 os.path.join(path, n),
52 indent + 1)
530
=== added file 'juju/agents/tests/test_base.py'
--- juju/agents/tests/test_base.py 1970-01-01 00:00:00 +0000
+++ juju/agents/tests/test_base.py 2013-04-22 11:00:38 +0000
@@ -0,0 +1,633 @@
1import argparse
2import json
3import logging
4import os
5import stat
6import sys
7import yaml
8
9from twisted.application.app import AppLogger
10from twisted.application.service import IService, IServiceCollection
11from twisted.internet.defer import (
12 fail, succeed, Deferred, inlineCallbacks, returnValue)
13from twisted.python.components import Componentized
14from twisted.python import log
15
16import zookeeper
17from txzookeeper import ZookeeperClient
18from juju.lib.testing import TestCase
19from juju.lib.mocker import MATCH
20from juju.tests.common import get_test_zookeeper_address
21
22from juju.agents.base import (
23 BaseAgent, TwistedOptionNamespace, AgentRunner, AgentLogger)
24from juju.agents.dummy import DummyAgent
25from juju.errors import NoConnection, JujuError
26from juju.lib.zklog import ZookeeperHandler
27
28from juju.agents.tests.common import AgentTestBase
29
30MATCH_APP = MATCH(lambda x: isinstance(x, Componentized))
31MATCH_HANDLER = MATCH(lambda x: isinstance(x, ZookeeperHandler))
32
33
34class BaseAgentTest(TestCase):
35
36 @inlineCallbacks
37 def setUp(self):
38 yield super(BaseAgentTest, self).setUp()
39 self.juju_home = self.makeDir()
40 self.change_environment(JUJU_HOME=self.juju_home)
41
42 def test_as_app(self):
43 """The agent class can be accessed as an application."""
44 app = BaseAgent().as_app()
45 multi_service = IService(app, None)
46 self.assertTrue(IServiceCollection.providedBy(multi_service))
47 services = list(multi_service)
48 self.assertEqual(len(services), 1)
49
50 def test_twistd_default_options(self):
51 """The agent cli parsing, populates standard twistd options."""
52 parser = argparse.ArgumentParser()
53 BaseAgent.setup_options(parser)
54
55 # Daemon group
56 self.assertEqual(
57 parser.get_default("logfile"), "%s.log" % BaseAgent.name)
58 self.assertEqual(parser.get_default("pidfile"), "")
59
60 self.assertEqual(parser.get_default("loglevel"), "DEBUG")
61 self.assertFalse(parser.get_default("nodaemon"))
62 self.assertEqual(parser.get_default("rundir"), ".")
63 self.assertEqual(parser.get_default("chroot"), None)
64 self.assertEqual(parser.get_default("umask"), '0022')
65 self.assertEqual(parser.get_default("uid"), None)
66 self.assertEqual(parser.get_default("gid"), None)
67 self.assertEqual(parser.get_default("euid"), None)
68 self.assertEqual(parser.get_default("prefix"), BaseAgent.name)
69 self.assertEqual(parser.get_default("syslog"), False)
70
71 # Development Group
72 self.assertFalse(parser.get_default("debug"))
73 self.assertFalse(parser.get_default("profile"))
74 self.assertFalse(parser.get_default("savestats"))
75 self.assertEqual(parser.get_default("profiler"), "cprofile")
76
77 # Hidden defaults
78 self.assertEqual(parser.get_default("reactor"), "epoll")
79 self.assertEqual(parser.get_default("originalname"), None)
80
81 # Agent options
82 self.assertEqual(parser.get_default("principals"), [])
83 self.assertEqual(parser.get_default("zookeeper_servers"), "")
84 self.assertEqual(parser.get_default("juju_directory"), self.juju_home)
85 self.assertEqual(parser.get_default("session_file"), None)
86
87 def test_twistd_flags_correspond(self):
88 parser = argparse.ArgumentParser()
89 BaseAgent.setup_options(parser)
90 args = [
91 "--profile",
92 "--savestats",
93 "--nodaemon"]
94
95 options = parser.parse_args(args, namespace=TwistedOptionNamespace())
96 self.assertEqual(options.get("savestats"), True)
97 self.assertEqual(options.get("nodaemon"), True)
98 self.assertEqual(options.get("profile"), True)
99
100 def test_agent_logger(self):
101 parser = argparse.ArgumentParser()
102 BaseAgent.setup_options(parser)
103 log_file_path = self.makeFile()
104
105 options = parser.parse_args(
106 ["--logfile", log_file_path, "--session-file", self.makeFile()],
107 namespace=TwistedOptionNamespace())
108
109 def match_observer(observer):
110 return isinstance(observer.im_self, log.PythonLoggingObserver)
111
112 def cleanup(observer):
113 # post test cleanup of global state.
114 log.removeObserver(observer)
115 logging.getLogger().handlers = []
116
117 original_log_with_observer = log.startLoggingWithObserver
118
119 def _start_log_with_observer(observer):
120 self.addCleanup(cleanup, observer)
121 # by default logging will replace stdout/stderr
122 return original_log_with_observer(observer, 0)
123
124 app = self.mocker.mock()
125 app.getComponent(log.ILogObserver, None)
126 self.mocker.result(None)
127
128 start_log_with_observer = self.mocker.replace(
129 log.startLoggingWithObserver)
130 start_log_with_observer(MATCH(match_observer))
131 self.mocker.call(_start_log_with_observer)
132 self.mocker.replay()
133
134 agent_logger = AgentLogger(options)
135 agent_logger.start(app)
136
137 # We suppress twisted messages below the error level.
138 output = open(log_file_path).read()
139 self.assertFalse(output)
140
141 # also verify we didn't mess with the app logging.
142 app_log = logging.getLogger()
143 app_log.info("Good")
144
145 # and that twisted errors still go through.
146 log.err("Something bad happened")
147 output = open(log_file_path).read()
148
149 self.assertIn("Good", output)
150 self.assertIn("Something bad happened", output)
151
152 def test_custom_log_level(self):
153 parser = argparse.ArgumentParser()
154 BaseAgent.setup_options(parser)
155 options = parser.parse_args(
156 ["--loglevel", "INFO"], namespace=TwistedOptionNamespace())
157 self.assertEqual(options.loglevel, "INFO")
158
159 def test_twistd_option_namespace(self):
160 """
161 The twisted option namespace bridges argparse attribute access,
162 to twisted dictionary access for cli options.
163 """
164 options = TwistedOptionNamespace()
165 options.x = 1
166 self.assertEqual(options['x'], 1)
167 self.assertEqual(options.get('x'), 1)
168 self.assertEqual(options.get('y'), None)
169 self.assertRaises(KeyError, options.__getitem__, 'y')
170 options['y'] = 2
171 self.assertEqual(options.y, 2)
172 self.assertTrue(options.has_key('y'))
173 self.assertFalse(options.has_key('z'))
174
175 def test_runner_attribute_application(self):
176 """The agent runner retrieve the application as an attribute."""
177 runner = AgentRunner({})
178 self.assertEqual(runner.createOrGetApplication(), None)
179 runner.application = 21
180 self.assertEqual(runner.createOrGetApplication(), 21)
181
182 def test_run(self):
183 """Invokes the run class method on an agent.
184
185 This will create an agent instance, parse the cli args, passes them to
186 the agent, and starts the agent runner.
187 """
188 self.change_args(
189 "es-agent", "--zookeeper-servers", get_test_zookeeper_address(),
190 "--session-file", self.makeFile())
191 runner = self.mocker.patch(AgentRunner)
192 runner.run()
193 mock_agent = self.mocker.patch(BaseAgent)
194
195 def match_args(config):
196 self.assertEqual(config["zookeeper_servers"],
197 get_test_zookeeper_address())
198 return True
199
200 mock_agent.configure(MATCH(match_args))
201 self.mocker.passthrough()
202
203 self.mocker.replay()
204 BaseAgent.run()
205
206 def test_full_run(self):
207 """Verify a functional agent start via the 'run' method.
208
209 This test requires Zookeeper running on the default port of localhost.
210 The mocked portions are to prevent the daemon start from altering the
211 test environment (sys.stdout/sys.stderr, and reactor start).
212 """
213 zookeeper.set_debug_level(0)
214 started = Deferred()
215
216 class DummyAgent(BaseAgent):
217 started = False
218
219 def start(self):
220 started.callback(self)
221
222 def validate_started(agent):
223 self.assertTrue(agent.client.connected)
224
225 started.addCallback(validate_started)
226
227 self.change_args(
228 "es-agent", "--nodaemon",
229 "--zookeeper-servers", get_test_zookeeper_address(),
230 "--session-file", self.makeFile())
231 runner = self.mocker.patch(AgentRunner)
232 logger = self.mocker.patch(AppLogger)
233 logger.start(MATCH_APP)
234 runner.startReactor(None, sys.stdout, sys.stderr)
235 logger.stop()
236 self.mocker.replay()
237 DummyAgent.run()
238 return started
239
240 @inlineCallbacks
241 def test_stop_service_stub_closes_agent(self):
242 """The base class agent, stopService will the stop method.
243
244 Additionally it will close the agent's zookeeper client if
245 the client is still connected.
246 """
247 mock_agent = self.mocker.patch(BaseAgent)
248 mock_client = self.mocker.mock(ZookeeperClient)
249 session_file = self.makeFile()
250
251 # connection is closed after agent.stop invoked.
252 with self.mocker.order():
253 mock_agent.stop()
254 self.mocker.passthrough()
255
256 # client existence check
257 mock_agent.client
258 self.mocker.result(mock_client)
259
260 # client connected check
261 mock_agent.client
262 self.mocker.result(mock_client)
263 mock_client.connected
264 self.mocker.result(True)
265
266 # client close
267 mock_agent.client
268 self.mocker.result(mock_client)
269 mock_client.close()
270
271 # delete session file
272 mock_agent.config
273 self.mocker.result({"session_file": session_file})
274
275 self.mocker.replay()
276
277 agent = BaseAgent()
278 yield agent.stopService()
279 self.assertFalse(os.path.exists(session_file))
280
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches