Skip to content

Commit ada1d04

Browse files
[cluster_run] Add code watchdog
$ ./cluster_run -w (modify erl files) $ make (files get reloaded automatically) Change-Id: I10290692dd6c911fff9fd4b0e1d78ced60db1095 Reviewed-on: https://review.couchbase.org/c/ns_server/+/225572 Reviewed-by: Bryan McCoid <[email protected]> Tested-by: Timofey Barmin <[email protected]> Tested-by: Build Bot <[email protected]> Well-Formed: Build Bot <[email protected]>
1 parent 80fc862 commit ada1d04

File tree

3 files changed

+83
-4
lines changed

3 files changed

+83
-4
lines changed

cluster_run

+19-4
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ def argument_parser():
166166
nargs="*",
167167
help=argparse.SUPPRESS,
168168
)
169+
arg_parser.add_argument(
170+
'--code-watchdog', '-w',
171+
action='store_true',
172+
default=False,
173+
help="Reload ns_server beam files automatically when they are modified")
169174

170175
args = arg_parser.parse_args()
171176

@@ -189,8 +194,8 @@ def argument_parser():
189194
"run_serverless": args.serverless,
190195
"run_provisioned": args.provisioned,
191196
"num_vbuckets": args.num_vbuckets,
192-
"args": args.additional_args
193-
}
197+
"args": args.additional_args,
198+
"code_watchdog": args.code_watchdog}
194199

195200
# Removes the keys when value is None, therefore start_cluster uses
196201
# the default values.
@@ -204,6 +209,8 @@ def main():
204209
params = argument_parser()
205210
nodes = []
206211
terminal_attrs = None
212+
code_watchdog = params.get("code_watchdog", False)
213+
del params["code_watchdog"]
207214

208215
def kill_nodes():
209216
cluster_run_lib.kill_nodes(nodes, terminal_attrs)
@@ -218,9 +225,17 @@ def main():
218225

219226
nodes = cluster_run_lib.start_cluster(**params)
220227

221-
for node in nodes:
222-
node.wait()
228+
if code_watchdog:
229+
observer = cluster_run_lib.start_code_watchdog(
230+
params.get("num_nodes", 1),
231+
params.get("start_index", 0))
223232

233+
try:
234+
for node in nodes:
235+
node.wait()
236+
finally:
237+
if code_watchdog:
238+
cluster_run_lib.stop_code_watchdog(observer)
224239

225240
if __name__ == '__main__':
226241
main()

pylib/cluster_run_lib.py

+63
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,69 @@ def start_node(node_num):
513513
return processes
514514

515515

516+
def start_code_watchdog(num_nodes, start_index):
517+
# This is a workaround to avoid importing watchdog if it's not installed.
518+
# Otherwise, we will need to install watchdog on all test machines, where
519+
# it will never be actually used.
520+
from watchdog.observers import Observer
521+
from watchdog.events import FileSystemEventHandler
522+
523+
class BeamFileHandler(FileSystemEventHandler):
524+
def __init__(self, num_nodes, start_index):
525+
self.num_nodes = num_nodes
526+
self.start_index = start_index
527+
528+
def on_modified(self, event):
529+
self.handle_file_change(event.src_path)
530+
531+
def on_created(self, event):
532+
self.handle_file_change(event.src_path)
533+
534+
def on_moved(self, event):
535+
self.handle_file_change(event.dest_path)
536+
537+
def handle_file_change(self, file_path):
538+
if file_path.endswith('.beam'):
539+
module_name = os.path.splitext(os.path.basename(file_path))[0]
540+
self.reload_module(module_name)
541+
542+
def reload_module(self, module_name):
543+
for i in range(self.num_nodes):
544+
port = base_api_port + self.start_index + i
545+
node = f"http://127.0.0.1:{port}"
546+
url = f"{node}/diag/eval"
547+
548+
# Erlang code to reload the module
549+
erlang_code = f"{{module, _}} = c:l({module_name})."
550+
551+
try:
552+
response = requests.post(url,
553+
auth=(default_username, default_pass),
554+
data=erlang_code)
555+
if response.status_code == 200:
556+
print(f"*** reloaded {module_name} on {node}")
557+
else:
558+
print(f"*** reload failed for {module_name} on {node}:" \
559+
f" {response.text}")
560+
except Exception as e:
561+
print(f"*** reload failed for {module_name} on {node}: {e}")
562+
563+
code_path = os.path.join(ns_server_dir, '_build', 'default', 'lib')
564+
print(f"*** Starting code watchdog for {code_path}")
565+
# Create a watchdog observer
566+
observer = Observer()
567+
handler = BeamFileHandler(num_nodes, start_index)
568+
observer.schedule(handler, code_path, recursive=True)
569+
observer.start()
570+
return observer
571+
572+
573+
def stop_code_watchdog(observer):
574+
observer.stop()
575+
observer.join()
576+
print("*** Code watchdog stopped")
577+
578+
516579
def wait_nodes_up(num_nodes=1, start_index=0, master_passwords=None,
517580
root_dir=None, timeout_s=node_start_timeout_s,
518581
node_urls=None, verbose=True):

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
requests>=2.32.3
2+
watchdog>=6.0.0

0 commit comments

Comments
 (0)