Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 910dd53

Browse files
committed
add startup notification for KCL process
1 parent ddc3be9 commit 910dd53

File tree

5 files changed

+74
-11
lines changed

5 files changed

+74
-11
lines changed

‎README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ make web
115115

116116
## Change Log
117117

118+
* v0.1.11: Add startup/initialization notification for KCL process
118119
* v0.1.10: Bump version of amazon_kclpy to 1.4.1
119120
* v0.1.9: Add initial support for SQS/SNS
120121
* v0.1.8: Fix installation of JARs in amazon_kclpy if localstack is installed transitively

‎localstack/utils/kinesis/kinesis_connector.py

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import traceback
1111
import threading
1212
import logging
13+
import Queue
1314
from urlparse import urlparse
1415
from amazon_kclpy import kcl
1516
from docopt import docopt
@@ -114,6 +115,8 @@ def __init__(self, params):
114115
self.buffer_size = 2
115116
# determine log level
116117
self.log_level = params.get('level')
118+
# get log subscribers
119+
self.log_subscribers = params.get('log_subscribers', [])
117120
if self.log_level is None:
118121
self.log_level = DEFAULT_KCL_LOG_LEVEL
119122
if self.log_level > 0:
@@ -140,12 +143,20 @@ def get_logger_for_level_in_log_line(self, line):
140143
return getattr(self.logger, level_name.lower())
141144
return None
142145

146+
def notify_subscribers(self, line):
147+
for subscriber in self.log_subscribers:
148+
if re.match(subscriber.regex, line):
149+
subscriber.update(line)
150+
143151
def start_reading(self, params):
144152
for line in tail("-n", 0, "-f", params['file'], _iter=True):
145153
if not self.running:
146154
return
147155
if self.log_level > 0:
148156
line = line.replace('\n', '')
157+
# notify subscribers
158+
self.notify_subscribers(line)
159+
# add line to buffer
149160
self.buffer.append(line)
150161
if len(self.buffer) >= self.buffer_size:
151162
logger_func = None
@@ -205,6 +216,33 @@ def stop(self, quiet=True):
205216
self.running = False
206217

207218

