Charmed Zookeeper

Channel Revision Published Runs on
latest/stable 53 29 Nov 2021
Ubuntu 16.04
latest/candidate 56 29 Nov 2021
Ubuntu 16.04
latest/beta 56 29 Nov 2021
Ubuntu 16.04
latest/edge 98 20 Apr 2023
Ubuntu 22.04 Ubuntu 20.04 Ubuntu 18.04 Ubuntu 16.04
latest/edge 85 21 Oct 2022
Ubuntu 22.04 Ubuntu 20.04 Ubuntu 18.04 Ubuntu 16.04
latest/edge 65 09 Feb 2022
Ubuntu 22.04 Ubuntu 20.04 Ubuntu 18.04 Ubuntu 16.04
latest/edge 52 29 Nov 2021
Ubuntu 22.04 Ubuntu 20.04 Ubuntu 18.04 Ubuntu 16.04
3/stable 149 23 Oct 2024
Ubuntu 22.04
3/candidate 149 21 Oct 2024
Ubuntu 22.04
3/beta 149 21 Oct 2024
Ubuntu 22.04
3/edge 152 17 Dec 2024
Ubuntu 22.04
juju deploy zookeeper --channel 3/stable
Show information

Platform:

Ubuntu
22.04 20.04 18.04 16.04

charms.zookeeper.v0.cluster

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

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