diff --git a/src/charm.py b/src/charm.py index 97f673c..2ee1aa2 100755 --- a/src/charm.py +++ b/src/charm.py @@ -101,7 +101,7 @@ def _upgrade_charm(self, event: UpgradeCharmEvent) -> None: self._store_certificates() # NOTE: This is the case of a single unit deployment. If that's the case, the charm - # doesn't need to check for failovers or figure out who the master is. + # doesn't need to check for failovers or figure out who the primary is. if not self._peers.units: # NOTE: pod restart or charm upgrade can come along with pod IP changes, and # during those process, the leader-elected and any relation events are not emitted. @@ -126,9 +126,9 @@ def _upgrade_charm(self, event: UpgradeCharmEvent) -> None: # unit is not a leader, add a key to the application databag so peer_relation_changed # triggers for the leader unit and application databag is updated. if self.unit.is_leader(): - info = self.sentinel.get_master_info(host=k8s_host) - logger.debug(f"Master info: {info}") - logger.info(f"Unit {self.unit.name} updating master info to {info['ip']}") + info = self.sentinel.get_primary_info(host=k8s_host) + logger.debug(f"primary info: {info}") + logger.info(f"Unit {self.unit.name} updating primary info to {info['ip']}") self._peers.data[self.app][LEADER_HOST_KEY] = info["ip"] else: relations = self.model.relations[REDIS_REL_NAME] @@ -152,7 +152,7 @@ def _leader_elected(self, event) -> None: self._peers.data[self.app][SENTINEL_PASSWORD_KEY] = self._generate_password() # NOTE: if current_primary is not set yet, the application is being deployed for the # first time. Otherwise, we check for failover in case previous juju leader was redis - # master as well. + # primary as well. if self.current_primary is None: logger.info( "Initial replication, setting leader-host to {}".format(self.unit_pod_hostname) @@ -160,7 +160,7 @@ def _leader_elected(self, event) -> None: self._peers.data[self.app][LEADER_HOST_KEY] = self.unit_pod_hostname else: # TODO extract to method shared with relation_departed - self._update_application_master() + self._update_application_primary() self._update_quorum() if not self._is_failover_finished(): logger.info("Failover didn't finish, deferring") @@ -199,21 +199,21 @@ def _update_status(self, _) -> None: """ logger.info("Beginning update_status") if self.unit.is_leader(): - self._update_application_master() + self._update_application_primary() self._redis_check() def _peer_relation_changed(self, event): """Handle relation for joining units.""" - if not self._master_up_to_date(): - logger.error(f"Unit {self.unit.name} doesn't agree on tracked master") + if not self._primary_up_to_date(): + logger.error(f"Unit {self.unit.name} doesn't agree on tracked primary") if not self._is_failover_finished(): logger.info("Failover didn't finish, deferring") event.defer() return if self.unit.is_leader(): - # Update who the current master is - self._update_application_master() + # Update who the current primary is + self._update_application_primary() # (DEPRECATE) If legacy relation exists, layer might need to be # reconfigured to remove auth @@ -247,8 +247,8 @@ def _peer_relation_departed(self, event): if not self.unit.is_leader(): return - if not self._master_up_to_date(): - self._update_application_master() + if not self._primary_up_to_date(): + self._update_application_primary() # Quorum is updated beforehand, since removal of more units than current majority # could lead to the cluster never reaching quorum. @@ -352,7 +352,7 @@ def _redis_extra_flags(self) -> str: extra_flags = [ f"--requirepass {self._get_password()}", "--bind 0.0.0.0", - f"--masterauth {self._get_password()}", + f"--primaryauth {self._get_password()}", f"--replica-announce-ip {self.unit_pod_hostname}", ] @@ -376,7 +376,7 @@ def _redis_extra_flags(self) -> str: f"--tls-ca-cert-file {self._storage_path}/ca.crt", ] - # Check that current unit is master + # Check that current unit is primary if self.current_primary != self.unit_pod_hostname: extra_flags += [f"--replicaof {self.current_primary} {REDIS_PORT}"] @@ -456,7 +456,7 @@ def unit_pod_hostname(self, name="") -> str: @property def current_primary(self) -> Optional[str]: - """Get the current master.""" + """Get the current primary.""" return self._peers.data[self.app].get(LEADER_HOST_KEY) def _valid_app_databag(self) -> bool: @@ -574,13 +574,13 @@ def _redis_client(self, hostname="localhost") -> Redis: finally: client.close() - def _master_up_to_date(self, host="0.0.0.0") -> bool: - """Check if stored master is the same as sentinel tracked. + def _primary_up_to_date(self, host="0.0.0.0") -> bool: + """Check if stored primary is the same as sentinel tracked. Returns: host: string to connect to sentinel """ - info = self.sentinel.get_master_info(host=host) + info = self.sentinel.get_primary_info(host=host) if info is None: return False elif (info["ip"] == self.current_primary) and ("s_down" not in info["flags"]): @@ -588,19 +588,19 @@ def _master_up_to_date(self, host="0.0.0.0") -> bool: return False - def _update_application_master(self) -> None: - """Use Sentinel to update the current master hostname.""" - info = self.sentinel.get_master_info() - logger.debug(f"Master info: {info}") + def _update_application_primary(self) -> None: + """Use Sentinel to update the current primary hostname.""" + info = self.sentinel.get_primary_info() + logger.debug(f"primary info: {info}") if info is None: - logger.warning("Could not update current master") + logger.warning("Could not update current primary") return - logger.info(f"Unit {self.unit.name} updating master info to {info['ip']}") + logger.info(f"Unit {self.unit.name} updating primary info to {info['ip']}") self._peers.data[self.app][LEADER_HOST_KEY] = info["ip"] def _sentinel_failover(self, departing_unit_name: str) -> None: - """Try to failover the current master. + """Try to failover the current primary. This method should only be called from juju leader, to avoid more than one sentinel sending failovers concurrently. @@ -628,7 +628,7 @@ def _is_failover_finished(self, host="localhost") -> bool: True if failover is finished, false otherwise """ logger.warning("Checking if failover is finished.") - info = self.sentinel.get_master_info(host=host) + info = self.sentinel.get_primary_info(host=host) if info is None: logger.warning("Could not check failover status") return False diff --git a/src/sentinel.py b/src/sentinel.py index 1243f1c..7ad21cb 100644 --- a/src/sentinel.py +++ b/src/sentinel.py @@ -108,12 +108,12 @@ def _render_sentinel_config_file(self) -> None: # render the template file with the correct values. rendered = template.render( hostname=self.charm.unit_pod_hostname, - master_name=self.charm._name, + primary_name=self.charm._name, sentinel_port=SENTINEL_PORT, - redis_master=self.charm.current_primary, + redis_primary=self.charm.current_primary, redis_port=REDIS_PORT, quorum=self.expected_quorum, - master_password=self.charm._get_password(), + primary_password=self.charm._get_password(), sentinel_password=self.charm.get_sentinel_password(), ) self._copy_file(SENTINEL_CONFIG_PATH, rendered, "sentinel") @@ -147,17 +147,17 @@ def _copy_file(self, path: str, rendered: str, container: str) -> None: group="redis", ) - def get_master_info(self, host="0.0.0.0") -> Optional[dict]: - """Connect to sentinel and return the current master.""" + def get_primary_info(self, host="0.0.0.0") -> Optional[dict]: + """Connect to sentinel and return the current primary.""" with self.sentinel_client(host) as sentinel: try: - # get sentinel info about the master - master_info = sentinel.execute_command(f"SENTINEL MASTER {self.charm._name}") + # get sentinel info about the primary + primary_info = sentinel.execute_command(f"SENTINEL primary {self.charm._name}") - # NOTE: master info from redis comes like a list: + # NOTE: primary info from redis comes like a list: # ['key1', 'value1', 'key2', 'value2', ...] # this creates a dictionary in a more readable form. - return dict(zip(master_info[::2], master_info[1::2])) + return dict(zip(primary_info[::2], primary_info[1::2])) except (ConnectionError, TimeoutError) as e: logger.error("Error when connecting to sentinel: {}".format(e)) diff --git a/templates/sentinel.conf.j2 b/templates/sentinel.conf.j2 index 413e7ea..24579d3 100644 --- a/templates/sentinel.conf.j2 +++ b/templates/sentinel.conf.j2 @@ -1,11 +1,11 @@ port {{ sentinel_port }} -sentinel monitor {{ master_name }} {{ redis_master }} {{ redis_port }} {{ quorum }} -sentinel down-after-milliseconds {{ master_name }} 5000 -sentinel failover-timeout {{ master_name }} 30000 -sentinel parallel-syncs {{ master_name }} 1 +sentinel monitor {{ primary_name }} {{ redis_primary }} {{ redis_port }} {{ quorum }} +sentinel down-after-milliseconds {{ primary_name }} 5000 +sentinel failover-timeout {{ primary_name }} 30000 +sentinel parallel-syncs {{ primary_name }} 1 -{% if master_password != None %} -sentinel auth-pass {{ master_name }} {{ master_password }} +{% if primary_password != None %} +sentinel auth-pass {{ primary_name }} {{ primary_password }} {% endif %} requirepass {{ sentinel_password }} diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 42eb2ae..186a860 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -113,7 +113,7 @@ async def test_sentinels_expected(ops_test: OpsTest): logger.info("retrieved sentinel password for %s: %s", APP_NAME, password) sentinel = Redis(address, password=password, port=26379) - sentinels_connected = sentinel.info("sentinel")["master0"]["sentinels"] + sentinels_connected = sentinel.info("sentinel")["primary0"]["sentinels"] assert sentinels_connected == NUM_UNITS diff --git a/tests/integration/test_scaling.py b/tests/integration/test_scaling.py index 469a6e1..0530cc8 100644 --- a/tests/integration/test_scaling.py +++ b/tests/integration/test_scaling.py @@ -50,7 +50,7 @@ async def test_build_and_deploy(ops_test: OpsTest): assert ops_test.model.applications[APP_NAME].units[0].workload_status == "active" -@pytest.mark.run(before="test_scale_down_departing_master") +@pytest.mark.run(before="test_scale_down_departing_primary") async def test_scale_up_replication_after_failover(ops_test: OpsTest): """Trigger a failover and scale up the application, then test replication status.""" unit_map = await get_unit_map(ops_test) @@ -60,7 +60,7 @@ async def test_scale_up_replication_after_failover(ops_test: OpsTest): leader_address = await get_address(ops_test, unit_num=leader_num) password = await get_password(ops_test, leader_num) - # Set some key on the master replica. + # Set some key on the primary replica. leader_client = Redis(leader_address, password=password) leader_client.set("testKey", "myValue") leader_client.close() @@ -68,14 +68,14 @@ async def test_scale_up_replication_after_failover(ops_test: OpsTest): sentinel_password = await get_sentinel_password(ops_test) logger.info("retrieved sentinel password for %s: %s", APP_NAME, password) - # Trigger a master failover + # Trigger a primary failover sentinel = Redis(leader_address, password=sentinel_password, port=26379, decode_responses=True) sentinel.execute_command(f"SENTINEL failover {APP_NAME}") # Give time so sentinel updates information of failover time.sleep(60) await ops_test.model.block_until( - lambda: "failover-status" not in sentinel.execute_command(f"SENTINEL MASTER {APP_NAME}"), + lambda: "failover-status" not in sentinel.execute_command(f"SENTINEL primary {APP_NAME}"), timeout=60, ) @@ -94,13 +94,13 @@ async def test_scale_up_replication_after_failover(ops_test: OpsTest): timeout=1000, ) - master_info = sentinel.execute_command(f"SENTINEL MASTER {APP_NAME}") - master_info = dict(zip(master_info[::2], master_info[1::2])) + primary_info = sentinel.execute_command(f"SENTINEL primary {APP_NAME}") + primary_info = dict(zip(primary_info[::2], primary_info[1::2])) # General checks that the system is aware of the new unit - assert master_info["num-slaves"] == "3" - assert master_info["quorum"] == "3" - assert master_info["num-other-sentinels"] == "3" + assert primary_info["num-slaves"] == "3" + assert primary_info["quorum"] == "3" + assert primary_info["num-other-sentinels"] == "3" unit_map = await get_unit_map(ops_test) # Check that the initial key is still replicated across units @@ -112,7 +112,7 @@ async def test_scale_up_replication_after_failover(ops_test: OpsTest): @pytest.mark.run(after="test_scale_up_replication_after_failover") -async def test_scale_down_departing_master(ops_test: OpsTest): +async def test_scale_down_departing_primary(ops_test: OpsTest): """Failover to the last unit and scale down.""" unit_map = await get_unit_map(ops_test) logger.info("Unit mapping: {}".format(unit_map)) @@ -130,22 +130,22 @@ async def test_scale_down_departing_master(ops_test: OpsTest): last_redis = Redis(last_address, password=password, decode_responses=True) # INITIAL SETUP # - # Sanity check that the added unit on the previous test is not a master - assert last_redis.execute_command("ROLE")[0] != "master" + # Sanity check that the added unit on the previous test is not a primary + assert last_redis.execute_command("ROLE")[0] != "primary" # Make the added unit a priority during failover last_redis.execute_command("CONFIG SET replica-priority 1") time.sleep(1) - # Failover so the last unit becomes master + # Failover so the last unit becomes primary sentinel.execute_command(f"SENTINEL FAILOVER {APP_NAME}") # Give time so sentinel updates information of failover time.sleep(60) await ops_test.model.block_until( - lambda: "failover-status" not in sentinel.execute_command(f"SENTINEL MASTER {APP_NAME}"), + lambda: "failover-status" not in sentinel.execute_command(f"SENTINEL primary {APP_NAME}"), timeout=60, ) - assert last_redis.execute_command("ROLE")[0] == "master" + assert last_redis.execute_command("ROLE")[0] == "primary" last_redis.close() # SCALE DOWN # @@ -158,12 +158,12 @@ async def test_scale_down_departing_master(ops_test: OpsTest): assert client.get("testKey") == b"myValue" client.close() - master_info = sentinel.execute_command(f"SENTINEL MASTER {APP_NAME}") - master_info = dict(zip(master_info[::2], master_info[1::2])) + primary_info = sentinel.execute_command(f"SENTINEL primary {APP_NAME}") + primary_info = dict(zip(primary_info[::2], primary_info[1::2])) # General checks that the system is reconfigured after departed leader - assert master_info["num-slaves"] == "2" - assert master_info["quorum"] == "2" - assert master_info["num-other-sentinels"] == "2" + assert primary_info["num-slaves"] == "2" + assert primary_info["quorum"] == "2" + assert primary_info["num-other-sentinels"] == "2" sentinel.close() diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index bfaaff6..4ea3edc 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -86,7 +86,7 @@ def test_config_changed_when_unit_is_leader_status_success(self, info): extra_flags = [ f"--requirepass {self.harness.charm._get_password()}", "--bind 0.0.0.0", - f"--masterauth {self.harness.charm._get_password()}", + f"--primaryauth {self.harness.charm._get_password()}", f"--replica-announce-ip {self.harness.charm.unit_pod_hostname}", ] expected_plan = { @@ -118,7 +118,7 @@ def test_config_changed_when_unit_is_leader_status_failure(self, info): extra_flags = [ f"--requirepass {self.harness.charm._get_password()}", "--bind 0.0.0.0", - f"--masterauth {self.harness.charm._get_password()}", + f"--primaryauth {self.harness.charm._get_password()}", f"--replica-announce-ip {self.harness.charm.unit_pod_hostname}", ] expected_plan = { @@ -283,7 +283,7 @@ def test_active_on_enable_tls_with_certificates(self, info, _store_certificates) extra_flags = [ f"--requirepass {self.harness.charm._get_password()}", "--bind 0.0.0.0", - f"--masterauth {self.harness.charm._get_password()}", + f"--primaryauth {self.harness.charm._get_password()}", f"--replica-announce-ip {self.harness.charm.unit_pod_hostname}", "--tls-port 6379", "--port 0", @@ -319,11 +319,11 @@ def test_non_leader_unit_as_replica(self, execute_command): def my_side_effect(value: str): mapping = { f"SENTINEL CKQUORUM {self.harness.charm._name}": "OK", - f"SENTINEL MASTER {self.harness.charm._name}": [ + f"SENTINEL primary {self.harness.charm._name}": [ "ip", APPLICATION_DATA["leader-host"], "flags", - "master", + "primary", ], } return mapping.get(value) @@ -343,7 +343,7 @@ def my_side_effect(value: str): extra_flags = [ f"--requirepass {self.harness.charm._get_password()}", "--bind 0.0.0.0", - f"--masterauth {self.harness.charm._get_password()}", + f"--primaryauth {self.harness.charm._get_password()}", f"--replica-announce-ip {self.harness.charm.unit_pod_hostname}", f"--replicaof {leader_hostname} {redis_port}", ] @@ -370,7 +370,7 @@ def test_application_data_update_after_failover(self, execute_command): def my_side_effect(value: str): mapping = { f"SENTINEL CKQUORUM {self.harness.charm._name}": "OK", - f"SENTINEL MASTER {self.harness.charm._name}": [ + f"SENTINEL primary {self.harness.charm._name}": [ "ip", "different-leader", "flags", @@ -389,8 +389,8 @@ def my_side_effect(value: str): self.harness.update_relation_data(rel.id, "redis-k8s", APPLICATION_DATA) # NOTE: On config changed, charm will be updated with APPLICATION_DATA. But a - # call to `execute_command(SENTINEL MASTER redis-k8s)` will return `different_leader` - # when checking the master, simulating that sentinel triggered failover in between + # call to `execute_command(SENTINEL primary redis-k8s)` will return `different_leader` + # when checking the primary, simulating that sentinel triggered failover in between # charm events. self.harness._emit_relation_changed(rel.id, "redis-k8s") @@ -407,16 +407,16 @@ def my_side_effect(value: str): self.assertEqual(updated_data["leader-host"], "different-leader") @mock.patch.object(Redis, "execute_command") - def test_forced_failover_when_unit_departed_is_master(self, execute_command): + def test_forced_failover_when_unit_departed_is_primary(self, execute_command): # Custom responses to Redis `execute_command` call def my_side_effect(value: str): mapping = { f"SENTINEL CKQUORUM {self.harness.charm._name}": "OK", - f"SENTINEL MASTER {self.harness.charm._name}": [ + f"SENTINEL primary {self.harness.charm._name}": [ "ip", self.harness.charm._k8s_hostname("redis-k8s/1"), "flags", - "master", + "primary", ], } return mapping.get(value) @@ -427,7 +427,7 @@ def my_side_effect(value: str): rel = self.harness.charm.model.get_relation(self._peer_relation) # Simulate an update to the application databag made by the leader unit self.harness.update_relation_data(rel.id, "redis-k8s", APPLICATION_DATA) - # Add and remove a unit that sentinel will simulate as current master + # Add and remove a unit that sentinel will simulate as current primary self.harness.add_relation_unit(rel.id, "redis-k8s/1") rel = self.harness.charm.model.get_relation(self._peer_relation)