219+
class KclLogListener(object):
220+
def __init__(self, regex='.*'):
221+
self.regex = regex
222+
223+
def update(self, log_line):
224+
print(log_line)
225+
226+
227+
class KclStartedLogListener(KclLogListener):
228+
def __init__(self):
229+
self.regex_init = r'.*Initialization complete.*'
230+
self.regex_take_shard = r'.*Received response .* for initialize.*'
231+
# construct combined regex
232+
regex = r'(%s)|(%s)' % (self.regex_init, self.regex_take_shard)
233+
super(KclStartedLogListener, self).__init__(regex=regex)
234+
# Semaphore.acquire does not provide timeout parameter, so we
235+
# use a Queue here which provides the required functionality
236+
self.sync_init = Queue.Queue(0)
237+
self.sync_take_shard = Queue.Queue(0)
238+
239+
def update(self, log_line):
240+
if re.match(self.regex_init, log_line):
241+
self.sync_init.put(1, block=False)
242+
if re.match(self.regex_take_shard, log_line):
243+
self.sync_take_shard.put(1, block=False)
244+
245+
208246
# construct a stream info hash
209247
def get_stream_info(stream_name, log_file=None, shards=None, env=None, endpoint_url=None,
210248
ddb_lease_table_suffix=None, env_vars={}):
@@ -242,7 +280,8 @@ def get_stream_info(stream_name, log_file=None, shards=None, env=None, endpoint_
242280

243281

244282
def start_kcl_client_process(stream_name, listener_script, log_file=None, env=None, configs={},
245-
endpoint_url=None, ddb_lease_table_suffix=None, env_vars={}, kcl_log_level=DEFAULT_KCL_LOG_LEVEL):
283+
endpoint_url=None, ddb_lease_table_suffix=None, env_vars={},
284+
kcl_log_level=DEFAULT_KCL_LOG_LEVEL, log_subscribers=[]):
246285
env = aws_stack.get_environment(env)
247286
# decide which credentials provider to use
248287
credentialsProvider = None
@@ -266,7 +305,8 @@ def start_kcl_client_process(stream_name, listener_script, log_file=None, env=No
266305
run('touch %s' % log_file)
267306
# start log output reader thread which will read the KCL log
268307
# file and print each line to stdout of this process...
269-
reader_thread = OutputReaderThread({'file': log_file, 'level': kcl_log_level, 'log_prefix': 'KCL'})
308+
reader_thread = OutputReaderThread({'file': log_file, 'level': kcl_log_level,
309+
'log_prefix': 'KCL', 'log_subscribers': log_subscribers})
270310
reader_thread.start()
271311

272312
# construct stream info
@@ -352,7 +392,8 @@ def receive_msg(records, checkpointer, shard_id):
352392

353393
def listen_to_kinesis(stream_name, listener_func=None, processor_script=None,
354394
events_file=None, endpoint_url=None, log_file=None, configs={}, env=None,
355-
ddb_lease_table_suffix=None, env_vars={}, kcl_log_level=DEFAULT_KCL_LOG_LEVEL):
395+
ddb_lease_table_suffix=None, env_vars={}, kcl_log_level=DEFAULT_KCL_LOG_LEVEL,
396+
log_subscribers=[], wait_until_started=False):
356397
"""
357398
High-level function that allows to subscribe to a Kinesis stream
358399
and receive events in a listener function. A KCL client process is
@@ -375,6 +416,27 @@ def listen_to_kinesis(stream_name, listener_func=None, processor_script=None,
375416
# start KCL client (background process)
376417
if processor_script[-4:] == '.pyc':
377418
processor_script = processor_script[0:-1]
378-
return start_kcl_client_process(stream_name, processor_script,
419+
# add log listener that notifies when KCL is started
420+
if wait_until_started:
421+
listener = KclStartedLogListener()
422+
log_subscribers.append(listener)
423+
424+
process = start_kcl_client_process(stream_name, processor_script,
379425
endpoint_url=endpoint_url, log_file=log_file, configs=configs, env=env,
380-
ddb_lease_table_suffix=ddb_lease_table_suffix, env_vars=env_vars, kcl_log_level=kcl_log_level)
426+
ddb_lease_table_suffix=ddb_lease_table_suffix, env_vars=env_vars, kcl_log_level=kcl_log_level,
427+
log_subscribers=log_subscribers)
428+
429+
if wait_until_started:
430+
# wait at most 30 seconds for initialization
431+
try:
432+
listener.sync_init.get(block=True, timeout=30)
433+
except Exception, e:
434+
raise Exception('Timeout when waiting for KCL initialization.')
435+
# wait at most 30 seconds for shard lease notification
436+
try:
437+
listener.sync_take_shard.get(block=True, timeout=30)
438+
except Exception, e:
439+
# this merely means that there is no shard available to take. Do nothing.
440+
pass
441+
442+
return process

‎setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def run(self):
7676

7777
setup(
7878
name='localstack',
79-
version='0.1.10',
79+
version='0.1.11',
8080
description='Provides an easy-to-use test/mocking framework for developing Cloud applications',
8181
author='Waldemar Hummer (Atlassian)',
8282
author_email='[email protected]',

‎tests/test_iam_credentials.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ def process_records(records):
2222
stream_name=stream_name,
2323
listener_func=process_records,
2424
env_vars=env_vars,
25-
kcl_log_level=logging.INFO)
26-
time.sleep(60 * 10)
25+
kcl_log_level=logging.INFO,
26+
wait_until_started=True)
2727

2828
if __name__ == '__main__':
2929
run_kcl_with_iam_assume_role()

‎tests/test_integration.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,10 @@ def process_records(records):
140140
EVENTS.extend(records)
141141

142142
# start the KCL client process in the background
143-
kinesis_connector.listen_to_kinesis(TEST_STREAM_NAME, listener_func=process_records)
143+
kinesis_connector.listen_to_kinesis(TEST_STREAM_NAME, listener_func=process_records,
144+
wait_until_started=True)
144145

145-
print("Sleep some time (to give the Kinesis consumer enough time to come up)")
146-
time.sleep(25)
146+
print("Kinesis consumer initialized.")
147147

148148
# create table with stream forwarding config
149149
create_dynamodb_table(TEST_TABLE_NAME, partition_key=PARTITION_KEY,

0 commit comments

Comments
 (0)