From 52439e3040e80c9291bb66edf214c7efa9b984ce Mon Sep 17 00:00:00 2001 From: Pete Vander Giessen Date: Mon, 6 Jun 2016 10:46:48 -0400 Subject: [PATCH 1/5] BIGTOP-2476 Add Zookeeper Charm Deploy Apache Zookeeper to the cloud using Apache Bigtop via Juju. Addendums: * Added ability to specify an interface to bind the client to. Helps users who want to restrict Zookeeper to communicating with the outside world on a specific ip or interface. * Fixed relation name. (Was zkclient after all -- my bad) * Renamed client_port_bind_address -> client_bind_addr Makes it a bit more consistent with our other naming conventions. * Updated Zookeeper charm to use xenial. charm proof was unhappy about the null value for client_bind_addr when run on the xenial charm. Updated it to an empty string (which will work the same way in the code), and things were happier. * Fix for issues with Zookeeper client_bind_addr option. (#31) Since this value was set at config time, the config would be duplicated across instances of the charm, instead of being unique to each machine, as an ip address should be. This, coupled with matching code change in the bigtop base layer, fixes the issue, and also fixes an issue where Zookeeper would not restart if you changed its interface after deploying it. * Cleaned up some loose ends in last PR. Updated README to cover using the new network_interface config option. Wrote tests for the network_interface option. Fixed an issue where the 01-deploy test was not waiting for smoke tests to complete. * Updated README. Added -r to "resolved" instruction. Without it, juju won't automatically try to re-run config-changed. * update charm metadata * Fixed 10-bind-address test. It wasn't actually running, and when run, it was doing broken things. * More clearly marked that we are disabling a test. --- .../charm/zookeeper/layer-zookeeper/LICENSE | 177 +++++ .../charm/zookeeper/layer-zookeeper/README.md | 121 ++++ .../zookeeper/layer-zookeeper/actions.yaml | 2 + .../zookeeper/layer-zookeeper/actions/restart | 42 ++ .../zookeeper/layer-zookeeper/config.yaml | 10 + .../charm/zookeeper/layer-zookeeper/copyright | 16 + .../charm/zookeeper/layer-zookeeper/icon.svg | 639 ++++++++++++++++++ .../zookeeper/layer-zookeeper/layer.yaml | 22 + .../lib/charms/layer/zookeeper.py | 173 +++++ .../zookeeper/layer-zookeeper/metadata.yaml | 18 + .../layer-zookeeper/reactive/zookeeper.py | 87 +++ .../zookeeper/layer-zookeeper/tests/01-deploy | 77 +++ .../layer-zookeeper/tests/10-bind-address | 84 +++ .../layer-zookeeper/tests/tests.yaml | 3 + 14 files changed, 1471 insertions(+) create mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/LICENSE create mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md create mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions.yaml create mode 100755 bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions/restart create mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/config.yaml create mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/copyright create mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/icon.svg create mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml create mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/lib/charms/layer/zookeeper.py create mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml create mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/reactive/zookeeper.py create mode 100755 bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy create mode 100755 bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address create mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/tests.yaml diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/LICENSE b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/LICENSE new file mode 100644 index 0000000000..f433b1a53f --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/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/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md new file mode 100644 index 0000000000..0329cae8a3 --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md @@ -0,0 +1,121 @@ + +## Overview +Apache ZooKeeper is a high-performance coordination service for distributed +applications. It exposes common services such as naming, configuration +management, synchronization, and group services in a simple interface so you +don't have to write them from scratch. You can use it off-the-shelf to +implement consensus, group management, leader election, and presence protocols. + +## Usage +Deploy a Zookeeper unit. With only one unit, the service will be running in +`standalone` mode: + + juju deploy openjdk + juju deploy zookeeper zookeeper + juju add-relation openjdk zookeeper + + +## Scaling +Running ZooKeeper in `standalone` mode is convenient for evaluation, some +development, and testing. But in production, you should run ZooKeeper in +`replicated` mode. A replicated group of servers in the same application is +called a quorum, and in `replicated` mode, all servers in the quorum have +copies of the same configuration file. + +In order to add new Zookeeper servers to the quorum, you must deploy +them, and then perform a rolling restart of all the servers in the +quorum. Note that Zookeeper can break when you're adding nodes to a +cluster with active connections, so you'll want to checkup on the +cluster afterward to verify that everything is still happy. + +The following commands will add two new nodes to a cluster: + + juju add-unit -n 2 zookeeper + juju run-action zookeeper/0 restart + juju run-action zookeeper/1 restart + juju run-action zookeeper/2 restart + +(Future versions of Zookeeper are more stable, and we are planning on +automating the restart process in the future.) + + +## Network Interfaces + +In some network environments, you may want to restrict your Zookeepers +to listen for client connections on a specific network interface (for +example, for security reasons). To do so, you may pass either a +network interface name or a CIDR range specifying a subnet to the +``network_interface`` configuration variable. For example: + + juju set-config zookeeper network_interface=eth0 + +or + + juju set-config zookeeper network_interface=10.0.2.0/24 + +Each Zookeeper machine in your cluster will lookup the IP address of that +network interface, or find the first network interface with an IP +address in the specified subnet, and bind Zookeeper to that address. + +If you make a mistake, and pass an invalid name for a network +interface, you may recover by passing the correct name to set-config, +and then running "juju resolved" on each unit: + + juju set-config zookeeper network_interface=eth0 + juju resolved -r zookeeper/0 + +If you want to go back to listening on any network interface on the +machine, simply pass ``0.0.0.0`` to ``network_interface``. + + juju set-config zookeeper network_interface=0.0.0.0 + + +## Test the deployment +Test if the Zookeeper service is running by using the `zkServer.sh` script: + + juju run --service=zookeeper '/usr/lib/zookeeper/bin/zkServer.sh status' + +A successful deployment will report the service mode as either `standalone` +(if only one Zookeeper unit has been deployed) or `leader` / `follower` (if +a Zookeeper quorum has been formed). + + +## Integrate Zookeeper into another charm +1) Add following lines to your charm's metadata.yaml: + + requires: + zookeeper: + interface: zookeeper + +2) Add a `zookeeper-relation-changed` hook to your charm. Example contents: + + from charmhelpers.core.hookenv import relation_get + ZK_hostname = relation_get('private-address') + ZK_port = relation_get('port') + + + +## Contact Information +[bigdata@lists.ubuntu.com](mailto:bigdata@lists.ubuntu.com) + + +## Help +- [Apache Zookeeper home page](https://zookeeper.apache.org/) +- [Apache Zookeeper issue tracker](https://issues.apache.org/jira/browse/ZOOKEEPER) +- [Juju mailing list](https://lists.ubuntu.com/mailman/listinfo/juju) +- [Juju community](https://jujucharms.com/community) diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions.yaml b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions.yaml new file mode 100644 index 0000000000..10c5a7813e --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions.yaml @@ -0,0 +1,2 @@ +restart: + description: stop and start a Zookeeper server. diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions/restart b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions/restart new file mode 100755 index 0000000000..4dcf6ae7fd --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions/restart @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 sys +sys.path.append('lib') # Add our helpers to our path. + +from charmhelpers.core import hookenv +from charms.layer.zookeeper import Zookeeper +from charms.reactive import is_state + +LOG = hookenv.log + +LOG("starting restart handler.") + +def main(): + if not is_state('zookeeper.started'): + hookenv.action_fail('Cannot restart: Zookeeper has not yet started!') + return + + LOG("starting restart handler main.") + hookenv.status_set('maintenance', 'restarting ...') + zookeeper = Zookeeper() + zookeeper.install() + hookenv.status_set('active', 'ready {}'.format(zookeeper.quorum_check())) + + +if __name__ == '__main__': + main() diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/config.yaml b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/config.yaml new file mode 100644 index 0000000000..d88a94bca7 --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/config.yaml @@ -0,0 +1,10 @@ +options: + network_interface: + default: "" + type: string + description: | + Network interface to bind the Zookeeper client port to. Defaults + to accepting connections on all interfaces. Accepts either the + name of an interface (e.g., 'eth0'), or a CIDR range. If the + latter, we\'ll bind to the first interface that we find with an + IP address in that range. \ No newline at end of file diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/copyright b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/copyright new file mode 100644 index 0000000000..e900b97c45 --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/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/bigtop-packages/src/charm/zookeeper/layer-zookeeper/icon.svg b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/icon.svg new file mode 100644 index 0000000000..4fd8c0ca2e --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/icon.svg @@ -0,0 +1,639 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml new file mode 100644 index 0000000000..f3b8ee0d1b --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml @@ -0,0 +1,22 @@ +repo: git@github.com:juju-solutions/bigtop.git +includes: + - 'layer:apache-bigtop-base' + - 'interface:zookeeper-quorum' + - 'interface:zookeeper' +options: + apache-bigtop-base: + 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. + # If adding a new port here, you will need to update + # charmhelpers.contrib.bigdata.handlers.apache or hooks/callbacks.py + # to ensure that it is supported. + zookeeper-rest: + port: 9998 + exposed_on: 'zookeeper' + zookeeper: + port: 2181 + exposed_on: 'zookeeper' + bigtop_smoketest_components: ['zookeeper'] diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/lib/charms/layer/zookeeper.py b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/lib/charms/layer/zookeeper.py new file mode 100644 index 0000000000..cdc52c97b6 --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/lib/charms/layer/zookeeper.py @@ -0,0 +1,173 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 charmhelpers.core import host +from charmhelpers.core.hookenv import (open_port, close_port, log, + unit_private_ip, local_unit, config) +from charms import layer +from charms.layer.apache_bigtop_base import Bigtop +from charms.reactive.relations import RelationBase +from jujubigdata.utils import DistConfig + + +def format_node(unit, node_ip): + ''' + Given a juju unit name and an ip address, return a tuple + containing an id and formatted ip string suitable for passing to + puppet, which will write it out to zoo.cfg. + + ''' + return (unit.split("/")[1], "{ip}:2888:3888".format(ip=node_ip)) + + +class Zookeeper(object): + ''' + Utility class for managing Zookeeper tasks like configuration, start, + stop, and adding and removing nodes. + + ''' + def __init__(self, dist_config=None): + self._dist_config = dist_config or DistConfig( + data=layer.options('apache-bigtop-base')) + + self._roles = ['zookeeper-server', 'zookeeper-client'] + self._hosts = {} + + def is_zk_leader(self): + ''' + Attempt to determine whether this node is the Zookeeper leader. + + Note that Zookeeper tracks leadership independently of juju, + and that this command can fail, depending on the state that + the Zookeeper node is in when we attempt to run it. + + ''' + try: + status = subprocess.check_output( + ["/usr/lib/zookeeper/bin/zkServer.sh", "status"]) + return "leader" in status.decode('utf-8') + except Exception: + log( + "Unable to determine whether this node is the Zookeeper leader.", + level="WARN" + ) + return False + + def read_peers(self): + ''' + Fetch the list of peers available. + + The first item in this list should always be the node that + this code is executing on. + + ''' + # A Zookeeper node likes to be first on the list. + nodes = [(local_unit(), unit_private_ip())] + # Get the list of peers + zkpeer = RelationBase.from_state('zkpeer.joined') + if zkpeer: + nodes.extend(sorted(zkpeer.get_nodes())) + nodes = [format_node(*node) for node in nodes] + return nodes + + @property + def dist_config(self): + ''' + Charm level config. + + ''' + return self._dist_config + + @property + def _override(self): + ''' + Return a dict of keys and values that will override puppet's + defaults. + + ''' + override = { + "hadoop_zookeeper::server::myid": local_unit().split("/")[1], + "hadoop_zookeeper::server::ensemble": self.read_peers() + } + network_interface = config().get('network_interface') + if network_interface: + key = "hadoop_zookeeper::server::client_bind_addr" + override[key] = Bigtop().get_ip_for_interface(network_interface) + + return override + + def install(self, nodes=None): + ''' + Write out the config, then run puppet. + + After this runs, we should have a configured and running service. + + ''' + bigtop = Bigtop() + log("Rendering site yaml ''with overrides: {}".format(self._override)) + bigtop.render_site_yaml(self._hosts, self._roles, self._override) + bigtop.trigger_puppet() + + def start(self): + ''' + Request that our service start. Normally, puppet will handle this + for us. + + ''' + host.service_start('zookeeper-server') + + def stop(self): + ''' + Stop Zookeeper. + + ''' + host.service_stop('zookeeper-server') + + def open_ports(self): + ''' + Expose the ports in the configuration to the outside world. + + ''' + for port in self.dist_config.exposed_ports('zookeeper'): + open_port(port) + + def close_ports(self): + ''' + Close off communication from the outside world. + + ''' + for port in self.dist_config.exposed_ports('zookeeper'): + close_port(port) + + def quorum_check(self): + ''' + Returns a string reporting the node count. Append a message + informing the user if the node count is too low for good quorum, + or is even (meaning that one of the nodes is redundant for + quorum). + + ''' + node_count = len(self.read_peers()) + if node_count == 1: + count_str = "{} zk node".format(node_count) + else: + count_str = "{} zk nodes".format(node_count) + if node_count < 3: + return " ({}; less than 3 nodes is suboptimal)".format(count_str) + if node_count % 2 == 0: + return " ({}; an even number is suboptimal)".format(count_str) + return "({})".format(count_str) diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml new file mode 100644 index 0000000000..14dd722ecd --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml @@ -0,0 +1,18 @@ +name: zookeeper +maintainer: Juju Big Data +summary: High-performance coordination service for distributed applications +description: | + Apache ZooKeeper is a centralized, reliable, service for maintaining + configuration information, naming, providing distributed + synchronization, and group services. All of these kinds of services + are used in some form or another by distributed applications. + In order to install and configure Apache HBase and other Hadoop ecosystem + components, you must start the ZooKeeper service. +tags: ["bigdata", "hadoop", "apache"] +provides: + zookeeper: + interface: zookeeper +peers: + zkpeer: + interface: zookeeper-quorum +series: ["xenial"] diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/reactive/zookeeper.py b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/reactive/zookeeper.py new file mode 100644 index 0000000000..a749c5bd5a --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/reactive/zookeeper.py @@ -0,0 +1,87 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +from charmhelpers.core import hookenv +from charms.layer.zookeeper import Zookeeper +from charms.reactive import set_state, when, when_not +from charms.reactive.helpers import data_changed + + +@when('bigtop.available') +@when_not('zookeeper.installed') +def install_zookeeper(): + ''' + After Bigtop has done the initial setup, trigger a puppet install, + via our Zooekeeper library. + + puppet will start the service, as a side effect. + + ''' + hookenv.status_set('maintenance', 'installing zookeeper') + zookeeper = Zookeeper() + # Prime data changed + data_changed('zkpeer.nodes', zookeeper.read_peers()) + data_changed( + 'zk.network_interface', + hookenv.config().get('network_interface')) + zookeeper.install() + zookeeper.open_ports() + set_state('zookeeper.installed') + set_state('zookeeper.started') + hookenv.status_set('active', 'ready {}'.format(zookeeper.quorum_check())) + + +@when('zookeeper.started') +def update_network_interface(): + ''' + Possibly restart zookeeper, due to the network interface that it + should listen on changing. + + ''' + network_interface = hookenv.config().get('network_interface') + if data_changed('zk.network_interface', network_interface): + hookenv.status_set('maintenance', 'updating network interface') + zookeeper = Zookeeper() + zookeeper.install() + hookenv.status_set( + 'active', 'ready {}'.format(zookeeper.quorum_check())) + + +@when('zookeeper.started') +def check_cluster(): + ''' + Checkup on the state of the cluster. Inform an operator that they + need to restart if the peers have changed. + + ''' + zk = Zookeeper() + if data_changed('zkpeer.nodes', zk.read_peers()): + if zk.is_zk_leader(): + note = ' (restart this node last)' + else: + note = '' + message = ( + "number of zk peers has changed -- you must use " + "the 'restart' action to perform a rolling restart to " + "update your cluster{}".format(note)) + hookenv.status_set('active', message) + + +@when('zookeeper.started', 'zookeeper.joined') +def serve_client(client): + config = Zookeeper().dist_config + port = config.port('zookeeper') + rest_port = config.port('zookeeper-rest') # TODO: add zookeeper REST + client.send_port(port, rest_port) diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy new file mode 100755 index 0000000000..a1ef4e7b91 --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy @@ -0,0 +1,77 @@ +#!/usr/bin/python3 + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 unittest +import amulet + + +class TestDeploy(unittest.TestCase): + """ + Deployment test for Apache Zookkepper quorum + """ + + @classmethod + def setUpClass(cls): + cls.d = amulet.Deployment(series='xenial') + + cls.d.add('jdk', charm='openjdk') + cls.d.add('zookeeper', charm='zookeeper', units=3) + cls.d.relate('zookeeper:java', 'jdk:java') + + cls.d.setup(timeout=900) + cls.d.sentry.wait(timeout=1800) + cls.unit = cls.d.sentry['zookeeper'][0] + + def test_deploy(self): + output, retcode = self.unit.run("pgrep -a java") + assert 'QuorumPeerMain' in output, "zookeeper QuorumPeerMain daemon is not started" + + def test_quorum(self): + ''' + Verify that our peers are talking to each other, and taking on + appropriate roles. + + ''' + self.assertEqual(3, len(self.d.sentry['zookeeper'])) + # Perform a rolling restart + for unit in self.d.sentry['zookeeper']: + action_id = unit.run_action("restart") + amulet.actions.get_action_output(action_id) # Wait for restart to complete + + # Verify that everything worked. + for unit in self.d.sentry['zookeeper']: + output, _ = unit.run( + "/usr/lib/zookeeper/bin/zkServer.sh status" + ) + # Unit should be a leader or follower + self.assertTrue("leader" in output or "follower" in output) + + def test_smoke(self): + ''' + Run smoke tests. + ''' + smk_uuids = [] + + for unit in self.d.sentry['zookeeper']: + smk_uuids.append(unit.action_do("smoke-test")) + + for smk_uuid in smk_uuids: + output = self.d.action_fetch(smk_uuid, full_output=True) + assert "completed" in output['status'] + +if __name__ == '__main__': + unittest.main() diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address new file mode 100755 index 0000000000..d815c4c1e4 --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address @@ -0,0 +1,84 @@ +#!/usr/bin/python3 + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 unittest +import amulet +import re + + +class TestBindClientPort(unittest.TestCase): + """ + Test to verify that we can bind to listen for client connections + on a specific interface. + """ + + @classmethod + def setUpClass(cls): + cls.d = amulet.Deployment(series='xenial') + + cls.d.add('jdk', charm='openjdk') + cls.d.add('zookeeper', charm='zookeeper', units=3) + cls.d.relate('zookeeper:java', 'jdk:java') + + cls.d.setup(timeout=900) + cls.d.sentry.wait(timeout=1800) + cls.unit = cls.d.sentry['zookeeper'][0] + + def test_bind_port(self): + """ + Test to verify that we update client port bindings successfully. + + """ + self.d.configure('zookeeper', {'network_interface': 'eth0'}) + self.d.sentry.wait_for_messages({'zookeeper': 'updating network interface'}, timeout=60) + self.d.sentry.wait_for_messages({'zookeeper': 'ready (3 zk nodes)'}, timeout=60) + ret = self.unit.run( + 'grep clientPortAddress /etc/zookeeper/conf/zoo.cfg')[0] + matcher = re.compile( + "^clientPortAddress=\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.*") + + self.assertTrue(matcher.match(ret)) + + # Verify that smoke tests still run + smk_uuid = self.unit.action_do("smoke-test") + output = self.d.action_fetch(smk_uuid, full_output=True) + assert "completed" in output['status'] + + @unittest.skip( + 'Broken handling of 0.0.0.0 bindings upstream, in Zookeeper project.') + def test_reset_bindings(self): + """ + Verify that we can reset the client port bindings to 0.0.0.0 + + """ + self.d.configure('zookeeper', {'network_interface': '0.0.0.0'}) + self.d.sentry.wait_for_messages({'zookeeper': 'updating network interface'}, timeout=60) + self.d.sentry.wait_for_messages({'zookeeper': 'ready (3 zk nodes)'}, timeout=60) + ret = self.unit.run( + 'grep clientPortAddress /etc/zookeeper/conf/zoo.cfg')[0] + + matcher = re.compile("^clientPortAddress=0\.0\.0\.0.*") + self.assertTrue(matcher.match(ret)) + + # Verify that smoke tests still run + smk_uuid = self.unit.action_do("smoke-test") + output = self.d.action_fetch(smk_uuid, full_output=True) + assert "completed" in output['status'] + + +if __name__ == '__main__': + unittest.main() diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/tests.yaml b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/tests.yaml new file mode 100644 index 0000000000..3b6ce3e5f8 --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/tests.yaml @@ -0,0 +1,3 @@ +reset: false +packages: + - amulet From 344be777c6cf12fe290c6d30972330f7e9066234 Mon Sep 17 00:00:00 2001 From: Pete Vander Giessen Date: Fri, 16 Sep 2016 14:32:18 -0700 Subject: [PATCH 2/5] Removed openjdk dependency. (#41) --- .../charm/zookeeper/layer-zookeeper/README.md | 2 - .../zookeeper/layer-zookeeper/tests/01-deploy | 8 ++-- .../layer-zookeeper/tests/10-bind-address | 43 ++++++++++++++----- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md index 0329cae8a3..14ce8deeda 100644 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md @@ -25,9 +25,7 @@ implement consensus, group management, leader election, and presence protocols. Deploy a Zookeeper unit. With only one unit, the service will be running in `standalone` mode: - juju deploy openjdk juju deploy zookeeper zookeeper - juju add-relation openjdk zookeeper ## Scaling diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy index a1ef4e7b91..c587077d22 100755 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy @@ -18,6 +18,8 @@ import unittest import amulet +TIMEOUT = 1800 + class TestDeploy(unittest.TestCase): """ @@ -28,12 +30,10 @@ class TestDeploy(unittest.TestCase): def setUpClass(cls): cls.d = amulet.Deployment(series='xenial') - cls.d.add('jdk', charm='openjdk') cls.d.add('zookeeper', charm='zookeeper', units=3) - cls.d.relate('zookeeper:java', 'jdk:java') - cls.d.setup(timeout=900) - cls.d.sentry.wait(timeout=1800) + cls.d.setup(timeout=TIMEOUT) + cls.d.sentry.wait(timeout=TIMEOUT) cls.unit = cls.d.sentry['zookeeper'][0] def test_deploy(self): diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address index d815c4c1e4..cf4641e6a6 100755 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address @@ -16,8 +16,11 @@ # limitations under the License. import unittest -import amulet import re +import amulet + +DEPLOY_TIMEOUT = 1800 +CONFIG_TIMEOUT = 900 class TestBindClientPort(unittest.TestCase): @@ -30,12 +33,10 @@ class TestBindClientPort(unittest.TestCase): def setUpClass(cls): cls.d = amulet.Deployment(series='xenial') - cls.d.add('jdk', charm='openjdk') cls.d.add('zookeeper', charm='zookeeper', units=3) - cls.d.relate('zookeeper:java', 'jdk:java') - cls.d.setup(timeout=900) - cls.d.sentry.wait(timeout=1800) + cls.d.setup(timeout=CONFIG_TIMEOUT) + cls.d.sentry.wait(timeout=DEPLOY_TIMEOUT) cls.unit = cls.d.sentry['zookeeper'][0] def test_bind_port(self): @@ -43,9 +44,29 @@ class TestBindClientPort(unittest.TestCase): Test to verify that we update client port bindings successfully. """ - self.d.configure('zookeeper', {'network_interface': 'eth0'}) - self.d.sentry.wait_for_messages({'zookeeper': 'updating network interface'}, timeout=60) - self.d.sentry.wait_for_messages({'zookeeper': 'ready (3 zk nodes)'}, timeout=60) + network_interface = None + # Regular expression should handle interfaces in the format + # eth[n], and in the format en[foo] (the "predicatble + # interface names" in v197+ of systemd). + ethernet_interface = re.compile('^e[thn]+.*') + interfaces, _ = self.unit.run( + "ifconfig -a | sed 's/[ \t].*//;/^$/d'") + interfaces = interfaces.split() # Splits on newlines + for interface in interfaces: + if ethernet_interface.match(interface): + network_interface = interface + break + + if network_interface is None: + raise Exception( + "Could not find any interface on the unit that matched my " + "criteria.") + + self.d.configure('zookeeper', {'network_interface': network_interface}) + self.d.sentry.wait_for_messages( + {'zookeeper': 'updating network interface'}, timeout=CONFIG_TIMEOUT) + self.d.sentry.wait_for_messages( + {'zookeeper': 'ready (3 zk nodes)'}, timeout=CONFIG_TIMEOUT) ret = self.unit.run( 'grep clientPortAddress /etc/zookeeper/conf/zoo.cfg')[0] matcher = re.compile( @@ -66,8 +87,10 @@ class TestBindClientPort(unittest.TestCase): """ self.d.configure('zookeeper', {'network_interface': '0.0.0.0'}) - self.d.sentry.wait_for_messages({'zookeeper': 'updating network interface'}, timeout=60) - self.d.sentry.wait_for_messages({'zookeeper': 'ready (3 zk nodes)'}, timeout=60) + self.d.sentry.wait_for_messages( + {'zookeeper': 'updating network interface'}, timeout=CONFIG_TIMEOUT) + self.d.sentry.wait_for_messages( + {'zookeeper': 'ready (3 zk nodes)'}, timeout=CONFIG_TIMEOUT) ret = self.unit.run( 'grep clientPortAddress /etc/zookeeper/conf/zoo.cfg')[0] From 39afd2a79923bbda436472e14a261735d58be224 Mon Sep 17 00:00:00 2001 From: Konstantinos Tsakalozos Date: Mon, 19 Sep 2016 12:28:31 -0400 Subject: [PATCH 3/5] Removing series from metadata.yaml of zookeeper. (#44) --- .../src/charm/zookeeper/layer-zookeeper/metadata.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml index 14dd722ecd..8a9f769c6c 100644 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml @@ -15,4 +15,3 @@ provides: peers: zkpeer: interface: zookeeper-quorum -series: ["xenial"] From fc9a53ca64ab4aa46298650f3e78494e07d5ad77 Mon Sep 17 00:00:00 2001 From: Pete Vander Giessen Date: Mon, 10 Oct 2016 15:14:08 -0400 Subject: [PATCH 4/5] Zookeeper now does an automatic rolling restart after quorum change. (#46) * Zookeeper now does an automatic rolling restart after quorum change. Replaces user unfriendly manual restart process. * Fixed an issue where things broke when you removed the Juju leader. The leader still attempted to orchestrate things as it was shutting down. We fix this by requiring zkpeer.joined on check_cluster, and add an alternate version of the funciton that triggers on zkpeer.departed, so the new leader picks things up. * Fixed issue w/ unrelated relation data changing causing bugs during restart We now update the queue based on which nodes have restarted, for the current nonce, instead of trying to do the dance with states. This seems to work well. --- .../charm/zookeeper/layer-zookeeper/README.md | 22 +- .../zookeeper/layer-zookeeper/layer.yaml | 1 + .../lib/charms/layer/zookeeper.py | 15 ++ .../layer-zookeeper/reactive/zookeeper.py | 198 +++++++++++++++--- .../zookeeper/layer-zookeeper/tests/01-deploy | 4 - .../layer-zookeeper/tests/10-bind-address | 9 +- 6 files changed, 207 insertions(+), 42 deletions(-) diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md index 14ce8deeda..9a872320da 100644 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md @@ -35,21 +35,19 @@ development, and testing. But in production, you should run ZooKeeper in called a quorum, and in `replicated` mode, all servers in the quorum have copies of the same configuration file. -In order to add new Zookeeper servers to the quorum, you must deploy -them, and then perform a rolling restart of all the servers in the -quorum. Note that Zookeeper can break when you're adding nodes to a -cluster with active connections, so you'll want to checkup on the -cluster afterward to verify that everything is still happy. - -The following commands will add two new nodes to a cluster: +In order to add new Zookeeper servers to the quorum, simply add units +as you usually would in juju: juju add-unit -n 2 zookeeper - juju run-action zookeeper/0 restart - juju run-action zookeeper/1 restart - juju run-action zookeeper/2 restart -(Future versions of Zookeeper are more stable, and we are planning on -automating the restart process in the future.) +The Zookeeper nodes will then automatically perform a rolling restart, +in order to update the Zookeeper quorum without losing any jobs in +progress. Once the rolling restart has completed, all of your +Zookeeper nodes should be in the following state: + + ready (n zk nodes) + +(Where 'n' is the total number of Zookeeper nodes in your quorum.) ## Network Interfaces diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml index f3b8ee0d1b..335c441ad4 100644 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml @@ -1,6 +1,7 @@ repo: git@github.com:juju-solutions/bigtop.git includes: - 'layer:apache-bigtop-base' + - 'layer:leadership' - 'interface:zookeeper-quorum' - 'interface:zookeeper' options: diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/lib/charms/layer/zookeeper.py b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/lib/charms/layer/zookeeper.py index cdc52c97b6..4b4932b234 100644 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/lib/charms/layer/zookeeper.py +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/lib/charms/layer/zookeeper.py @@ -84,6 +84,18 @@ def read_peers(self): nodes = [format_node(*node) for node in nodes] return nodes + def sort_peers(self, zkpeer): + ''' + Return peers, sorted in an order suitable for performing a rolling + restart. + + ''' + peers = self.read_peers() + leader = zkpeer.find_zk_leader() + peers.sort(key=lambda x: x[1] == leader) + + return peers + @property def dist_config(self): ''' @@ -121,6 +133,9 @@ def install(self, nodes=None): log("Rendering site yaml ''with overrides: {}".format(self._override)) bigtop.render_site_yaml(self._hosts, self._roles, self._override) bigtop.trigger_puppet() + if self.is_zk_leader(): + zkpeer = RelationBase.from_state('zkpeer.joined') + zkpeer.set_zk_leader() def start(self): ''' diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/reactive/zookeeper.py b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/reactive/zookeeper.py index a749c5bd5a..825c2198ce 100644 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/reactive/zookeeper.py +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/reactive/zookeeper.py @@ -13,9 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json +import time from charmhelpers.core import hookenv from charms.layer.zookeeper import Zookeeper -from charms.reactive import set_state, when, when_not +from charms.leadership import leader_set, leader_get +from charms.reactive import set_state, when, when_not, is_state from charms.reactive.helpers import data_changed @@ -43,6 +46,17 @@ def install_zookeeper(): hookenv.status_set('active', 'ready {}'.format(zookeeper.quorum_check())) +def _restart_zookeeper(msg): + ''' + Restart Zookeeper by re-running the puppet scripts. + + ''' + hookenv.status_set('maintenance', msg) + zookeeper = Zookeeper() + zookeeper.install() + hookenv.status_set('active', 'ready {}'.format(zookeeper.quorum_check())) + + @when('zookeeper.started') def update_network_interface(): ''' @@ -52,36 +66,172 @@ def update_network_interface(): ''' network_interface = hookenv.config().get('network_interface') if data_changed('zk.network_interface', network_interface): - hookenv.status_set('maintenance', 'updating network interface') - zookeeper = Zookeeper() - zookeeper.install() - hookenv.status_set( - 'active', 'ready {}'.format(zookeeper.quorum_check())) + _restart_zookeeper('updating network interface') -@when('zookeeper.started') -def check_cluster(): +@when('zookeeper.started', 'zookeeper.joined') +def serve_client(client): + config = Zookeeper().dist_config + port = config.port('zookeeper') + rest_port = config.port('zookeeper-rest') # TODO: add zookeeper REST + client.send_port(port, rest_port) + + +# +# Rolling restart -- helpers and handlers +# +# When we add or remove a Zookeeper peer, Zookeeper needs to perform a +# rolling restart of all of its peers, restarting the Zookeeper +# "leader" last. +# +# The following functions accomplish this. Here's how they all fit together: +# +# (As you read, keep in mind that one node functions as the "leader" +# in the context of Juju, and one node functions as the "leader" in +# the context of Zookeeper; these nodes may or may not be the same.) +# +# 0. Whenever the Zookeeper server starts, it attempts to determine +# whether it is the Zookeeper leader. If so, it sets a flag on the +# Juju peer relation data. +# +# 1. When a node is added or remove from the cluster, the Juju leader +# runs `check_cluster`, and generates a "restart queue" comprising +# nodes in the cluster, with the Zookeeper lead node sorted last in +# the queue. It also sets a nonce, to identify this restart queue +# uniquely, and thus handle the situation where another node is +# added or restarted while we're still reacting to the first node's +# addition or removal. The leader drops the queue and nonce into +# the leadership data as "restart_queue" and "restart_nonce", +# respectively. +# +# 2. When any node detects a leadership.changed.restart_queue event, +# it runs `restart_for_quorum`, which is a noop unless the node's +# private address is the first element of the restart queue. In +# that case, if the node is the Juju leader, it will restart, then +# remove itself from the restart queue, triggering another +# leadership.changed.restart_queue event. If the node isn't the +# Juju leader, it will restart itself, then run `inform_restart`. +# +# 3. `inform_restart` will create a relation data changed event, which +# triggers `update_restart_queue` to run on the leader. This method +# will update the restart_queue, clearing any nodes that have +# restarted for the current nonce, and looping us back to step 2. +# +# 4. Once all the nodes have restarted, we should be in the following state: +# +# * All nodes have an updated Zookeeper server running with the new +# * peer data. +# +# * The Zookeeper leader has restarted last, which should help +# prevent orphaned jobs, per the Zookeeper docs. +# +# * peers still have zkpeer.restarted. set on their relation +# data. This is okay, as we will generate a new nonce next time, +# and the data is small. +# +# Edge cases and potential bugs: +# +# 1. Juju leader changes in the middle of a restart: this gets a +# little bit dicey, but it should work. The new leader should run +# `check_cluster_departed`, and start a new restart_queue. +# + +def _ip_list(nodes): ''' - Checkup on the state of the cluster. Inform an operator that they - need to restart if the peers have changed. + Given a list of nodes, in the format that our peer relation or + zookeeper lib will typically return node lists in, make a list of + just the ips (stripping ports, if they have been added). + + We expect the list we passed in to look something like this: + + [('zookeeper/0', '10.0.0.4'), ('zookeeper/1', '10.0.0.5')] + + or this: + + [('0', '10.0.0.4:2888:4888'), ('1', '10.0.0.5:2888:4888')] + + We will return a list in the form: + + ['10.0.0.4', '10.0.0.5'] + + ''' + return [node[1].split(':')[0] for node in nodes] + + +@when('zookeeper.started', 'leadership.is_leader', 'zkpeer.joined') +@when_not('zkpeer.departed') +def check_cluster(zkpeer): + ''' + Checkup on the state of the cluster. Start a rolling restart if + the peers have changed. ''' zk = Zookeeper() if data_changed('zkpeer.nodes', zk.read_peers()): - if zk.is_zk_leader(): - note = ' (restart this node last)' + peers = _ip_list(zk.sort_peers(zkpeer)) + nonce = time.time() + hookenv.log('Quorum changed. Restart queue: {}'.format(peers)) + leader_set( + restart_queue=json.dumps(peers), + restart_nonce=json.dumps(nonce) + ) + + +@when('zookeeper.started', 'leadership.is_leader', 'zkpeer.joined', + 'zkpeer.departed') +def check_cluster_departed(zkpeer, zkpeer_departed): + ''' + Wrapper around check_cluster. + + Together with check_cluster, implements the following logic: + + "Run this when zkpeer.joined and zkpeer departed, or zkpeer.joined + and not zkpeer.departed" + + ''' + check_cluster(zkpeer) + + +@when('leadership.changed.restart_queue', 'zkpeer.joined') +def restart_for_quorum(zkpeer): + ''' + If we're the next node in the restart queue, restart, and then + inform the leader that we've restarted. (If we are the leader, + remove ourselves from the queue, and update the leadership data.) + + ''' + private_address = hookenv.unit_get('private-address') + queue = json.loads(leader_get('restart_queue') or '[]') + + if not queue: + # Everything has restarted. + return + + if private_address == queue[0]: + # It's our turn to restart. + _restart_zookeeper('rolling restart for quorum update') + if is_state('leadership.is_leader'): + queue = queue[1:] + hookenv.log('Leader updating restart queue: {}'.format(queue)) + leader_set(restart_queue=json.dumps(queue)) else: - note = '' - message = ( - "number of zk peers has changed -- you must use " - "the 'restart' action to perform a rolling restart to " - "update your cluster{}".format(note)) - hookenv.status_set('active', message) + zkpeer.inform_restart() -@when('zookeeper.started', 'zookeeper.joined') -def serve_client(client): - config = Zookeeper().dist_config - port = config.port('zookeeper') - rest_port = config.port('zookeeper-rest') # TODO: add zookeeper REST - client.send_port(port, rest_port) +@when('leadership.is_leader', 'zkpeer.joined') +def update_restart_queue(zkpeer): + ''' + If a Zookeeper node has restarted as part of a rolling restart, + pop it off of the queue. + + ''' + queue = json.loads(leader_get('restart_queue') or '[]') + if not queue: + return + + restarted_nodes = _ip_list(zkpeer.restarted_nodes()) + new_queue = [node for node in queue if node not in restarted_nodes] + + if new_queue != queue: + hookenv.log('Leader updating restart queue: {}'.format(queue)) + leader_set(restart_queue=json.dumps(new_queue)) diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy index c587077d22..07f76cce64 100755 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy @@ -47,10 +47,6 @@ class TestDeploy(unittest.TestCase): ''' self.assertEqual(3, len(self.d.sentry['zookeeper'])) - # Perform a rolling restart - for unit in self.d.sentry['zookeeper']: - action_id = unit.run_action("restart") - amulet.actions.get_action_output(action_id) # Wait for restart to complete # Verify that everything worked. for unit in self.d.sentry['zookeeper']: diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address index cf4641e6a6..9c767f9267 100755 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address @@ -18,6 +18,7 @@ import unittest import re import amulet +import subprocess DEPLOY_TIMEOUT = 1800 CONFIG_TIMEOUT = 900 @@ -61,8 +62,12 @@ class TestBindClientPort(unittest.TestCase): raise Exception( "Could not find any interface on the unit that matched my " "criteria.") - - self.d.configure('zookeeper', {'network_interface': network_interface}) + # self.d.configure broken due to change in juju api. TODO: + # switch this out when fixed. + subprocess.check_call( + ['juju', 'config', 'zookeeper', 'network_interface={}'.format( + network_interface)]) + #self.d.configure('zookeeper', {'network_interface': network_interface}) self.d.sentry.wait_for_messages( {'zookeeper': 'updating network interface'}, timeout=CONFIG_TIMEOUT) self.d.sentry.wait_for_messages( From 305ffd9b9c21188c2a1764a2a28b2e1580a25075 Mon Sep 17 00:00:00 2001 From: Kevin W Monroe Date: Mon, 10 Oct 2016 20:53:22 +0000 Subject: [PATCH 5/5] refresh for juju2 and base layer smoke-test changes - remove LICENSE (available in the base source tree) - update README with 2.0 instructions and consistent bigtop charm messaging - add explicit smoke-test (rather than rely on bigtop base boiler plate) - update layer repo to point upstream; inherit tags from bigtop base layer - update amulet tests to detect smoke-test failures --- .../charm/zookeeper/layer-zookeeper/LICENSE | 177 ------------------ .../charm/zookeeper/layer-zookeeper/README.md | 177 +++++++++++++----- .../zookeeper/layer-zookeeper/actions.yaml | 4 +- .../layer-zookeeper/actions/smoke-test | 43 +++++ .../zookeeper/layer-zookeeper/layer.yaml | 10 +- .../zookeeper/layer-zookeeper/metadata.yaml | 13 +- .../zookeeper/layer-zookeeper/tests/01-deploy | 11 +- .../layer-zookeeper/tests/10-bind-address | 7 +- 8 files changed, 190 insertions(+), 252 deletions(-) delete mode 100644 bigtop-packages/src/charm/zookeeper/layer-zookeeper/LICENSE create mode 100755 bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions/smoke-test diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/LICENSE b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/LICENSE deleted file mode 100644 index f433b1a53f..0000000000 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/LICENSE +++ /dev/null @@ -1,177 +0,0 @@ - - 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/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md index 9a872320da..69aa650af2 100644 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/README.md @@ -14,104 +14,179 @@ See the License for the specific language governing permissions and limitations under the License. --> -## Overview +# Overview + Apache ZooKeeper is a high-performance coordination service for distributed applications. It exposes common services such as naming, configuration -management, synchronization, and group services in a simple interface so you -don't have to write them from scratch. You can use it off-the-shelf to -implement consensus, group management, leader election, and presence protocols. +management, synchronization, and group services in a simple interface. Use it +off-the-shelf to implement consensus, group management, leader election, and +presence protocols. + +This charm provides the Zookeeper component of the [Apache Bigtop][] platform. + +[Apache Bigtop]: http://bigtop.apache.org/ + + +# Deploying + +A working Juju installation is assumed to be present. If Juju is not yet set +up, please follow the [getting-started][] instructions prior to deploying this +charm. -## Usage -Deploy a Zookeeper unit. With only one unit, the service will be running in +Deploy a Zookeeper unit. With only one unit, the application will be running in `standalone` mode: - juju deploy zookeeper zookeeper + juju deploy zookeeper +## Network-Restricted Environments +Charms can be deployed in environments with limited network access. To deploy +in this environment, configure a Juju model with appropriate proxy and/or +mirror options. See [Configuring Models][] for more information. -## Scaling -Running ZooKeeper in `standalone` mode is convenient for evaluation, some -development, and testing. But in production, you should run ZooKeeper in -`replicated` mode. A replicated group of servers in the same application is -called a quorum, and in `replicated` mode, all servers in the quorum have -copies of the same configuration file. +[getting-started]: https://jujucharms.com/docs/stable/getting-started +[Configuring Models]: https://jujucharms.com/docs/stable/models-config -In order to add new Zookeeper servers to the quorum, simply add units -as you usually would in juju: +## Configuring Network Interfaces +In some network environments, zookeeper may need to be restricted to +listen for incoming connections on a specific network interface +(e.g.: for security reasons). To do so, configure zookeeper with either a +network interface name or a CIDR range specifying a subnet. For example: - juju add-unit -n 2 zookeeper + juju config zookeeper network_interface=eth0 + juju config zookeeper network_interface=10.0.2.0/24 -The Zookeeper nodes will then automatically perform a rolling restart, -in order to update the Zookeeper quorum without losing any jobs in -progress. Once the rolling restart has completed, all of your -Zookeeper nodes should be in the following state: +> **Note**: The above assumes Juju 2.0 or greater. If using an earlier version +of Juju, the syntax is `juju set-config zookeeper network_interface=eth0`. - ready (n zk nodes) +Each zookeeper unit in the cluster will lookup the IP address of that +network interface, or find the first network interface with an IP +address in the specified subnet, and bind Zookeeper to that address. -(Where 'n' is the total number of Zookeeper nodes in your quorum.) +If a mistake is made and an invalid name for the network interface is +configured, recover by re-configuring with the correct name and then +run "juju resolved" on any failed units: + juju config zookeeper network_interface=eth0 + juju resolved zookeeper/0 -## Network Interfaces +> **Note**: The above assumes Juju 2.0 or greater. If using an earlier version +of Juju, the syntax is `juju set-config zookeeper network_interface=eth0; +juju resolved -r zookeeper/0`. -In some network environments, you may want to restrict your Zookeepers -to listen for client connections on a specific network interface (for -example, for security reasons). To do so, you may pass either a -network interface name or a CIDR range specifying a subnet to the -``network_interface`` configuration variable. For example: +To go back to listening on all interfaces, configure zookeeper with +`network_interface=0.0.0.0`: - juju set-config zookeeper network_interface=eth0 + juju config zookeeper network_interface=0.0.0.0 -or +> **Note**: The above assumes Juju 2.0 or greater. If using an earlier version +of Juju, the syntax is `juju set-config zookeeper network_interface=0.0.0.0`. - juju set-config zookeeper network_interface=10.0.2.0/24 -Each Zookeeper machine in your cluster will lookup the IP address of that -network interface, or find the first network interface with an IP -address in the specified subnet, and bind Zookeeper to that address. +# Verifying + +## Status +Apache Bigtop charms provide extended status reporting to indicate when they +are ready: + + juju status + +This is particularly useful when combined with `watch` to track the on-going +progress of the deployment: + + watch -n 2 juju status + +The message column will provide information about a given unit's state. +This charm is ready for use once the status message indicates that it is +ready. -If you make a mistake, and pass an invalid name for a network -interface, you may recover by passing the correct name to set-config, -and then running "juju resolved" on each unit: +## Smoke Test +This charm provides a `smoke-test` action that can be used to verify the +application is functioning as expected. Run the action as follows: - juju set-config zookeeper network_interface=eth0 - juju resolved -r zookeeper/0 + juju run-action zookeeper/0 smoke-test -If you want to go back to listening on any network interface on the -machine, simply pass ``0.0.0.0`` to ``network_interface``. +> **Note**: The above assumes Juju 2.0 or greater. If using an earlier version +of Juju, the syntax is `juju action do zookeeper/0 smoke-test`. - juju set-config zookeeper network_interface=0.0.0.0 +Watch the progress of the smoke test actions with: + watch -n 2 juju show-action-status -## Test the deployment -Test if the Zookeeper service is running by using the `zkServer.sh` script: +> **Note**: The above assumes Juju 2.0 or greater. If using an earlier version +of Juju, the syntax is `juju action status`. - juju run --service=zookeeper '/usr/lib/zookeeper/bin/zkServer.sh status' +Eventually, the action should settle to `status: completed`. If it +reports `status: failed`, the application is not working as expected. Get +more information about a specific smoke test with: + + juju show-action-output + +> **Note**: The above assumes Juju 2.0 or greater. If using an earlier version +of Juju, the syntax is `juju action fetch `. + +## Utilities +This charm includes Zookeeper command line utilities that can also be used to +verify that the application is running as expected. Check the status of the +Zookeeper daemon with `zkServer.sh`: + + juju run --application=zookeeper '/usr/lib/zookeeper/bin/zkServer.sh status' A successful deployment will report the service mode as either `standalone` (if only one Zookeeper unit has been deployed) or `leader` / `follower` (if a Zookeeper quorum has been formed). -## Integrate Zookeeper into another charm -1) Add following lines to your charm's metadata.yaml: +# Scaling + +Running ZooKeeper in `standalone` mode is convenient for evaluation, some +development, and testing. In production, however, ZooKeeper should be run in +`replicated` mode. A replicated group of servers in the same application is +called a quorum, and in `replicated` mode, all servers in the quorum have +copies of the same configuration file. + +In order to add new Zookeeper servers to the quorum, simply add more units. +For example, add two more zookeeper units with: + + juju add-unit -n 2 zookeeper + +The Zookeeper nodes will automatically perform a rolling restart to update the +Zookeeper quorum without losing any jobs in progress. Once the rolling restart +has completed, all of the Zookeeper nodes should report the following status: + + ready (n zk nodes) + +(Where 'n' is the total number of Zookeeper nodes in your quorum.) + + +# Integrating + +To integrate Zookeeper into solutions with other charms, update the charms +that require Zookeeper as follows: + +1) Add following lines to `metadata.yaml`: requires: zookeeper: interface: zookeeper -2) Add a `zookeeper-relation-changed` hook to your charm. Example contents: +2) Add a `zookeeper-relation-changed` hook. Example contents: from charmhelpers.core.hookenv import relation_get ZK_hostname = relation_get('private-address') ZK_port = relation_get('port') +# Contact Information + +- -## Contact Information -[bigdata@lists.ubuntu.com](mailto:bigdata@lists.ubuntu.com) +# Resources -## Help +- [Apache Bigtop](http://bigtop.apache.org/) home page +- [Apache Bigtop mailing lists](http://bigtop.apache.org/mail-lists.html) - [Apache Zookeeper home page](https://zookeeper.apache.org/) - [Apache Zookeeper issue tracker](https://issues.apache.org/jira/browse/ZOOKEEPER) +- [Juju Bigtop charms](https://jujucharms.com/q/apache/bigtop) - [Juju mailing list](https://lists.ubuntu.com/mailman/listinfo/juju) - [Juju community](https://jujucharms.com/community) diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions.yaml b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions.yaml index 10c5a7813e..a4e110fa1e 100644 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions.yaml +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions.yaml @@ -1,2 +1,4 @@ restart: - description: stop and start a Zookeeper server. + description: Restart the Zookeeper server daemon. +smoke-test: + description: Run an Apache Bigtop smoke test. diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions/smoke-test b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions/smoke-test new file mode 100755 index 0000000000..37264cfaa4 --- /dev/null +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/actions/smoke-test @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 sys +sys.path.append('lib') + +from charmhelpers.core import hookenv +from charms.layer.apache_bigtop_base import Bigtop +from charms.reactive import is_state + + +def fail(msg, output=None): + if output: + hookenv.action_set({'output': output}) + hookenv.action_fail(msg) + sys.exit() + +if not is_state('zookeeper.started'): + fail('Charm is not yet ready to run the Bigtop smoke test(s)') + +# Bigtop smoke test components +smoke_components = ['zookeeper'] + +bigtop = Bigtop() +result = bigtop.run_smoke_tests(smoke_components) +if result == 'success': + hookenv.action_set({'outcome': result}) +else: + fail('{} smoke tests failed'.format(smoke_components), result) diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml index 335c441ad4..7f6ee76fe2 100644 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/layer.yaml @@ -1,4 +1,4 @@ -repo: git@github.com:juju-solutions/bigtop.git +repo: https://github.com/apache/bigtop/tree/master/bigtop-packages/src/charm/zookeeper/layer-zookeeper includes: - 'layer:apache-bigtop-base' - 'layer:leadership' @@ -7,17 +7,9 @@ includes: options: apache-bigtop-base: 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. - # If adding a new port here, you will need to update - # charmhelpers.contrib.bigdata.handlers.apache or hooks/callbacks.py - # to ensure that it is supported. zookeeper-rest: port: 9998 exposed_on: 'zookeeper' zookeeper: port: 2181 exposed_on: 'zookeeper' - bigtop_smoketest_components: ['zookeeper'] diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml index 8a9f769c6c..d9818d792b 100644 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/metadata.yaml @@ -2,13 +2,12 @@ name: zookeeper maintainer: Juju Big Data summary: High-performance coordination service for distributed applications description: | - Apache ZooKeeper is a centralized, reliable, service for maintaining - configuration information, naming, providing distributed - synchronization, and group services. All of these kinds of services - are used in some form or another by distributed applications. - In order to install and configure Apache HBase and other Hadoop ecosystem - components, you must start the ZooKeeper service. -tags: ["bigdata", "hadoop", "apache"] + Apache ZooKeeper is a centralized, reliable application for maintaining + configuration information, naming, synchronization, and group services. All + of these kinds of services are used in some form or another by distributed + applications. In order to install and configure Apache HBase and other Hadoop + ecosystem components, you need ZooKeeper. +tags: [] provides: zookeeper: interface: zookeeper diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy index 07f76cce64..744a71fb8f 100755 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/01-deploy @@ -57,17 +57,18 @@ class TestDeploy(unittest.TestCase): self.assertTrue("leader" in output or "follower" in output) def test_smoke(self): - ''' - Run smoke tests. - ''' + """Validates Zookeeper using the Bigtop 'zookeeper' smoke test.""" smk_uuids = [] for unit in self.d.sentry['zookeeper']: smk_uuids.append(unit.action_do("smoke-test")) for smk_uuid in smk_uuids: - output = self.d.action_fetch(smk_uuid, full_output=True) - assert "completed" in output['status'] + result = self.d.action_fetch(smk_uuid, full_output=True) + # zookeeper smoke-test sets outcome=success on success + if (result['outcome'] != "success"): + error = "Zookeeper smoke-test failed" + amulet.raise_status(amulet.FAIL, msg=error) if __name__ == '__main__': unittest.main() diff --git a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address index 9c767f9267..66e42eb005 100755 --- a/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address +++ b/bigtop-packages/src/charm/zookeeper/layer-zookeeper/tests/10-bind-address @@ -81,8 +81,11 @@ class TestBindClientPort(unittest.TestCase): # Verify that smoke tests still run smk_uuid = self.unit.action_do("smoke-test") - output = self.d.action_fetch(smk_uuid, full_output=True) - assert "completed" in output['status'] + result = self.d.action_fetch(smk_uuid, full_output=True) + # zookeeper smoke-test sets outcome=success on success + if (result['outcome'] != "success"): + error = "Zookeeper smoke-test failed" + amulet.raise_status(amulet.FAIL, msg=error) @unittest.skip( 'Broken handling of 0.0.0.0 bindings upstream, in Zookeeper project.')