aboutsummaryrefslogtreecommitdiffstats
path: root/charms/trusty/kafka
diff options
context:
space:
mode:
Diffstat (limited to 'charms/trusty/kafka')
-rw-r--r--charms/trusty/kafka/.bzr/README3
-rw-r--r--charms/trusty/kafka/.bzr/branch-format1
-rw-r--r--charms/trusty/kafka/.bzr/branch/format1
-rw-r--r--charms/trusty/kafka/.bzr/branch/location1
-rw-r--r--charms/trusty/kafka/.bzr/checkout/conflicts1
-rw-r--r--charms/trusty/kafka/.bzr/checkout/dirstatebin0 -> 10605 bytes
-rw-r--r--charms/trusty/kafka/.bzr/checkout/format1
-rw-r--r--charms/trusty/kafka/.bzr/checkout/views0
-rw-r--r--charms/trusty/kafka/LICENSE177
-rw-r--r--charms/trusty/kafka/README.md84
-rw-r--r--charms/trusty/kafka/actions.yaml48
-rwxr-xr-xcharms/trusty/kafka/actions/create-topic40
-rwxr-xr-xcharms/trusty/kafka/actions/delete-topic36
-rwxr-xr-xcharms/trusty/kafka/actions/list-topics31
-rwxr-xr-xcharms/trusty/kafka/actions/list-zks28
-rwxr-xr-xcharms/trusty/kafka/actions/read-topic35
-rwxr-xr-xcharms/trusty/kafka/actions/write-topic36
-rw-r--r--charms/trusty/kafka/config.yaml7
-rw-r--r--charms/trusty/kafka/copyright16
-rw-r--r--charms/trusty/kafka/dist.yaml30
-rw-r--r--charms/trusty/kafka/hooks/callbacks.py181
-rwxr-xr-xcharms/trusty/kafka/hooks/common.py90
-rwxr-xr-xcharms/trusty/kafka/hooks/config-changed15
-rwxr-xr-xcharms/trusty/kafka/hooks/install17
-rwxr-xr-xcharms/trusty/kafka/hooks/kafka-relation-changed15
-rw-r--r--charms/trusty/kafka/hooks/setup.py33
-rwxr-xr-xcharms/trusty/kafka/hooks/start15
-rwxr-xr-xcharms/trusty/kafka/hooks/stop15
-rwxr-xr-xcharms/trusty/kafka/hooks/zookeeper-relation-changed15
-rwxr-xr-xcharms/trusty/kafka/hooks/zookeeper-relation-departed15
-rw-r--r--charms/trusty/kafka/icon.svg90
-rw-r--r--charms/trusty/kafka/metadata.yaml30
-rw-r--r--charms/trusty/kafka/resources.yaml12
-rw-r--r--charms/trusty/kafka/resources/python/PyYAML-3.11.tar.gzbin0 -> 248685 bytes
-rw-r--r--charms/trusty/kafka/resources/python/charmhelpers-0.3.1.tar.gzbin0 -> 62031 bytes
-rw-r--r--charms/trusty/kafka/resources/python/jujuresources-0.2.11.tar.gzbin0 -> 12679 bytes
-rw-r--r--charms/trusty/kafka/resources/python/pyaml-15.5.7.tar.gzbin0 -> 14374 bytes
-rw-r--r--charms/trusty/kafka/resources/python/six-1.9.0-py2.py3-none-any.whlbin0 -> 10222 bytes
-rw-r--r--charms/trusty/kafka/templates/upstart.conf14
-rwxr-xr-xcharms/trusty/kafka/tests/00-setup5
-rwxr-xr-xcharms/trusty/kafka/tests/100-deploy-kafka29
-rwxr-xr-xcharms/trusty/kafka/tests/remote/test_dist_config.py71
-rw-r--r--charms/trusty/kafka/tests/tests.yaml10
43 files changed, 1248 insertions, 0 deletions
diff --git a/charms/trusty/kafka/.bzr/README b/charms/trusty/kafka/.bzr/README
new file mode 100644
index 0000000..f82dc1c
--- /dev/null
+++ b/charms/trusty/kafka/.bzr/README
@@ -0,0 +1,3 @@
+This is a Bazaar control directory.
+Do not change any files in this directory.
+See http://bazaar.canonical.com/ for more information about Bazaar.
diff --git a/charms/trusty/kafka/.bzr/branch-format b/charms/trusty/kafka/.bzr/branch-format
new file mode 100644
index 0000000..9eb09b7
--- /dev/null
+++ b/charms/trusty/kafka/.bzr/branch-format
@@ -0,0 +1 @@
+Bazaar-NG meta directory, format 1
diff --git a/charms/trusty/kafka/.bzr/branch/format b/charms/trusty/kafka/.bzr/branch/format
new file mode 100644
index 0000000..b391ffd
--- /dev/null
+++ b/charms/trusty/kafka/.bzr/branch/format
@@ -0,0 +1 @@
+Bazaar-NG Branch Reference Format 1
diff --git a/charms/trusty/kafka/.bzr/branch/location b/charms/trusty/kafka/.bzr/branch/location
new file mode 100644
index 0000000..2f40eef
--- /dev/null
+++ b/charms/trusty/kafka/.bzr/branch/location
@@ -0,0 +1 @@
+http://bazaar.launchpad.net/~sdn-charmers/charms/trusty/apache-kafka/trunk/ \ No newline at end of file
diff --git a/charms/trusty/kafka/.bzr/checkout/conflicts b/charms/trusty/kafka/.bzr/checkout/conflicts
new file mode 100644
index 0000000..0dc2d3a
--- /dev/null
+++ b/charms/trusty/kafka/.bzr/checkout/conflicts
@@ -0,0 +1 @@
+BZR conflict list format 1
diff --git a/charms/trusty/kafka/.bzr/checkout/dirstate b/charms/trusty/kafka/.bzr/checkout/dirstate
new file mode 100644
index 0000000..9d2ef8a
--- /dev/null
+++ b/charms/trusty/kafka/.bzr/checkout/dirstate
Binary files differ
diff --git a/charms/trusty/kafka/.bzr/checkout/format b/charms/trusty/kafka/.bzr/checkout/format
new file mode 100644
index 0000000..e0261c7
--- /dev/null
+++ b/charms/trusty/kafka/.bzr/checkout/format
@@ -0,0 +1 @@
+Bazaar Working Tree Format 6 (bzr 1.14)
diff --git a/charms/trusty/kafka/.bzr/checkout/views b/charms/trusty/kafka/.bzr/checkout/views
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/charms/trusty/kafka/.bzr/checkout/views
diff --git a/charms/trusty/kafka/LICENSE b/charms/trusty/kafka/LICENSE
new file mode 100644
index 0000000..f433b1a
--- /dev/null
+++ b/charms/trusty/kafka/LICENSE
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/charms/trusty/kafka/README.md b/charms/trusty/kafka/README.md
new file mode 100644
index 0000000..31e8e4f
--- /dev/null
+++ b/charms/trusty/kafka/README.md
@@ -0,0 +1,84 @@
+## Overview
+Apache Kafka is an open-source message broker project developed by the Apache
+Software Foundation written in Scala. The project aims to provide a unified,
+high-throughput, low-latency platform for handling real-time data feeds. Learn
+more at [kafka.apache.org](http://kafka.apache.org/).
+
+
+## Usage
+Kafka requires the Zookeeper distributed coordination service. Deploy and
+relate them as follows:
+
+ juju deploy apache-zookeeper zookeeper
+ juju deploy apache-kafka kafka
+ juju add-relation kafka zookeeper
+
+Once deployed, we can list the zookeeper servers that our kafka brokers
+are connected to. The following will list `<ip>:<port>` information for each
+zookeeper unit in the environment (e.g.: `10.0.3.221:2181`).
+
+ juju action do kafka/0 list-zks
+ juju action fetch <id> # <-- id from above command
+
+We can create a Kafka topic with:
+
+ juju action do kafka/0 create-topic topic=<topic_name> \
+ partitions=<#> replication=<#>
+ juju action fetch <id> # <-- id from above command
+
+We can list topics with:
+
+ juju action do kafka/0 list-topics
+ juju action fetch <id> # <-- id from above command
+
+We can write to a topic with:
+
+ juju action do kafka/0 write-topic topic=<topic_name> data=<data>
+ juju action fetch <id> # <-- id from above command
+
+We can read from a topic with:
+
+ juju action do kafka/0 read-topic topic=<topic_name> partition=<#>
+ juju action fetch <id> # <-- id from above command
+
+And finally, we can delete a topic with:
+
+ juju action do kafka/0 delete-topic topic=<topic_name>
+ juju action fetch <id> # <-- id from above command
+
+## Deploying in Network-Restricted Environments
+This charm can be deployed in environments with limited network access. To
+deploy in this environment, you will need a local mirror to serve the packages
+and resources required by this charm.
+
+### Mirroring Packages
+You can setup a local mirror for apt packages using squid-deb-proxy.
+For instructions on configuring juju to use this, see the
+[Juju Proxy Documentation](https://juju.ubuntu.com/docs/howto-proxies.html).
+
+### Mirroring Resources
+In addition to apt packages, this charm requires a few binary resources
+which are normally hosted on Launchpad. If access to Launchpad is not
+available, the `jujuresources` library makes it easy to create a mirror
+of these resources:
+
+ sudo pip install jujuresources
+ juju-resources fetch --all /path/to/resources.yaml -d /tmp/resources
+ juju-resources serve -d /tmp/resources
+
+This will fetch all of the resources needed by this charm and serve them via a
+simple HTTP server. The output from `juju-resources serve` will give you a
+URL that you can set as the `resources_mirror` config option for this charm.
+Setting this option will cause all resources required by this charm to be
+downloaded from the configured URL.
+
+
+## Contact Information
+- <bigdata-dev@lists.launchpad.net>
+
+
+## Help
+- [Apache Kafka home page](http://kafka.apache.org/)
+- [Apache Kafka issue tracker](https://issues.apache.org/jira/browse/KAFKA)
+- [Juju mailing list](https://lists.ubuntu.com/mailman/listinfo/juju)
+- [Juju community](https://jujucharms.com/community)
diff --git a/charms/trusty/kafka/actions.yaml b/charms/trusty/kafka/actions.yaml
new file mode 100644
index 0000000..6026d0b
--- /dev/null
+++ b/charms/trusty/kafka/actions.yaml
@@ -0,0 +1,48 @@
+create-topic:
+ description: Create a new Kafka topic
+ params:
+ topic:
+ type: string
+ description: Topic name
+ partitions:
+ type: integer
+ description: Number of partitions for the topic being created
+ replication:
+ type: integer
+ description: Replication factor for each partition in the topic
+ required: [topic, partitions, replication]
+ additionalProperties: false
+delete-topic:
+ description: Delete a Kafka topic
+ params:
+ topic:
+ type: string
+ description: Topic name
+ required: [topic]
+ additionalProperties: false
+list-topics:
+ description: List all Kafka topics
+list-zks:
+ description: List ip:port info for connected Zookeeper servers
+read-topic:
+ description: Consume an existing kafka topic
+ params:
+ topic:
+ type: string
+ description: Topic name
+ partition:
+ type: integer
+ description: Partition to consume
+ required: [topic, partition]
+ additionalProperties: false
+write-topic:
+ description: Write to a kafka topic
+ params:
+ topic:
+ type: string
+ description: Topic name
+ data:
+ type: string
+ description: Data to write to topic
+ required: [topic, data]
+ additionalProperties: false
diff --git a/charms/trusty/kafka/actions/create-topic b/charms/trusty/kafka/actions/create-topic
new file mode 100755
index 0000000..4910430
--- /dev/null
+++ b/charms/trusty/kafka/actions/create-topic
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+import sys
+
+try:
+ from charmhelpers.core import hookenv
+ from charmhelpers.core import unitdata
+ from jujubigdata import utils
+ from jujubigdata.relations import Zookeeper
+ charm_ready = unitdata.kv().get('charm.active', False)
+except ImportError:
+ charm_ready = False
+
+if not charm_ready:
+ # might not have hookenv.action_fail available yet
+ from subprocess import call
+ call(['action-fail', 'Kafka service not yet ready'])
+
+# Grab the business
+topic_name = hookenv.action_get('topic')
+topic_partitions = hookenv.action_get('partitions')
+topic_replication = hookenv.action_get('replication')
+
+# Create the topic if we've got zookeepers; otherwise fail.
+if Zookeeper().connected_units() and Zookeeper().is_ready():
+ zks = []
+ for unit, data in Zookeeper().filtered_data().items():
+ ip = utils.resolve_private_address(data['private-address'])
+ zks.append("%s:%s" % (ip, data['port']))
+ zks.sort()
+ zookeepers = ",".join(zks)
+ output = utils.run_as('kafka', 'kafka-topics.sh',
+ '--zookeeper', zookeepers, '--create',
+ '--topic', topic_name,
+ '--partitions', topic_partitions,
+ '--replication-factor', topic_replication,
+ capture_output=True)
+ hookenv.action_set({'output': output})
+else:
+ hookenv.action_fail('Zookeeper relation is not present/ready')
+ sys.exit()
diff --git a/charms/trusty/kafka/actions/delete-topic b/charms/trusty/kafka/actions/delete-topic
new file mode 100755
index 0000000..b56f004
--- /dev/null
+++ b/charms/trusty/kafka/actions/delete-topic
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+import sys
+
+try:
+ from charmhelpers.core import hookenv
+ from charmhelpers.core import unitdata
+ from jujubigdata import utils
+ from jujubigdata.relations import Zookeeper
+ charm_ready = unitdata.kv().get('charm.active', False)
+except ImportError:
+ charm_ready = False
+
+if not charm_ready:
+ # might not have hookenv.action_fail available yet
+ from subprocess import call
+ call(['action-fail', 'Kafka service not yet ready'])
+
+# Grab the business
+topic_name = hookenv.action_get('topic')
+
+# Delete the topic if we've got zookeepers; otherwise fail.
+if Zookeeper().connected_units() and Zookeeper().is_ready():
+ zks = []
+ for unit, data in Zookeeper().filtered_data().items():
+ ip = utils.resolve_private_address(data['private-address'])
+ zks.append("%s:%s" % (ip, data['port']))
+ zks.sort()
+ zookeepers = ",".join(zks)
+ output = utils.run_as('kafka', 'kafka-topics.sh',
+ '--zookeeper', zookeepers, '--delete',
+ '--topic', topic_name,
+ capture_output=True)
+ hookenv.action_set({'output': output})
+else:
+ hookenv.action_fail('Zookeeper relation is not present/ready')
+ sys.exit()
diff --git a/charms/trusty/kafka/actions/list-topics b/charms/trusty/kafka/actions/list-topics
new file mode 100755
index 0000000..629d2b4
--- /dev/null
+++ b/charms/trusty/kafka/actions/list-topics
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+import sys
+
+try:
+ from charmhelpers.core import hookenv
+ from charmhelpers.core import unitdata
+ from jujubigdata import utils
+ from jujubigdata.relations import Zookeeper
+ charm_ready = unitdata.kv().get('charm.active', False)
+except ImportError:
+ charm_ready = False
+
+if not charm_ready:
+ # might not have hookenv.action_fail available yet
+ from subprocess import call
+ call(['action-fail', 'Kafka service not yet ready'])
+
+if Zookeeper().connected_units() and Zookeeper().is_ready():
+ zks = []
+ for unit, data in Zookeeper().filtered_data().items():
+ ip = utils.resolve_private_address(data['private-address'])
+ zks.append("%s:%s" % (ip, data['port']))
+ zks.sort()
+ zookeepers = ",".join(zks)
+ output = utils.run_as('kafka', '/usr/lib/kafka/bin/kafka-topics.sh',
+ '--zookeeper', zookeepers, '--list',
+ capture_output=True)
+ hookenv.action_set({'topics': output})
+else:
+ hookenv.action_fail('Zookeeper relation is not present/ready')
+ sys.exit()
diff --git a/charms/trusty/kafka/actions/list-zks b/charms/trusty/kafka/actions/list-zks
new file mode 100755
index 0000000..9de9e9a
--- /dev/null
+++ b/charms/trusty/kafka/actions/list-zks
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+import sys
+
+try:
+ from charmhelpers.core import hookenv
+ from charmhelpers.core import unitdata
+ from jujubigdata import utils
+ from jujubigdata.relations import Zookeeper
+ charm_ready = unitdata.kv().get('charm.active', False)
+except ImportError:
+ charm_ready = False
+
+if not charm_ready:
+ # might not have hookenv.action_fail available yet
+ from subprocess import call
+ call(['action-fail', 'Kafka service not yet ready'])
+
+if Zookeeper().connected_units() and Zookeeper().is_ready():
+ zks = []
+ for unit, data in Zookeeper().filtered_data().items():
+ ip = utils.resolve_private_address(data['private-address'])
+ zks.append("%s:%s" % (ip, data['port']))
+ zks.sort()
+ zookeepers = ",".join(zks)
+ hookenv.action_set({'zookeepers': zookeepers})
+else:
+ hookenv.action_fail('Zookeeper relation is not present/ready')
+ sys.exit()
diff --git a/charms/trusty/kafka/actions/read-topic b/charms/trusty/kafka/actions/read-topic
new file mode 100755
index 0000000..9f59396
--- /dev/null
+++ b/charms/trusty/kafka/actions/read-topic
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+#pylint: disable=C0103
+try:
+ from charmhelpers.core import hookenv
+ from charmhelpers.core import unitdata
+ import jujubigdata
+ from jujubigdata import utils
+ charm_ready = unitdata.kv().get('charm.active', False)
+except ImportError:
+ charm_ready = False
+
+if not charm_ready:
+ # might not have hookenv.action_fail available yet
+ from subprocess import call
+ call(['action-fail', 'Kafka service not yet ready'])
+
+kafka_reqs = ['vendor', 'packages', 'groups', 'users', 'dirs', 'ports']
+dist_config = jujubigdata.utils.DistConfig(filename='dist.yaml',
+ required_keys=kafka_reqs)
+
+# Grab the business
+topic_name = hookenv.action_get('topic')
+topic_partition = hookenv.action_get('partition')
+
+output = utils.run_as(
+ 'kafka', 'kafka-simple-consumer-shell.sh',
+ '--broker-list', '{}:{}'.format(
+ hookenv.unit_private_ip(),
+ dist_config.port('kafka'),
+ ),
+ '--topic', topic_name,
+ '--partition', topic_partition,
+ '--no-wait-at-logend',
+ capture_output=True)
+hookenv.action_set({'output': output})
diff --git a/charms/trusty/kafka/actions/write-topic b/charms/trusty/kafka/actions/write-topic
new file mode 100755
index 0000000..fce8e1b
--- /dev/null
+++ b/charms/trusty/kafka/actions/write-topic
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+#pylint: disable=C0103
+
+try:
+ from charmhelpers.core import hookenv
+ from charmhelpers.core import unitdata
+ import jujubigdata
+ from jujubigdata import utils
+ charm_ready = unitdata.kv().get('charm.active', False)
+except ImportError:
+ charm_ready = False
+
+if not charm_ready:
+ # might not have hookenv.action_fail available yet
+ from subprocess import call
+ call(['action-fail', 'Kafka service not yet ready'])
+
+
+kafka_reqs = ['vendor', 'packages', 'groups', 'users', 'dirs', 'ports']
+dist_config = jujubigdata.utils.DistConfig(filename='dist.yaml',
+ required_keys=kafka_reqs)
+
+# Grab the business
+topic_name = hookenv.action_get('topic')
+data = hookenv.action_get('data')
+
+output = utils.run_as(
+ 'kafka', 'kafka-console-producer.sh',
+ '--broker-list', '{}:{}'.format(
+ hookenv.unit_private_ip(),
+ dist_config.port('kafka'),
+ ),
+ '--topic', topic_name,
+ capture_output=True,
+ input=data)
+hookenv.action_set({'output': output})
diff --git a/charms/trusty/kafka/config.yaml b/charms/trusty/kafka/config.yaml
new file mode 100644
index 0000000..f2483b0
--- /dev/null
+++ b/charms/trusty/kafka/config.yaml
@@ -0,0 +1,7 @@
+options:
+ resources_mirror:
+ type: string
+ default: ''
+ description: |
+ URL from which to fetch resources (e.g., Hadoop binaries) instead of Launchpad.
+
diff --git a/charms/trusty/kafka/copyright b/charms/trusty/kafka/copyright
new file mode 100644
index 0000000..e900b97
--- /dev/null
+++ b/charms/trusty/kafka/copyright
@@ -0,0 +1,16 @@
+Format: http://dep.debian.net/deps/dep5/
+
+Files: *
+Copyright: Copyright 2015, Canonical Ltd., All Rights Reserved.
+License: Apache License 2.0
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ .
+ http://www.apache.org/licenses/LICENSE-2.0
+ .
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/charms/trusty/kafka/dist.yaml b/charms/trusty/kafka/dist.yaml
new file mode 100644
index 0000000..e8f1a37
--- /dev/null
+++ b/charms/trusty/kafka/dist.yaml
@@ -0,0 +1,30 @@
+# This file contains values that are likely to change per distribution.
+# The aim is to make it easier to update / extend the charms with
+# minimal changes to the shared code in the jujubigdata library.
+vendor: 'apache'
+packages:
+ - 'openjdk-7-jdk'
+groups:
+ - 'hadoop'
+users:
+ kafka:
+ groups: ['hadoop']
+dirs:
+ kafka:
+ path: '/usr/lib/kafka'
+ kafka_conf:
+ path: '/etc/kafka/conf'
+ kafka_app_logs:
+ path: '/var/log/kafka'
+ owner: 'kafka'
+ kafka_data_logs:
+ path: '/var/lib/kafka'
+ owner: 'kafka'
+ports:
+ # Ports that need to be exposed, overridden, or manually specified.
+ # Only expose ports serving a UI or external API (i.e., namenode and
+ # resourcemanager). Communication among units within the cluster does
+ # not need ports to be explicitly opened.
+ kafka:
+ port: 9092
+ exposed_on: 'kafka'
diff --git a/charms/trusty/kafka/hooks/callbacks.py b/charms/trusty/kafka/hooks/callbacks.py
new file mode 100644
index 0000000..bac68cb
--- /dev/null
+++ b/charms/trusty/kafka/hooks/callbacks.py
@@ -0,0 +1,181 @@
+import os
+from socket import gaierror, gethostbyname, gethostname
+from subprocess import Popen, check_call
+
+import jujuresources
+from charmhelpers.core import hookenv, templating
+from charmhelpers.core import host
+from charmhelpers.core import unitdata
+from jujubigdata import utils
+from jujubigdata.relations import Zookeeper
+
+
+# Extended status support
+# We call update_blocked_status from the "requires" section of our service
+# block, so be sure to return True. Otherwise, we'll block the "requires"
+# and never move on to callbacks. The other status update methods are called
+# from the "callbacks" section and therefore don't need to return True.
+def update_blocked_status():
+ if unitdata.kv().get('charm.active', False):
+ return True
+ if not Zookeeper().connected_units():
+ hookenv.status_set('blocked', 'Waiting for relation to apache-zookeeper')
+ elif not Zookeeper().is_ready():
+ hookenv.status_set('waiting', 'Waiting for Zookeeper to become ready')
+ return True
+
+
+def update_working_status():
+ if unitdata.kv().get('charm.active', False):
+ hookenv.status_set('maintenance', 'Updating configuration')
+ return
+ hookenv.status_set('maintenance', 'Setting up Apache Kafka')
+
+
+def update_active_status():
+ unitdata.kv().set('charm.active', True)
+ hookenv.status_set('active', 'Ready')
+
+
+def clear_active_flag():
+ unitdata.kv().set('charm.active', False)
+
+
+# Main Kafka class for callbacks
+class Kafka(object):
+ def __init__(self, dist_config):
+ self.dist_config = dist_config
+ self.resources = {
+ 'kafka': 'kafka-%s' % host.cpu_arch(),
+ }
+ self.verify_resources = utils.verify_resources(*self.resources.values())
+
+ def fix_hostname(self):
+ # ensure hostname is resolvable
+ hostname = gethostname()
+ try:
+ gethostbyname(hostname)
+ except gaierror:
+ check_call(['sed', '-E', '-i', '-e',
+ '/127.0.0.1[[:blank:]]+/a \\\n127.0.1.1 ' + hostname,
+ '/etc/hosts'])
+
+ def is_installed(self):
+ return unitdata.kv().get('kafka.installed')
+
+ def install(self, force=False):
+ if not force and self.is_installed():
+ return
+ self.fix_hostname()
+ self.dist_config.add_users()
+ self.dist_config.add_dirs()
+ self.dist_config.add_packages()
+ jujuresources.install(self.resources['kafka'],
+ destination=self.dist_config.path('kafka'),
+ skip_top_level=True)
+ self.setup_kafka_config()
+ unitdata.kv().set('kafka.installed', True)
+
+ def setup_kafka_config(self):
+ '''
+ copy the default configuration files to kafka_conf property
+ defined in dist.yaml
+ '''
+ default_conf = self.dist_config.path('kafka') / 'config'
+ kafka_conf = self.dist_config.path('kafka_conf')
+ kafka_conf.rmtree_p()
+ default_conf.copytree(kafka_conf)
+ # Now remove the conf included in the tarball and symlink our real conf
+ # dir. we've seen issues where kafka still looks for config in
+ # KAFKA_HOME/config.
+ default_conf.rmtree_p()
+ kafka_conf.symlink(default_conf)
+
+ # Configure immutable bits
+ kafka_bin = self.dist_config.path('kafka') / 'bin'
+ with utils.environment_edit_in_place('/etc/environment') as env:
+ if kafka_bin not in env['PATH']:
+ env['PATH'] = ':'.join([env['PATH'], kafka_bin])
+ env['LOG_DIR'] = self.dist_config.path('kafka_app_logs')
+
+ # note: we set the advertised.host.name below to the public_address
+ # to ensure that external (non-Juju) clients can connect to Kafka
+ public_address = hookenv.unit_get('public-address')
+ private_ip = utils.resolve_private_address(hookenv.unit_get('private-address'))
+ kafka_server_conf = self.dist_config.path('kafka_conf') / 'server.properties'
+ service, unit_num = os.environ['JUJU_UNIT_NAME'].split('/', 1)
+ utils.re_edit_in_place(kafka_server_conf, {
+ r'^broker.id=.*': 'broker.id=%s' % unit_num,
+ r'^port=.*': 'port=%s' % self.dist_config.port('kafka'),
+ r'^log.dirs=.*': 'log.dirs=%s' % self.dist_config.path('kafka_data_logs'),
+ r'^#?advertised.host.name=.*': 'advertised.host.name=%s' % public_address,
+ })
+
+ kafka_log4j = self.dist_config.path('kafka_conf') / 'log4j.properties'
+ utils.re_edit_in_place(kafka_log4j, {
+ r'^kafka.logs.dir=.*': 'kafka.logs.dir=%s' % self.dist_config.path('kafka_app_logs'),
+ })
+
+ # fix for lxc containers and some corner cases in manual provider
+ # ensure that public_address is resolvable internally by mapping it to the private IP
+ utils.update_etc_hosts({private_ip: public_address})
+
+ templating.render(
+ 'upstart.conf',
+ '/etc/init/kafka.conf',
+ context={
+ 'kafka_conf': self.dist_config.path('kafka_conf'),
+ 'kafka_bin': '{}/bin'.format(self.dist_config.path('kafka'))
+ },
+ )
+
+ def configure_kafka(self):
+ # Get ip:port data from our connected zookeepers
+ if Zookeeper().connected_units() and Zookeeper().is_ready():
+ zks = []
+ for unit, data in Zookeeper().filtered_data().items():
+ ip = utils.resolve_private_address(data['private-address'])
+ zks.append("%s:%s" % (ip, data['port']))
+ zks.sort()
+ zk_connect = ",".join(zks)
+
+ # update consumer props
+ cfg = self.dist_config.path('kafka_conf') / 'consumer.properties'
+ utils.re_edit_in_place(cfg, {
+ r'^zookeeper.connect=.*': 'zookeeper.connect=%s' % zk_connect,
+ })
+
+ # update server props
+ cfg = self.dist_config.path('kafka_conf') / 'server.properties'
+ utils.re_edit_in_place(cfg, {
+ r'^zookeeper.connect=.*': 'zookeeper.connect=%s' % zk_connect,
+ })
+ else:
+ # if we have no zookeepers, make sure kafka is stopped
+ self.stop()
+
+ def run_bg(self, user, command, *args):
+ """
+ Run a Kafka command as the `kafka` user in the background.
+
+ :param str command: Command to run
+ :param list args: Additional args to pass to the command
+ """
+ parts = [command] + list(args)
+ quoted = ' '.join("'%s'" % p for p in parts)
+ e = utils.read_etc_env()
+ Popen(['su', user, '-c', quoted], env=e)
+
+ def restart(self):
+ self.stop()
+ self.start()
+
+ def start(self):
+ host.service_start('kafka')
+
+ def stop(self):
+ host.service_stop('kafka')
+
+ def cleanup(self):
+ self.dist_config.remove_users()
+ self.dist_config.remove_dirs()
diff --git a/charms/trusty/kafka/hooks/common.py b/charms/trusty/kafka/hooks/common.py
new file mode 100755
index 0000000..1ca080b
--- /dev/null
+++ b/charms/trusty/kafka/hooks/common.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Common implementation for all hooks.
+"""
+
+import jujuresources
+from charmhelpers.core import hookenv
+from charmhelpers.core import unitdata
+from charmhelpers.core import charmframework
+
+
+def bootstrap_resources():
+ """
+ Install required resources defined in resources.yaml
+ """
+ if unitdata.kv().get('charm.bootstrapped', False):
+ return True
+ hookenv.status_set('maintenance', 'Installing base resources')
+ mirror_url = jujuresources.config_get('resources_mirror')
+ if not jujuresources.fetch(mirror_url=mirror_url):
+ missing = jujuresources.invalid()
+ hookenv.status_set('blocked', 'Unable to fetch required resource%s: %s' % (
+ 's' if len(missing) > 1 else '',
+ ', '.join(missing),
+ ))
+ return False
+ jujuresources.install(['pathlib', 'jujubigdata'])
+ unitdata.kv().set('charm.bootstrapped', True)
+ return True
+
+
+def manage():
+ if not bootstrap_resources():
+ # defer until resources are available, since charmhelpers, and thus
+ # the framework, are required (will require manual intervention)
+ return
+
+ import jujubigdata
+ import callbacks
+
+ kafka_reqs = ['vendor', 'packages', 'groups', 'users', 'dirs', 'ports']
+ dist_config = jujubigdata.utils.DistConfig(filename='dist.yaml',
+ required_keys=kafka_reqs)
+ kafka = callbacks.Kafka(dist_config)
+ manager = charmframework.Manager([
+ {
+ 'name': 'kafka',
+ 'provides': [
+ jujubigdata.relations.Kafka(port=dist_config.port('kafka'))
+ ],
+ 'requires': [
+ kafka.verify_resources,
+ jujubigdata.relations.Zookeeper(),
+ callbacks.update_blocked_status, # not really a requirement, but best way to fit into framework
+ ],
+ 'callbacks': [
+ callbacks.update_working_status,
+ kafka.install,
+ kafka.configure_kafka,
+ kafka.restart,
+ charmframework.helpers.open_ports(
+ dist_config.exposed_ports('kafka')),
+ callbacks.update_active_status,
+ ],
+ 'cleanup': [
+ callbacks.clear_active_flag,
+ charmframework.helpers.close_ports(
+ dist_config.exposed_ports('kafka')),
+ kafka.stop,
+ kafka.cleanup,
+ callbacks.update_blocked_status,
+ ],
+ },
+ ])
+ manager.manage()
+
+
+if __name__ == '__main__':
+ manage()
diff --git a/charms/trusty/kafka/hooks/config-changed b/charms/trusty/kafka/hooks/config-changed
new file mode 100755
index 0000000..1b4e92c
--- /dev/null
+++ b/charms/trusty/kafka/hooks/config-changed
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import common
+common.manage()
diff --git a/charms/trusty/kafka/hooks/install b/charms/trusty/kafka/hooks/install
new file mode 100755
index 0000000..7ea6d0f
--- /dev/null
+++ b/charms/trusty/kafka/hooks/install
@@ -0,0 +1,17 @@
+#!/usr/bin/python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import setup
+setup.pre_install()
+
+import common
+common.manage()
diff --git a/charms/trusty/kafka/hooks/kafka-relation-changed b/charms/trusty/kafka/hooks/kafka-relation-changed
new file mode 100755
index 0000000..1b4e92c
--- /dev/null
+++ b/charms/trusty/kafka/hooks/kafka-relation-changed
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import common
+common.manage()
diff --git a/charms/trusty/kafka/hooks/setup.py b/charms/trusty/kafka/hooks/setup.py
new file mode 100644
index 0000000..496edc6
--- /dev/null
+++ b/charms/trusty/kafka/hooks/setup.py
@@ -0,0 +1,33 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import subprocess
+from glob import glob
+
+
+def pre_install():
+ """
+ Do any setup required before the install hook.
+ """
+ install_pip()
+ install_bundled_resources()
+
+
+def install_pip():
+ subprocess.check_call(['apt-get', 'install', '-yq', 'python-pip', 'bzr'])
+
+
+def install_bundled_resources():
+ """
+ Install the bundled resources libraries.
+ """
+ archives = glob('resources/python/*')
+ subprocess.check_call(['pip', 'install'] + archives)
diff --git a/charms/trusty/kafka/hooks/start b/charms/trusty/kafka/hooks/start
new file mode 100755
index 0000000..1b4e92c
--- /dev/null
+++ b/charms/trusty/kafka/hooks/start
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import common
+common.manage()
diff --git a/charms/trusty/kafka/hooks/stop b/charms/trusty/kafka/hooks/stop
new file mode 100755
index 0000000..1b4e92c
--- /dev/null
+++ b/charms/trusty/kafka/hooks/stop
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import common
+common.manage()
diff --git a/charms/trusty/kafka/hooks/zookeeper-relation-changed b/charms/trusty/kafka/hooks/zookeeper-relation-changed
new file mode 100755
index 0000000..1b4e92c
--- /dev/null
+++ b/charms/trusty/kafka/hooks/zookeeper-relation-changed
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import common
+common.manage()
diff --git a/charms/trusty/kafka/hooks/zookeeper-relation-departed b/charms/trusty/kafka/hooks/zookeeper-relation-departed
new file mode 100755
index 0000000..1b4e92c
--- /dev/null
+++ b/charms/trusty/kafka/hooks/zookeeper-relation-departed
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import common
+common.manage()
diff --git a/charms/trusty/kafka/icon.svg b/charms/trusty/kafka/icon.svg
new file mode 100644
index 0000000..1564f99
--- /dev/null
+++ b/charms/trusty/kafka/icon.svg
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="75pt"
+ height="117pt"
+ viewBox="0 0 75 117"
+ version="1.1"
+ id="svg3201"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="icon.svg">
+ <metadata
+ id="metadata3221">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs3219" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1236"
+ inkscape:window-height="847"
+ id="namedview3217"
+ showgrid="false"
+ inkscape:zoom="4.5641627"
+ inkscape:cx="35.369926"
+ inkscape:cy="80.60281"
+ inkscape:window-x="397"
+ inkscape:window-y="78"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer1" />
+ <path
+ d="m 0,0.04322 76.8,0 0,76.8 -76.8,0 0,-76.8 z"
+ id="path3203"
+ style="opacity:0;fill:#fffffe;fill-opacity:0.94977172"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 13.76256,3.6403482 C 20.992,-0.05524154 33.024,1.9665021 36.42368,7.4934765 40.37632,12.889169 34.816,19.735528 25.77408,20.917066 c 0,1.732924 0.01024,3.459282 0,5.185642 4.6592,0.708923 8.87808,2.258051 12.05248,4.56205 2.21184,-0.892717 4.526079,-1.700102 6.72768,-2.605948 -0.59392,-3.209847 -0.59392,-6.833231 3.10272,-9.340718 5.77536,-4.719589 18.51392,-4.476718 23.72608,0.531692 4.751359,4.076308 3.266559,10.450051 -3.21536,13.403898 -6.03136,3.012923 -15.28832,2.271179 -20.41856,-1.266872 -2.16064,0.833641 -4.43392,1.549128 -6.52288,2.454975 -0.307201,1.122461 0.45056,2.251486 0.6144,3.373948 0.70656,1.956103 -0.78848,3.859693 -0.68608,5.809231 2.11968,0.951795 4.47488,1.68041 6.79936,2.415589 6.36928,-4.653948 19.445759,-3.918768 24.10496,1.575386 5.12,5.087179 0.62464,12.668717 -8.52992,14.211281 -6.85056,1.273436 -14.7968,-0.794256 -17.84832,-4.988717 -1.81248,-2.23836 -1.69984,-4.857437 -0.8704,-7.259898 -2.29376,-0.951795 -4.67968,-1.805128 -6.97344,-2.756923 -3.16416,2.284307 -7.34208,3.885949 -12.01152,4.516102 -0.06144,1.746051 -0.04096,3.498667 -0.0512,5.251283 4.78208,0.728615 8.98048,2.921025 10.81344,5.868307 2.75456,4.299487 0.07168,9.872411 -6.51264,12.025436 -6.2464,2.422154 -15.06304,1.234052 -19.33312,-2.546872 -4.89472,-3.846564 -3.84,-10.095589 2.17088,-13.213539 1.91488,-1.102768 4.41344,-1.634461 6.78912,-2.19241 0.03072,-1.68041 0.04096,-3.367384 0.07168,-5.047794 C 14.98112,50.009169 10.21952,48.597887 7.26016,45.919734 1.44384,41.180451 2.2528,33.671118 9.20576,29.581682 c 2.78528,-1.93641 6.77888,-2.776616 10.567679,-3.577436 -0.03072,-1.68041 -0.04096,-3.360821 -0.07168,-5.034667 C 15.59552,20.181887 11.60192,18.678708 9.5641599,16.164655 5.82656,12.081784 7.69024,6.3644508 13.76256,3.6403482 z"
+ id="path3205"
+ inkscape:connector-curvature="0"
+ style="fill:#201f1f" />
+ <path
+ d="M 18.95424,7.4869124 C 23.58272,5.3338867 30.53568,8.2614765 29.85984,11.799528 29.48096,14.924041 23.5008,17.182092 19.2,15.462297 14.42816,13.926297 14.27456,9.1410662 18.95424,7.4869124 z"
+ id="path3207"
+ inkscape:connector-curvature="0"
+ style="fill:#fffffe" />
+ <path
+ d="m 55.76704,20.844861 c 4.51584,-1.673846 10.91584,0.761436 10.56768,4.135384 0.235519,3.649642 -7.33184,5.96677 -11.64288,3.557744 -4.106241,-1.897025 -3.38944,-6.255589 1.0752,-7.693128 z"
+ id="path3209"
+ inkscape:connector-curvature="0"
+ style="fill:#fffffe" />
+ <path
+ d="m 18.78016,32.305784 c 7.311359,-2.303999 16.35328,2.829129 13.8752,7.75877 -1.44384,4.555487 -11.17184,6.721641 -16.5376,3.472409 -6.05184,-2.894768 -4.352,-9.570461 2.6624,-11.231179 z"
+ id="path3211"
+ inkscape:connector-curvature="0"
+ style="fill:#fffffe" />
+ <path
+ d="m 54.69184,48.348451 c 4.66944,-2.619077 12.759039,0.347897 11.601919,4.253538 -0.409599,3.629949 -8.345599,5.218462 -12.103679,2.651898 -3.2256,-1.772308 -2.8672,-5.303795 0.50176,-6.905436 z"
+ id="path3213"
+ inkscape:connector-curvature="0"
+ style="fill:#fffffe" />
+ <path
+ d="m 20.67456,61.030297 c 5.10976,-1.13559 10.78272,2.566565 8.82688,5.809231 -1.269761,3.190153 -8.48896,4.483282 -11.84768,1.857641 -4.06528,-2.19241 -2.03776,-6.872615 3.0208,-7.666872 z"
+ id="path3215"
+ inkscape:connector-curvature="0"
+ style="fill:#fffffe" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Alpha"
+ style="opacity:1" />
+</svg>
diff --git a/charms/trusty/kafka/metadata.yaml b/charms/trusty/kafka/metadata.yaml
new file mode 100644
index 0000000..735c20e
--- /dev/null
+++ b/charms/trusty/kafka/metadata.yaml
@@ -0,0 +1,30 @@
+name: apache-kafka
+maintainer: Kevin Monroe <kevin.monroe@canonical.com>
+summary: High-performance pub/sub as distributed commit log.
+description: |
+ Fast
+ A single Kafka broker can handle hundreds of megabytes of reads and writes per
+ second from thousands of clients.
+
+ Scalable
+ Kafka is designed to allow a single cluster to serve as the central data
+ backbone for a large organization. It can be elastically and transparently
+ expanded without downtime. Data streams are partitioned and spread over a
+ cluster of machines to allow data streams larger than the capability of any
+ single machine and to allow clusters of co-ordinated consumers.
+
+ Durable
+ Messages are persisted on disk and replicated within the cluster to prevent
+ data loss. Each broker can handle terabytes of messages without performance
+ impact.
+
+ Distributed by Design
+ Kafka has a modern cluster-centric design that offers strong durability and
+ fault-tolerance guarantees.
+tags: ["bigdata", "apache"]
+provides:
+ kafka:
+ interface: kafka
+requires:
+ zookeeper:
+ interface: zookeeper
diff --git a/charms/trusty/kafka/resources.yaml b/charms/trusty/kafka/resources.yaml
new file mode 100644
index 0000000..a3b5c74
--- /dev/null
+++ b/charms/trusty/kafka/resources.yaml
@@ -0,0 +1,12 @@
+options:
+ output_dir: /home/ubuntu/resources
+resources:
+ pathlib:
+ pypi: path.py>=7.0
+ jujubigdata:
+ pypi: jujubigdata>=4.1.0,<5.0.0
+optional_resources:
+ kafka-x86_64:
+ url: http://mirrors.ibiblio.org/apache/kafka/0.9.0.0/kafka_2.10-0.9.0.0.tgz
+ hash: 4bd0a264b84e8d88445b2712dd9e28ac
+ hash_type: md5
diff --git a/charms/trusty/kafka/resources/python/PyYAML-3.11.tar.gz b/charms/trusty/kafka/resources/python/PyYAML-3.11.tar.gz
new file mode 100644
index 0000000..2a5d431
--- /dev/null
+++ b/charms/trusty/kafka/resources/python/PyYAML-3.11.tar.gz
Binary files differ
diff --git a/charms/trusty/kafka/resources/python/charmhelpers-0.3.1.tar.gz b/charms/trusty/kafka/resources/python/charmhelpers-0.3.1.tar.gz
new file mode 100644
index 0000000..0ccdbd9
--- /dev/null
+++ b/charms/trusty/kafka/resources/python/charmhelpers-0.3.1.tar.gz
Binary files differ
diff --git a/charms/trusty/kafka/resources/python/jujuresources-0.2.11.tar.gz b/charms/trusty/kafka/resources/python/jujuresources-0.2.11.tar.gz
new file mode 100644
index 0000000..c491086
--- /dev/null
+++ b/charms/trusty/kafka/resources/python/jujuresources-0.2.11.tar.gz
Binary files differ
diff --git a/charms/trusty/kafka/resources/python/pyaml-15.5.7.tar.gz b/charms/trusty/kafka/resources/python/pyaml-15.5.7.tar.gz
new file mode 100644
index 0000000..c51f6d1
--- /dev/null
+++ b/charms/trusty/kafka/resources/python/pyaml-15.5.7.tar.gz
Binary files differ
diff --git a/charms/trusty/kafka/resources/python/six-1.9.0-py2.py3-none-any.whl b/charms/trusty/kafka/resources/python/six-1.9.0-py2.py3-none-any.whl
new file mode 100644
index 0000000..743ee12
--- /dev/null
+++ b/charms/trusty/kafka/resources/python/six-1.9.0-py2.py3-none-any.whl
Binary files differ
diff --git a/charms/trusty/kafka/templates/upstart.conf b/charms/trusty/kafka/templates/upstart.conf
new file mode 100644
index 0000000..d24fe8c
--- /dev/null
+++ b/charms/trusty/kafka/templates/upstart.conf
@@ -0,0 +1,14 @@
+#!upstart
+description "kafka"
+
+start on startup
+stop on shutdown
+
+setuid kafka
+setgid hadoop
+
+respawn
+
+script
+"{{kafka_bin}}/kafka-server-start.sh" "{{kafka_conf}}/server.properties"
+end script
diff --git a/charms/trusty/kafka/tests/00-setup b/charms/trusty/kafka/tests/00-setup
new file mode 100755
index 0000000..36549ea
--- /dev/null
+++ b/charms/trusty/kafka/tests/00-setup
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+sudo add-apt-repository ppa:juju/stable -y
+sudo apt-get update
+sudo apt-get install python3 amulet -y
diff --git a/charms/trusty/kafka/tests/100-deploy-kafka b/charms/trusty/kafka/tests/100-deploy-kafka
new file mode 100755
index 0000000..713a4b4
--- /dev/null
+++ b/charms/trusty/kafka/tests/100-deploy-kafka
@@ -0,0 +1,29 @@
+#!/usr/bin/python3
+import unittest
+import amulet
+
+
+class TestDeploy(unittest.TestCase):
+ """
+ Deployment test for Apache Kafka
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ cls.d = amulet.Deployment(series='trusty')
+ # Deploy Kafka Service
+ cls.d.add('kafka', charm='cs:~bigdata-dev/trusty/apache-kafka')
+ cls.d.add('zookeeper', charm='cs:~bigdata-dev/trusty/apache-zookeeper')
+ cls.d.relate('kafka:zookeeper', 'zookeeper:zookeeper')
+
+ cls.d.setup(timeout=1800)
+ cls.d.sentry.wait(timeout=1800)
+ cls.unit = cls.d.sentry['kafka'][0]
+
+ def test_deploy(self):
+ output, retcode = self.unit.run("pgrep -a java")
+ assert 'Kafka' in output, "Kafka daemon is not started"
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/charms/trusty/kafka/tests/remote/test_dist_config.py b/charms/trusty/kafka/tests/remote/test_dist_config.py
new file mode 100755
index 0000000..eb2c3aa
--- /dev/null
+++ b/charms/trusty/kafka/tests/remote/test_dist_config.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+import grp
+import os
+import pwd
+import unittest
+
+import jujubigdata
+
+
+class TestDistConfig(unittest.TestCase):
+ """
+ Test that the ``dist.yaml`` settings were applied properly, such as users, groups, and dirs.
+
+ This is done as a remote test on the deployed unit rather than a regular
+ test under ``tests/`` because filling in the ``dist.yaml`` requires Juju
+ context (e.g., config).
+ """
+ @classmethod
+ def setUpClass(cls):
+ config = None
+ config_dir = os.environ['JUJU_CHARM_DIR']
+ config_file = 'dist.yaml'
+ if os.path.isfile(os.path.join(config_dir, config_file)):
+ config = os.path.join(config_dir, config_file)
+ if not config:
+ raise IOError('Could not find {} in {}'.format(config_file, config_dir))
+ reqs = ['vendor', 'hadoop_version', 'groups', 'users', 'dirs']
+ cls.dist_config = jujubigdata.utils.DistConfig(config, reqs)
+
+ def test_groups(self):
+ for name in self.dist_config.groups:
+ try:
+ grp.getgrnam(name)
+ except KeyError:
+ self.fail('Group {} is missing'.format(name))
+
+ def test_users(self):
+ for username, details in self.dist_config.users.items():
+ try:
+ user = pwd.getpwnam(username)
+ except KeyError:
+ self.fail('User {} is missing'.format(username))
+ for groupname in details['groups']:
+ try:
+ group = grp.getgrnam(groupname)
+ except KeyError:
+ self.fail('Group {} referenced by user {} does not exist'.format(
+ groupname, username))
+ if group.gr_gid != user.pw_gid:
+ self.assertIn(username, group.gr_mem, 'User {} not in group {}'.format(
+ username, groupname))
+
+ def test_dirs(self):
+ for name, details in self.dist_config.dirs.items():
+ dirpath = self.dist_config.path(name)
+ self.assertTrue(dirpath.isdir(), 'Dir {} is missing'.format(name))
+ stat = dirpath.stat()
+ owner = pwd.getpwuid(stat.st_uid).pw_name
+ group = grp.getgrgid(stat.st_gid).gr_name
+ perms = stat.st_mode & ~0o40000
+ self.assertEqual(owner, details.get('owner', 'root'),
+ 'Dir {} ({}) has wrong owner: {}'.format(name, dirpath, owner))
+ self.assertEqual(group, details.get('group', 'root'),
+ 'Dir {} ({}) has wrong group: {}'.format(name, dirpath, group))
+ self.assertEqual(perms, details.get('perms', 0o755),
+ 'Dir {} ({}) has wrong perms: 0o{:o}'.format(name, dirpath, perms))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/charms/trusty/kafka/tests/tests.yaml b/charms/trusty/kafka/tests/tests.yaml
new file mode 100644
index 0000000..771f3fd
--- /dev/null
+++ b/charms/trusty/kafka/tests/tests.yaml
@@ -0,0 +1,10 @@
+# Driver for bundletester: https://github.com/juju-solutions/bundletester
+#
+# It may be useful to alter the defaults during manual testing. For example,
+# set 'reset: false' to reuse existing charms instead of redeploying them.
+
+# Allow bootstrap of current env, default: true
+bootstrap: true
+
+# Use juju-deployer to reset env between test, default: true
+reset: true