diff --git a/.drone.yml b/.drone.yml index a51e43c22..b39c7e1b3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -43,6 +43,9 @@ compose: # NOTE: Using 1.4.21 instead of 1.4.17 due to tag availability. image: memcached:1.4.21 + zookeeper: + image: jplock/zookeeper:3.4.6 + # Build steps are where the setup, compilation, and tests happen. build: # This is a fat Docker image with the apt dependencies pre-installed. diff --git a/install/install_services.sh b/install/install_services.sh index a1e459d1d..ced6d8a43 100755 --- a/install/install_services.sh +++ b/install/install_services.sh @@ -40,6 +40,7 @@ haproxy nginx gunicorn redis-server +zookeeperd PACKAGES ############################################################################### diff --git a/r2/example.ini b/r2/example.ini index 0b9fd7f1f..bed0acfe7 100644 --- a/r2/example.ini +++ b/r2/example.ini @@ -590,9 +590,15 @@ amqp_virtual_host = / ############################################ ZOOKEEPER # zookeeper is optional at the moment -zookeeper_connection_string = -zookeeper_username = -zookeeper_password = +zookeeper_connection_string = localhost:2181 +zookeeper_username = reddit +zookeeper_password = reddit + +# these should be one of "config" or "zookeeper" +# - config: read from the local ini file +# - zookeeper: read from the zookeeper cluster +liveconfig_source = config +secrets_source = config ############################################ EMAIL diff --git a/r2/r2/lib/app_globals.py b/r2/r2/lib/app_globals.py index fd1ad4d20..a026e5a1a 100644 --- a/r2/r2/lib/app_globals.py +++ b/r2/r2/lib/app_globals.py @@ -82,6 +82,7 @@ from r2.lib.stats import ( ) from r2.lib.translation import get_active_langs, I18N_PATH from r2.lib.utils import config_gold_price, thread_dump +from r2.lib.zookeeper import connect_to_zookeeper, LiveConfig, LiveList LIVE_CONFIG_NODE = "/config/live" @@ -325,6 +326,11 @@ class Globals(object): 'cassandra_wcl', ], + ConfigValue.choice(zookeeper="zookeeper", config="config"): [ + "liveconfig_source", + "secrets_source", + ], + ConfigValue.timeinterval: [ 'ARCHIVE_AGE', "vote_queue_grace_period", @@ -679,33 +685,29 @@ class Globals(object): self.startup_timer.intermediate("configuration") ################# ZOOKEEPER - # for now, zookeeper will be an optional part of the stack. - # if it's not configured, we will grab the expected config from the - # [live_config] section of the ini file - zk_hosts = self.config.get("zookeeper_connection_string") - if zk_hosts: - from r2.lib.zookeeper import (connect_to_zookeeper, - LiveConfig, LiveList) - zk_username = self.config["zookeeper_username"] - zk_password = self.config["zookeeper_password"] - self.zookeeper = connect_to_zookeeper(zk_hosts, (zk_username, - zk_password)) - self.live_config = LiveConfig(self.zookeeper, LIVE_CONFIG_NODE) - self.secrets = fetch_secrets(self.zookeeper) - self.throttles = LiveList(self.zookeeper, "/throttles", - map_fn=ipaddress.ip_network, - reduce_fn=ipaddress.collapse_addresses) + zk_hosts = self.config["zookeeper_connection_string"] + zk_username = self.config["zookeeper_username"] + zk_password = self.config["zookeeper_password"] + self.zookeeper = connect_to_zookeeper(zk_hosts, (zk_username, + zk_password)) - # close our zk connection when the app shuts down - SHUTDOWN_CALLBACKS.append(self.zookeeper.stop) + self.throttles = LiveList(self.zookeeper, "/throttles", + map_fn=ipaddress.ip_network, + reduce_fn=ipaddress.collapse_addresses) + + parser = ConfigParser.RawConfigParser() + parser.optionxform = str + parser.read([self.config["__file__"]]) + + if self.config["liveconfig_source"] == "zookeeper": + self.live_config = LiveConfig(self.zookeeper, LIVE_CONFIG_NODE) else: - self.zookeeper = None - parser = ConfigParser.RawConfigParser() - parser.optionxform = str - parser.read([self.config["__file__"]]) self.live_config = extract_live_config(parser, self.plugins) + + if self.config["secrets_source"] == "zookeeper": + self.secrets = fetch_secrets(self.zookeeper) + else: self.secrets = extract_secrets(parser) - self.throttles = tuple() # immutable since it's not real ################# PRIVILEGED USERS self.admins = PermissionFilteredEmployeeList(