autoreload: Add tests and warning when manifest changes

Rene pointed out that we were silently accepting an updated manifest despite
not actually being able to register it with `lightningd`. This meant that if
options, RPC methods, hooks or notifications get added or removed they will
not be wired in correctly.

The warning is really obnoxious and multi-line to draw the attention.

Suggested-by: Rene Pickhardt <@renepickhardt>
This commit is contained in:
Christian Decker
2020-05-15 18:38:48 +02:00
parent 8668a2244d
commit 63c9872ec2
4 changed files with 93 additions and 3 deletions

View File

@@ -37,8 +37,11 @@ class ChildPlugin(object):
last = now
try:
self.restart()
except:
self.plugin.log("Failed to start plugin, will wait for next change and try again.", level='warn')
except Exception as e:
self.plugin.log(
"Failed to start plugin, will wait for next change and try again:",
level='error'
)
def handle_init(self, request):
"""Lightningd has sent us its first init message, clean and forward.
@@ -93,7 +96,7 @@ class ChildPlugin(object):
try:
self.proc = subprocess.Popen([self.path], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
self.status = 'started'
self.getmanifest()
manifest = self.getmanifest()
return True
except Exception as e:
self.plugin.log(e, level='warn')
@@ -129,6 +132,15 @@ class ChildPlugin(object):
raise ValueError()
if 'id' in msg and msg['id'] == 0:
if self.manifest is not None and msg['result'] != self.manifest:
plugin.log(
"Plugin manifest changed between restarts: {new} != {old}\n\n"
"==> You need to restart c-lightning for these changes to be picked up! <==".format(
new=json.dumps(msg['result'], indent=True).replace("\"", "'"),
old=json.dumps(self.manifest, indent=True).replace("\"", "'")
), level='warn')
raise ValueError()
self.manifest = msg['result']
break
self.plugin._write_locked(msg)
@@ -180,6 +192,7 @@ class ChildPlugin(object):
@plugin.init()
def init(options, configuration, plugin, request):
#import remote_pdb; remote_pdb.set_trace()
if options['autoreload-plugin'] in ['null', None]:
print("Cannot run the autoreload plugin on its own, please specify --autoreload-plugin")
plugin.rpc.stop()

7
autoreload/tests/dummy.py Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env python3
from pyln.client import Plugin
plugin = Plugin()
plugin.run()

11
autoreload/tests/dummy2.py Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python3
from pyln.client import Plugin
plugin = Plugin()
@plugin.method('dummy')
def on_dummy():
pass
plugin.run()

View File

@@ -0,0 +1,59 @@
from pyln.testing.fixtures import *
import shutil
import subprocess
import time
import os
import unittest
def copy_plugin(src, directory, filename=None):
base = os.path.dirname(__file__)
src = os.path.join(base, src)
dst = os.path.join(directory, filename if filename is not None else os.path.basename(src))
shutil.copy(src, dst)
shutil.copystat(src, dst)
return dst
def test_restart_on_change(node_factory, directory):
# Copy the dummy plugin over
plugin = copy_plugin("dummy.py", directory, "plugin.py")
opts = {
'plugin': os.path.join(os.path.dirname(__file__), "..", 'autoreload.py'),
'autoreload-plugin': plugin,
}
l1 = node_factory.get_node(options=opts)
subprocess.check_call(['touch', plugin])
l1.daemon.wait_for_log(r'Detected a change in the child plugin, restarting')
@unittest.skipIf(True, "Doesn't work on travis yet")
def test_changing_manifest(node_factory, directory):
"""Change the manifest in-between restarts.
Adding an RPC method like the switch from dummy.py to dummy2.py does,
should result in an error while reloading the plugin.
"""
# Copy the dummy plugin over
plugin = copy_plugin("dummy.py", directory, "plugin.py")
plugin_path = os.path.join(os.path.dirname(__file__), "..", 'autoreload.py')
opts = {
'plugin': os.path.join(os.path.dirname(__file__), "..", 'autoreload.py'),
'autoreload-plugin': plugin,
}
l1 = node_factory.get_node(options=opts, allow_broken_log=True)
plugin = copy_plugin("dummy2.py", directory, "plugin.py")
time.sleep(10)
subprocess.check_call(['touch', plugin_path])
time.sleep(10)
subprocess.check_call(['touch', plugin_path])
l1.daemon.wait_for_log(r'Detected a change in the child plugin, restarting')
l1.daemon.wait_for_log(r'You need to restart c-lightning')