---
title: Charmhub | Deploy Charmed Apache Zookeeper using Charmhub - The Open Operator
  Collection
description: Deploy the latest version of Charmed Apache Zookeeper on any cloud.
url: https://charmhub.io/zookeeper/libraries/cluster
---

# Charmed Apache Zookeeper

[Canonical](https://charmhub.io/publisher/data-platform "View all packages from Canonical")

* [Canonical](https://charmhub.io/publisher/data-platform "View all packages from Canonical")
* [Databases](https://charmhub.io/?filter=databases)

Platform:

22.04

20.04

18.04

16.04

3/stable 163

```
juju deploy zookeeper --channel 3/stable
```

[Learn to deploy on juju >](https://juju.is/docs/juju/manage-applications)

[Toggle side navigation](https://charmhub.io/zookeeper/libraries/cluster#drawer)

## charms.zookeeper.v0.cluster

* [*Docstrings*Docstrings](https://charmhub.io/zookeeper/libraries/cluster)
  [*Code*Source code](https://charmhub.io/zookeeper/libraries/cluster/source-code)
* + Download

    Fetch library

    ```
    charmcraft fetch-lib charms.zookeeper.v0.cluster
    ```

    [Download cluster.py](https://charmhub.io/zookeeper/libraries/cluster/download)
  + *Last updated* 08 Jul 2022
  + *Revision* Library version 0.1

ZooKeeperCluster class and methods.

`ZooKeeperCluster` is a general-purpose handler for managing the ZooKeeper peer-relation data,
and scale-up/down orchestration.

Exposed attributes include a reference to the `cluster` peer relation for ZooKeeper, as well as wrappers
for grabbing units that have started the ZooKeeper service, found from the peer unit data.
Exposed methods include the setting of passwords for the cluster, ensuring units start up in increasing order
with the correct servers config, and handling the adding/removing of members from the ZooKeeper quorum.

Instances of `ZooKeeperCluster` are to be created during the `__init__` for the target `Charm`.
or from another library. It does not set any relation data itself, and as such is reliant on the calling `Charm`
to set required relation data where necessary during life-cycle events.
It maintains a `status` attribute for passing success/failure of called methods back to the calling `Charm`.

Example minimal usage for `ZooKeeperCluster`:

```
class ZooKeeperCharm(CharmBase):
    def __init__(self, *args):
        super().__init__(*args)
        self.cluster =  ZooKeeperCluster(self)

        self.framework.observe(getattr(self.on, "install"), self._on_install)
        self.framework.observe(getattr(self.on, "start"), self._on_start)
        self.framework.observe(
            getattr(self.on, "leader_elected"), self._on_cluster_relation_updated
        )
        self.framework.observe(
            getattr(self.on, "cluster_relation_changed"), self._on_cluster_relation_updated
        )
        self.framework.observe(
            getattr(self.on, "cluster_relation_joined"), self._on_cluster_relation_updated
        )
        self.framework.observe(
            getattr(self.on, "cluster_relation_departed"), self._on_cluster_relation_updated
        )

    def _on_install(self, event):
        unit_myid = self.cluster.get_unit_id(self.unit) + 1

        # write_unit_id(id=unit_myid)

    def _on_start(self, event):
        # setting cluster passwords during first deployment
        if self.unit.is_leader():
            self.cluster.relation.data[self.app].update({"super_password": self.cluster.generate_password()})
            self.cluster.relation.data[self.app].update({"sync_password": self.cluster.generate_password()})

        # checking passwords have been set by leader on other units, in case they started first
        if not self.cluster.passwords_set():
            event.defer()
            return

        # units must be started in increasing unit.id order
        try:
            servers, unit_config = self.cluster.ready_to_start(self.unit)
        except (NotUnitTurnError, UnitNotFoundError, NoPasswordError) as e:
            self.unit.status = self.cluster.status
            event.defer()
            return

        # start_zookeeper_service()

        # set useful metadata for each unit
        self.cluster.relation.data[self.unit].update(unit_config)

        # NECESSARY - set data so that subsequent `self.cluster` commands pick up this unit as a 'started' one
        self.cluster.relation.data[self.unit].update({"state": "started"})

    def _on_cluster_relation_updated(self, event):
        if not self.unit.is_leader():
            return

        # units need to exist in the app data to be iterated through for next_turn
        for unit in self.cluster.started_units:
            unit_id = self.cluster.get_unit_id(unit)
            current_value = self.cluster.relation.data[self.app].get(str(unit_id), None)

            # sets to "added" for init quorum leader, if not already exists
            # may already exist if during the case of a failover of unit 0
            if unit_id == 0:
                self.cluster.relation.data[self.app].update(
                    {str(unit_id): current_value or "added"}
                )

        if not self.cluster.passwords_set:
            event.defer()
            return

        # adds + removes members for all self-confirmed started units
        updated_servers = self.cluster.update_cluster()

        # either Active if successful, else Maintenance
        self.unit.status = self.cluster.status

        if self.cluster.status == ActiveStatus():
            self.cluster.relation.data[self.app].update(updated_servers)
        else:
            # in the event some unit wasn't started/ready
            event.defer()
            return
```

---

Index

* [class UnitNotFoundError](https://charmhub.io/zookeeper/libraries/cluster#unitnotfounderror)
* [class NotUnitTurnError](https://charmhub.io/zookeeper/libraries/cluster#notunitturnerror)
* [class NoPasswordError](https://charmhub.io/zookeeper/libraries/cluster#nopassworderror)
* [class ZooKeeperCluster](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster)
* + [def \_\_init\_\_(
    self,
    charm,
    client\_port,
    server\_port,
    election\_port)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-__init__)
* + [def relation(
    self)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-relation)
* + [def peer\_units(
    self)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-peer_units)
* + [def started\_units(
    self)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-started_units)
* + [def active\_hosts(
    self)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-active_hosts)
* + [def active\_servers(
    self)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-active_servers)
* + [def get\_unit\_id(
    unit)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-get_unit_id)
* + [def get\_unit\_from\_id(
    self,
    unit\_id)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-get_unit_from_id)
* + [def unit\_config(
    self,
    unit,
    state,
    role)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-unit_config)
* + [def update\_cluster(
    self)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-update_cluster)
* + [def ready\_to\_start(
    self,
    unit)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-ready_to_start)
* + [def generate\_password(
    )](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-generate_password)
* + [def passwords(
    self)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-passwords)
* + [def passwords\_set(
    self)](https://charmhub.io/zookeeper/libraries/cluster#zookeepercluster-passwords_set)

#### class UnitNotFoundError

Description

A desired unit isn't yet found in the relation data. None

#### class NotUnitTurnError

Description

A desired unit isn't next in line to start safely. None

#### class NoPasswordError

Description

Required passwords not yet set in the app data. None

#### class ZooKeeperCluster

Handler for managing the ZK peer-relation.

Description

Mainly for managing scale-up/down orchestration

Methods

ZooKeeperCluster.
\_\_init\_\_(

*self*

,
charm: CharmBase

,
client\_port: int

,
server\_port: int

,
election\_port: int
)

ZooKeeperCluster.
relation(

*self*
)

Relation property to be used by both the instance and charm.

Returns

The peer relation instance

ZooKeeperCluster.
peer\_units(

*self*
)

Grabs all units in the current peer relation, including the running unit.

Returns

Set of units in the current peer relation, including the running unit

ZooKeeperCluster.
started\_units(

*self*
)

Checks peer relation units for whether they've started the ZK service.

Returns

Set of units with unit data "state" == "started". Shows only those units
currently found related to the current unit.

Description

Such units are ready to join the ZK quorum if they haven't already.

ZooKeeperCluster.
active\_hosts(

*self*
)

Grabs all the hosts of the started units.

Returns

List of hosts for started units

ZooKeeperCluster.
active\_servers(

*self*
)

Grabs all the server strings of the started units.

Returns

List of ZK server strings for started units

ZooKeeperCluster.
get\_unit\_id(

unit: Unit
)

Grabs the unit's ID as defined by Juju.

Arguments

unit

The target `Unit`

Returns

The Juju unit ID for the unit.
e.g `zookeeper/0` -> `0`

ZooKeeperCluster.
get\_unit\_from\_id(

*self*

,
unit\_id: int
)

Grabs the corresponding Unit for a given Juju unit ID

Arguments

unit\_id

The target unit id

Returns

The target `Unit`

ZooKeeperCluster.
unit\_config(

*self*

,
unit

,
state: str

,
role: str
)

Builds a collection of data useful for ZK for a given unit.

Arguments

unit

The target `Unit`, either explicitly or from it's Juju unit ID

state

The desired output state. "ready" or "started"

role

The ZK role for the unit. Default = "participant"

Returns

The generated config for the given unit.
e.g for unit zookeeper/1:

{
"host": 10.121.23.23,
"server\_string": "server.1=host:server\_port:election\_port:role;localhost:clientport",
"server\_id": "2",
"unit\_id": "1",
"unit\_name": "zookeeper/1",
"state": "ready",
}

ZooKeeperCluster.
update\_cluster(

*self*
)

Adds and removes members from the current ZK quorum.

Returns

A mapping of Juju unit IDs and updated state for changed units
To be used in updating the app data
e.g {"0": "added", "1": "removed"}

Description

To be ran by the Juju leader.

After grabbing all the "started" units that the leader can see in the peer relation unit data.
Removes members not in the quorum anymore (i.e `relation_departed`/`leader_elected` event)
Adds new members to the quorum (i.e `relation_joined` event).

ZooKeeperCluster.
ready\_to\_start(

*self*

,
unit
)

Decides whether a unit should start the ZK service, and with what configuration.

Arguments

unit

the `Unit` or Juju unit ID to evaluate startability

Returns

`servers`

a new-line delimited string of servers to add to a config file
`unit_config`: a mapping of configuration for the given unit to be added to unit data

ZooKeeperCluster.
generate\_password(
)

Creates randomized string for use as app passwords.

Returns

String of 32 randomized letter+digit characters

ZooKeeperCluster.
passwords(

*self*
)

Gets the current super+sync passwords from the app relation data.

Returns

Tuple of super\_password, sync\_password

ZooKeeperCluster.
passwords\_set(

*self*
)

Checks that the two desired passwords are in the app relation data.

Returns

True if both passwords have been set. Otherwise False
