From 18f3f4c7bc657313f3816ce4e20c468640004146 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Tue, 15 Mar 2016 17:03:37 -0700 Subject: [PATCH] Add zookeeper test infrastructure This adds a per-test zookeeper server fixture and demonstrates its use in preparation for actual zookeeper use. Change-Id: I99ac519057cdd9d9c829928e0e12ece65b0a5c43 Depends-On: I66d2bc90eb350424c051f480b4102209054ce2eb --- nodepool/tests/__init__.py | 104 ++++++++++++++++++ .../tests/fixtures/zookeeper/log4j.properties | 51 +++++++++ nodepool/tests/fixtures/zookeeper/zoo.cfg | 5 + nodepool/tests/test_builder.py | 7 ++ requirements.txt | 1 + 5 files changed, 168 insertions(+) create mode 100644 nodepool/tests/fixtures/zookeeper/log4j.properties create mode 100644 nodepool/tests/fixtures/zookeeper/zoo.cfg diff --git a/nodepool/tests/__init__.py b/nodepool/tests/__init__.py index 1f2d22220..ab4b7a9b5 100644 --- a/nodepool/tests/__init__.py +++ b/nodepool/tests/__init__.py @@ -109,6 +109,102 @@ class GearmanServerFixture(fixtures.Fixture): raise +class ZookeeperServerFixture(fixtures.Fixture): + def __init__(self, port=0): + self._port = port + + def setUp(self): + super(ZookeeperServerFixture, self).setUp() + + # Get the local port range, we're going to pick one at a time + # at random to try. + with open('/proc/sys/net/ipv4/ip_local_port_range') as f: + line = f.readline() + begin, end = map(int, line.split()) + + zookeeper_fixtures = os.path.join(os.path.dirname(__file__), + 'fixtures', 'zookeeper') + + # Make a tmpdir to hold the config file, zookeeper data dir, + # and log file. + tmp_root = self.useFixture(fixtures.TempDir()).path + with open(os.path.join(zookeeper_fixtures, 'log4j.properties')) as i: + with open(os.path.join(tmp_root, 'log4j.properties'), 'w') as o: + o.write(i.read()) + + config_path = os.path.join(tmp_root, 'zoo.cfg') + log_path = os.path.join(tmp_root, 'zookeeper.log') + + classpath = [ + tmp_root, + '/usr/share/java/jline.jar', + '/usr/share/java/log4j-1.2.jar', + '/usr/share/java/xercesImpl.jar', + '/usr/share/java/xmlParserAPIs.jar', + '/usr/share/java/netty.jar', + '/usr/share/java/slf4j-api.jar', + '/usr/share/java/slf4j-log4j12.jar', + '/usr/share/java/zookeeper.jar', + ] + classpath = ':'.join(classpath) + + args = ['/usr/bin/java', '-cp', classpath, + '-Dzookeeper.log.dir=%s' % (tmp_root,), + '-Dzookeeper.root.logger=INFO,ROLLINGFILE', + 'org.apache.zookeeper.server.quorum.QuorumPeerMain', + config_path] + + found_port = False + + # Try a random port in the local port range one at a time + # until we find one that's available. + while not found_port: + port = random.randrange(begin, end) + + # Write a config file with this port. + with open(os.path.join(zookeeper_fixtures, 'zoo.cfg')) as i: + with open(config_path, 'w') as o: + o.write(i.read().format(datadir=os.path.join(tmp_root, 'data'), + port=port)) + + # Run zookeeper. + p = subprocess.Popen(args) + + # Wait up to 30 seconds to figure out if it has started. + for x in range(30): + r = self._checkZKLog(log_path) + if r is True: + found_port = True + break + elif r is False: + break + time.sleep(1) + + if not found_port: + p.kill() + p.wait() + + if found_port: + self.zookeeper_port = port + self.zookeeper_process = p + self.addCleanup(self.shutdownZookeeper) + + def _checkZKLog(self, path): + if not os.path.exists(path): + return None + with open(path) as f: + for line in f: + if 'Snapshotting:' in line: + return True + if 'Address already in use' in line: + return False + return None + + def shutdownZookeeper(self): + self.zookeeper_process.kill() + self.zookeeper_process.wait() + + class GearmanClient(gear.Client): def __init__(self): super(GearmanClient, self).__init__(client_id='test_client') @@ -397,3 +493,11 @@ class DBTestCase(BaseTestCase): class IntegrationTestCase(DBTestCase): def setUpFakes(self): pass + + +class ZKTestCase(BaseTestCase): + def setUp(self): + super(ZKTestCase, self).setUp() + f = ZookeeperServerFixture() + self.useFixture(f) + self.zookeeper_port = f.zookeeper_port diff --git a/nodepool/tests/fixtures/zookeeper/log4j.properties b/nodepool/tests/fixtures/zookeeper/log4j.properties new file mode 100644 index 000000000..d5bb08854 --- /dev/null +++ b/nodepool/tests/fixtures/zookeeper/log4j.properties @@ -0,0 +1,51 @@ +# +# ZooKeeper Logging Configuration +# + +# Format is " (, )+ + +log4j.rootLogger=${zookeeper.root.logger} + +# Example: console appender only +# log4j.rootLogger=INFO, CONSOLE + +# Example with rolling log file +#log4j.rootLogger=DEBUG, CONSOLE, ROLLINGFILE + +# Example with rolling log file and tracing +#log4j.rootLogger=TRACE, CONSOLE, ROLLINGFILE, TRACEFILE + +# +# Log INFO level and above messages to the console +# +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.Threshold=INFO +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} - %-5p [%t:%C{1}@%L] - %m%n + +# +# Add ROLLINGFILE to rootLogger to get log file output +# Log DEBUG level and above messages to a log file +log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender +log4j.appender.ROLLINGFILE.Threshold=DEBUG +log4j.appender.ROLLINGFILE.File=${zookeeper.log.dir}/zookeeper.log + +# Max log file size of 10MB +log4j.appender.ROLLINGFILE.MaxFileSize=10MB +# uncomment the next line to limit number of backup files +#log4j.appender.ROLLINGFILE.MaxBackupIndex=10 + +log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout +log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} - %-5p [%t:%C{1}@%L] - %m%n + + +# +# Add TRACEFILE to rootLogger to get log file output +# Log DEBUG level and above messages to a log file +log4j.appender.TRACEFILE=org.apache.log4j.FileAppender +log4j.appender.TRACEFILE.Threshold=TRACE +log4j.appender.TRACEFILE.File=${zookeeper.log.dir}/zookeeper_trace.log + +log4j.appender.TRACEFILE.layout=org.apache.log4j.PatternLayout +### Notice we are including log4j's NDC here (%x) +log4j.appender.TRACEFILE.layout.ConversionPattern=%d{ISO8601} - %-5p [%t:%C{1}@%L][%x] - %m%n diff --git a/nodepool/tests/fixtures/zookeeper/zoo.cfg b/nodepool/tests/fixtures/zookeeper/zoo.cfg new file mode 100644 index 000000000..bb83ebebb --- /dev/null +++ b/nodepool/tests/fixtures/zookeeper/zoo.cfg @@ -0,0 +1,5 @@ +tickTime=2000 +initLimit=10 +syncLimit=5 +dataDir={datadir} +clientPort={port} diff --git a/nodepool/tests/test_builder.py b/nodepool/tests/test_builder.py index 884cf9d11..0cc1af7ba 100644 --- a/nodepool/tests/test_builder.py +++ b/nodepool/tests/test_builder.py @@ -18,6 +18,7 @@ import time import threading import fixtures +import kazoo.client from nodepool import builder, exceptions, fakeprovider, tests @@ -147,3 +148,9 @@ class TestNodepoolBuilder(tests.DBTestCase): # We failed to upload first_fail_id and have # moved onto another upload that will fail. break + +class TestZookeeper(tests.ZKTestCase): + def test_zk(self): + zk = kazoo.client.KazooClient(hosts='127.0.0.1:%s' % self.zookeeper_port) + zk.start() + zk.get('/') diff --git a/requirements.txt b/requirements.txt index e5e8843a2..e63b2ae49 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ os-client-config>=1.2.0 shade>=0.12.0 diskimage-builder voluptuous +kazoo