From 08afa140e2cb4afda0e7a61edc02c500d75cbb76 Mon Sep 17 00:00:00 2001 From: Gavin Norman Date: Fri, 27 Jul 2018 16:49:57 +0200 Subject: [PATCH 1/6] Remove unused import --- src/turtle/env/model/Node.d | 1 - 1 file changed, 1 deletion(-) diff --git a/src/turtle/env/model/Node.d b/src/turtle/env/model/Node.d index b8b83d4d7..af0014073 100644 --- a/src/turtle/env/model/Node.d +++ b/src/turtle/env/model/Node.d @@ -36,7 +36,6 @@ import ocean.core.Verify; public abstract class Node ( NodeType, istring id ) : ITurtleEnv { - import swarm.Const : NodeItem; import swarm.neo.AddrPort; import ocean.io.device.File; import ocean.core.Buffer; From ff0a11708ec726d6f47cfc6c2f94f7635337184a Mon Sep 17 00:00:00 2001 From: Gavin Norman Date: Tue, 14 Aug 2018 11:16:28 +0200 Subject: [PATCH 2/6] Move construction of INodeBase's select listeners into abstract methods References to the listeners are required by INodeBase, but these were previously passed into the ctor from outside, rather than being owned and controlled by this class. This, combined with the lack of support in ocean for reopening a shutdown listener, made it impossible to implement a method to restart the node's listeners. (The ideal solution, of course, would be to add the missing restart support to the select listener in ocean, but this will require a major swarm release.) --- src/swarm/node/model/NeoNode.d | 175 +++++++++++++++++++++++---------- src/swarm/node/model/Node.d | 76 ++++++++++---- 2 files changed, 179 insertions(+), 72 deletions(-) diff --git a/src/swarm/node/model/NeoNode.d b/src/swarm/node/model/NeoNode.d index ab85d5b4b..128cf98fe 100644 --- a/src/swarm/node/model/NeoNode.d +++ b/src/swarm/node/model/NeoNode.d @@ -191,18 +191,10 @@ public abstract class INodeBase : INode, INodeInfo Params: node = node addres & port conn_setup_params = connection handler constructor arguments - listener = select listener, is evaluated exactly once after - conn_setup_params have been populated - neo_listener = select listener to handle the neo protocol - unix_listener = select listener to handle control requests through - the Unix domain server socket ***************************************************************************/ - public this ( NodeItem node, ConnectionSetupParams conn_setup_params, - lazy ISelectListener listener, - ISelectListener neo_listener = null, - ISelectListener unix_listener = null ) + public this ( NodeItem node, ConnectionSetupParams conn_setup_params ) { this.node_item_ = node; @@ -211,14 +203,13 @@ public abstract class INodeBase : INode, INodeInfo conn_setup_params.error_dg = &this.error; - this.listener = listener; - this.neo_listener = neo_listener; - this.unix_listener = unix_listener; + this.listener = this.newListener(); + this.neo_listener = this.newNeoListener(); + this.unix_listener = this.newUnixSocketListener(); this.record_action_counters_ = new RecordActionCounters(this.record_action_counter_ids); } - /************************************************************************** Sets the error callback delegate. @@ -577,6 +568,36 @@ public abstract class INodeBase : INode, INodeInfo } + /*************************************************************************** + + Returns: + new ISelectListener instance for the legacy protocol + + ***************************************************************************/ + + protected abstract ISelectListener newListener ( ); + + + /*************************************************************************** + + Returns: + new ISelectListener instance for the neo protocol + + ***************************************************************************/ + + protected abstract ISelectListener newNeoListener ( ); + + + /*************************************************************************** + + Returns: + new ISelectListener instance for the unix socket protocol + + ***************************************************************************/ + + protected abstract ISelectListener newUnixSocketListener ( ); + + /*************************************************************************** Returns: @@ -751,6 +772,21 @@ public class NodeBase ( ConnHandler : ISwarmConnectionHandler ) : INodeBase protected Object shared_resources; + /// Port for the neo protocol. + private ushort neo_port; + + /// Connection setup params to pass to each legacy connection handler. + private ConnectionSetupParams conn_setup_params; + + /// Connection setup params to pass to each neo connection handler. + private Neo.ConnectionHandler.SharedParams neo_conn_setup_params; + + /// Options for the neo node and connection handlers. + private Options options; + + /// Socket connection backlog. + private int backlog; + /*************************************************************************** Constructor @@ -768,16 +804,15 @@ public class NodeBase ( ConnHandler : ISwarmConnectionHandler ) : INodeBase ConnectionSetupParams conn_setup_params, Options options, int backlog ) { + this.neo_port = neo_port; + this.conn_setup_params = conn_setup_params; + this.options = options; + this.backlog = backlog; + verify(options.epoll !is null); this.shared_resources = options.shared_resources; - InetAddress!(false) addr, neo_addr; - - // Create listener sockets. - this.socket = new AddressIPSocket!(); - this.neo_socket = new AddressIPSocket!(); - // Load credentials from specified file. Const!(Key[istring])* credentials; if ( options.credentials_filename ) @@ -803,38 +838,12 @@ public class NodeBase ( ConnHandler : ISwarmConnectionHandler ) : INodeBase auto no_delay = true; // Instantiate params object shared by all neo connection handlers. - auto neo_conn_setup_params = new Neo.ConnectionHandler.SharedParams( + this.neo_conn_setup_params = new Neo.ConnectionHandler.SharedParams( options.epoll, options.requests, no_delay, *credentials, this, &this.getResourceAcquirer); - // Set up unix listener socket, if specified. - UnixListener unix_listener; - if ( options.unix_socket_path.length ) - { - BasicCommandHandler.Handler[istring] unix_socket_handlers; - if ( this.credentials_file ) - { - unix_socket_handlers = - ["update-credentials"[]: &this.handleUpdateCredentials, - "list-credentials": &this.handleListCredentials]; - } - - unix_listener = new UnixListener( - options.unix_socket_path, options.epoll, unix_socket_handlers); - } - // Super ctor. - super(node, conn_setup_params, - this.listener = new Listener( - addr(node.Address, node.Port), this.socket, conn_setup_params, - backlog - ), - this.neo_listener = new NeoListener( - neo_addr(node.Address, neo_port), this.neo_socket, - neo_conn_setup_params, backlog - ), - unix_listener - ); + super(node, conn_setup_params); // Set up stats tracking for all named requests specified. options.requests.initStats(this.neo_request_stats); @@ -901,6 +910,67 @@ public class NodeBase ( ConnHandler : ISwarmConnectionHandler ) : INodeBase return ret; } + + /*************************************************************************** + + Returns: + new ISelectListener instance for the legacy protocol + + ***************************************************************************/ + + protected override ISelectListener newListener ( ) + { + InetAddress!(false) addr; + this.socket = new AddressIPSocket!(); + return this.listener = new Listener( + addr(this.node_item.Address, this.node_item.Port), this.socket, + this.conn_setup_params, this.backlog); + } + + + /*************************************************************************** + + Returns: + new ISelectListener instance for the neo protocol + + ***************************************************************************/ + + protected override ISelectListener newNeoListener ( ) + { + InetAddress!(false) neo_addr; + this.neo_socket = new AddressIPSocket!(); + return this.neo_listener = new NeoListener( + neo_addr(this.node_item.Address, this.neo_port), this.neo_socket, + this.neo_conn_setup_params, this.backlog); + } + + + /*************************************************************************** + + Returns: + new ISelectListener instance for the unix socket protocol + + ***************************************************************************/ + + protected override ISelectListener newUnixSocketListener ( ) + { + if ( !this.options.unix_socket_path.length ) + return null; + + BasicCommandHandler.Handler[istring] unix_socket_handlers; + if ( this.credentials_file ) + { + unix_socket_handlers = + ["update-credentials"[]: &this.handleUpdateCredentials, + "list-credentials": &this.handleListCredentials]; + } + + return this.unix_listener = new UnixListener( + this.options.unix_socket_path, this.options.epoll, + unix_socket_handlers); + } + + /*************************************************************************** Scope allocates a request resource acquirer backed by the protected @@ -1003,15 +1073,12 @@ public class NodeBase ( ConnHandler : ISwarmConnectionHandler ) : INodeBase public this ( NodeItem node, ConnectionSetupParams conn_setup_params, int backlog ) { + this.conn_setup_params = conn_setup_params; + this.backlog = backlog; + InetAddress!(false) addr; - this.socket = new AddressIPSocket!(); - super(node, conn_setup_params, - this.listener = new Listener( - addr(node.Address, node.Port), this.socket, conn_setup_params, - backlog - ) - ); + super(node, conn_setup_params); enforce(this.socket.updateAddress() == 0, "socket.updateAddress() failed!"); } diff --git a/src/swarm/node/model/Node.d b/src/swarm/node/model/Node.d index b99d87e77..ea20857c0 100644 --- a/src/swarm/node/model/Node.d +++ b/src/swarm/node/model/Node.d @@ -184,13 +184,10 @@ public abstract class INodeBase : INode, INodeInfo Note that the `error_dg` field should not be set by the user; it is set internally. To set a user-defined error callback, use the `error_callback` method - listener = select listener, is evaluated exactly once after - conn_setup_params have been populated ***************************************************************************/ - public this ( NodeItem node, ConnectionSetupParams conn_setup_params, - lazy ISelectListener listener ) + public this ( NodeItem node, ConnectionSetupParams conn_setup_params ) { this.node_item_ = node; @@ -199,7 +196,7 @@ public abstract class INodeBase : INode, INodeInfo conn_setup_params.error_dg = &this.error; - this.listener = listener; + this.listener = this.newListener(); this.record_action_counters_ = new RecordActionCounters(this.record_action_counter_ids); } @@ -312,6 +309,22 @@ public abstract class INodeBase : INode, INodeInfo } + /*************************************************************************** + + Restarts all listeners by reconstructing them. Assumed to be called + after the listeners have been shutdown via stopListener(). + + Note: this method should not be called frequently, as it creates new + sockets each time. + + ***************************************************************************/ + + public void restartListeners ( ) + { + this.listener = this.newListener(); + } + + /*************************************************************************** Shuts down the node. The base implementation does nothing. @@ -519,6 +532,16 @@ public abstract class INodeBase : INode, INodeInfo } + /*************************************************************************** + + Returns: + new ISelectListener instance for the legacy protocol + + ***************************************************************************/ + + protected abstract ISelectListener newListener ( ); + + /*************************************************************************** Returns: @@ -592,6 +615,12 @@ public class NodeBase ( ConnHandler : ISwarmConnectionHandler, private AddressIPSocket!() socket; + /// Connection setup params to pass to each legacy connection handler. + private ConnectionSetupParams conn_setup_params; + + /// Socket connection backlog. + private int backlog; + /*************************************************************************** Constructor @@ -605,16 +634,13 @@ public class NodeBase ( ConnHandler : ISwarmConnectionHandler, public this ( NodeItem node, Setup conn_setup_params, int backlog ) { - InetAddress!(false) addr; + this.conn_setup_params = conn_setup_params; + this.backlog = backlog; - this.socket = new AddressIPSocket!(); - this.listener = new Listener(addr(node.Address, node.Port), - this.socket, conn_setup_params, backlog); + super(node, conn_setup_params); enforce(this.socket.updateAddress() == 0, "socket.updateAddress() failed!"); - node.Port = this.socket.port(); - super(node, conn_setup_params, this.listener); } @@ -631,18 +657,15 @@ public class NodeBase ( ConnHandler : ISwarmConnectionHandler, public this ( ushort port, Setup conn_setup_params, int backlog ) { + this.conn_setup_params = conn_setup_params; + this.backlog = backlog; + NodeItem node; node.Port = port; - InetAddress!(false) addr; - - this.socket = new AddressIPSocket!(); - this.listener = new Listener(addr(node.Port), this.socket, conn_setup_params, - backlog); + super(node, conn_setup_params); enforce(this.socket.updateAddress() == 0, "socket.updateAddress() failed!"); - - super(node, conn_setup_params, this.listener); } @@ -685,6 +708,23 @@ public class NodeBase ( ConnHandler : ISwarmConnectionHandler, } } } + + + /*************************************************************************** + + Returns: + new ISelectListener instance for the legacy protocol + + ***************************************************************************/ + + protected override ISelectListener newListener ( ) + { + InetAddress!(false) addr; + this.socket = new AddressIPSocket!(); + return this.listener = new Listener( + addr(this.node_item.Address, this.node_item.Port), this.socket, + this.conn_setup_params, this.backlog); + } } From 003e1d3a1fa4f7b2ccc0c9e369b9f062a4ecbf12 Mon Sep 17 00:00:00 2001 From: Gavin Norman Date: Tue, 14 Aug 2018 11:18:44 +0200 Subject: [PATCH 3/6] Add NodeBase.restartListeners (As a counterpart to the existing stopListener.) --- src/swarm/node/model/INode.d | 10 ++++++++++ src/swarm/node/model/NeoNode.d | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/swarm/node/model/INode.d b/src/swarm/node/model/INode.d index b7442ba93..1f71a8441 100644 --- a/src/swarm/node/model/INode.d +++ b/src/swarm/node/model/INode.d @@ -66,6 +66,16 @@ public interface INode public void stopListener ( EpollSelectDispatcher epoll ); + /*************************************************************************** + + Restarts all listeners by reconstructing them. Assumed to be called + after the listeners have been shutdown via stopListener(). + + ***************************************************************************/ + + public void restartListeners ( ); + + /*************************************************************************** Performs any required shutdown behaviour for the node. diff --git a/src/swarm/node/model/NeoNode.d b/src/swarm/node/model/NeoNode.d index 128cf98fe..d97bbea70 100644 --- a/src/swarm/node/model/NeoNode.d +++ b/src/swarm/node/model/NeoNode.d @@ -341,6 +341,24 @@ public abstract class INodeBase : INode, INodeInfo } + /*************************************************************************** + + Restarts all listeners by reconstructing them. Assumed to be called + after the listeners have been shutdown via stopListener(). + + Note: this method should not be called frequently, as it creates new + sockets each time. + + ***************************************************************************/ + + public void restartListeners ( ) + { + this.listener = this.newListener(); + this.neo_listener = this.newNeoListener(); + this.unix_listener = this.newUnixSocketListener(); + } + + /*************************************************************************** Shuts down the node. The base implementation does nothing. From 22e2b205fce6988333cee948933536ee1a9c5262 Mon Sep 17 00:00:00 2001 From: Gavin Norman Date: Tue, 14 Aug 2018 11:20:17 +0200 Subject: [PATCH 4/6] Rename NodeBase.stopListener -> stopListeners There's more than one listener now. --- relnotes/stoplistener.deprecation.md | 7 +++++++ src/swarm/node/model/NeoNode.d | 13 +++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 relnotes/stoplistener.deprecation.md diff --git a/relnotes/stoplistener.deprecation.md b/relnotes/stoplistener.deprecation.md new file mode 100644 index 000000000..bff25e229 --- /dev/null +++ b/relnotes/stoplistener.deprecation.md @@ -0,0 +1,7 @@ +### Neo node's `stopListener` method renamed to `stopListeners` + +`swarm.node.model.NeoNode` + +The `INodeBase` class' `stopListener` method has been renamed to +`stopListeners`, for accuracy (the neo node has three listening sockets). + diff --git a/src/swarm/node/model/NeoNode.d b/src/swarm/node/model/NeoNode.d index d97bbea70..d545701d7 100644 --- a/src/swarm/node/model/NeoNode.d +++ b/src/swarm/node/model/NeoNode.d @@ -322,7 +322,20 @@ public abstract class INodeBase : INode, INodeInfo ***************************************************************************/ + deprecated("Use `stopListeners()` instead") public void stopListener ( EpollSelectDispatcher epoll ) + { + this.stopListeners(epoll); + } + + + /*************************************************************************** + + Shuts down the listeners and all connections. + + ***************************************************************************/ + + public void stopListeners ( EpollSelectDispatcher epoll ) { epoll.unregister(this.listener); this.listener.shutdown; From 357a7434a5ef01752aba8ccffe0b2ab017633e74 Mon Sep 17 00:00:00 2001 From: Gavin Norman Date: Tue, 14 Aug 2018 11:27:38 +0200 Subject: [PATCH 5/6] Add turtle.env.model.TestNode This is intended as a replacement of the existing turtle.env.model.Node. The reason for the new class is that the turtle/swarm node framework is currently fairly complicated, with several different classes required to implement a fake node. The new TestNode derives directly from NodeBase (the standard base class for swarm nodes), whereas the older Node does not. This means that to implement a fake node using TestNode, one does not have to separately write a class deriving from NodeBase. --- relnotes/turtlenode.deprecation.md | 6 + src/turtle/env/model/Node.d | 1 + src/turtle/env/model/TestNode.d | 256 +++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 relnotes/turtlenode.deprecation.md create mode 100644 src/turtle/env/model/TestNode.d diff --git a/relnotes/turtlenode.deprecation.md b/relnotes/turtlenode.deprecation.md new file mode 100644 index 000000000..c542af7ce --- /dev/null +++ b/relnotes/turtlenode.deprecation.md @@ -0,0 +1,6 @@ +### New turtle node env class + +`turtle.env.model.Node` + +Usage of this class should be replaced with the new `turtle.env.model.TestNode`. + diff --git a/src/turtle/env/model/Node.d b/src/turtle/env/model/Node.d index af0014073..9f6816085 100644 --- a/src/turtle/env/model/Node.d +++ b/src/turtle/env/model/Node.d @@ -34,6 +34,7 @@ import ocean.core.Verify; *******************************************************************************/ +deprecated("Use turtle.env.model.TestNode instead") public abstract class Node ( NodeType, istring id ) : ITurtleEnv { import swarm.neo.AddrPort; diff --git a/src/turtle/env/model/TestNode.d b/src/turtle/env/model/TestNode.d new file mode 100644 index 000000000..edff3f5bc --- /dev/null +++ b/src/turtle/env/model/TestNode.d @@ -0,0 +1,256 @@ +/******************************************************************************* + + Abstract fake node for integration with turtle's registry of env additions. + + Provides the following features: + * Integrates with turtle's env node registry. + * Prevents construction of multiple instances. (A test should only need + one.) + * Methods to start, stop, and restart the node. + * A method to generate legacy and neo config files for clients to connect + with the node. + + To implement a fake node: + * Derive from this class, providing your connection handler as the template + argument. + * Add a storage engine and methods to directly read from and write to it. + (The application being tested should communicate with the fake node via + the standard network protocol, but the test process itself can set up the + data required by different test cases simply by writing directly into the + fake node's storage.) + * Implement the abstract `clear()` method, to remove all data. + * Implement the other abstract methods of NodeBase (see + swarm.node.model.NeoNode). + + Copyright: + Copyright (c) 2015-2018 dunnhumby Germany GmbH. All rights reserved. + + License: + Boost Software License Version 1.0. See LICENSE.txt for details. + +*******************************************************************************/ + +module turtle.env.model.TestNode; + +import ocean.transition; +import ocean.text.convert.Formatter; +import turtle.env.model.Registry; +import ocean.core.Verify; +import swarm.node.model.NeoNode; +import swarm.node.connection.ConnectionHandler; + +/******************************************************************************* + + Abstract fake node for integration with turtle's registry of env additions. + + Also includes methods for starting and stopping the fake node. + + Note: this class and derivatives are only used when running tests which need + to *access* a node (the turtle env addition provides a fake node that can + be inspected and modified by test cases). It is not relevant when running + tests *on* a node implementation itself. + + Params: + ConnHandler = node connection handler type + +*******************************************************************************/ + +public abstract class TestNode ( ConnHandler : ISwarmConnectionHandler ) + : NodeBase!(ConnHandler), ITurtleEnv +{ + import swarm.neo.AddrPort; + import ocean.io.device.File; + import ocean.core.Buffer; + import ocean.net.server.SelectListener; + import ocean.task.Scheduler; + import ocean.task.Task; + import turtle.env.Shell; + import Integer = ocean.text.convert.Integer_tango; + import ocean.io.select.client.model.ISelectClient : IAdvancedSelectClient; + import ocean.net.server.connection.IConnectionHandlerInfo; + import ocean.io.select.protocol.generic.ErrnoIOException; + import ocean.util.log.Logger; + + /// Enum defining the possibles states of the fake node service. + private enum State + { + Init, + Running, + Stopped + } + + /// State of the fake node service. + private State state; + + /// Used to prevent creating multiple fake nodes of the same type. + static bool already_created = false; + + /// Flag indicating that unhandled exceptions from the node must be printed + /// in test suite trace. + private bool log_errors = true; + + /// Logger for turtle nodes of this type. + private Logger log; + + /*************************************************************************** + + Constructor + + Params: + node = node addres & port + neo_port = port of neo listener (same address as above) + conn_setup_params = connection handler constructor arguments + options = options for the neo node and connection handlers + backlog = (see ISelectListener ctor) + + ***************************************************************************/ + + public this ( AddrPort node, ushort neo_port, + ConnectionSetupParams conn_setup_params, Options options, int backlog ) + { + verify(!already_created, "Can only have one " ~ idup(this.id) ~ + " per turtle test app"); + already_created = true; + + auto addr_buf = new char[AddrPort.AddrBufLength]; + super(node.asNodeItem(addr_buf), neo_port, conn_setup_params, options, + backlog); + this.error_callback = &this.onError; + + this.log = Log.lookup(this.id); + } + + /*************************************************************************** + + Starts the fake node as part of test suite event loop. It will + only terminate when whole test suite process dies. + + ***************************************************************************/ + + public void start ( ) + { + verify(this.state == State.Init, "Node has already been started"); + this.state = State.Running; + turtle_env_registry.register(this); + + this.register(theScheduler.epoll); + if ( Task.getThis() ) + theScheduler.processEvents(); + } + + /*************************************************************************** + + Restarts the fake node, reopening the listening socket on the same port + determined in the initial call to start(). + + Note: Restarting the node *does not* clear any data in its storage + engine. To do that, consult the methods of the derived class. + + ***************************************************************************/ + + public void restart ( ) + { + with ( State ) switch ( this.state ) + { + case Stopped: + break; + case Running: + this.stop(); + break; + case Init: + default: + verify(false, "Node has not been started yet"); + break; + } + + this.restartListeners(); + this.register(theScheduler.epoll); + turtle_env_registry.register(this); + + this.state = State.Running; + } + + /*************************************************************************** + + Stops the fake node service. The node may be started again on the same + port via restart(). + + ***************************************************************************/ + + final public void stop ( ) + { + verify(this.state == State.Running, "Node is not running"); + + this.stopListeners(theScheduler.epoll); + this.shutdown(); + this.unregister(); // Remove from turtle nodes registry + this.state = State.Stopped; + } + + /*************************************************************************** + + Generate nodes files for the fake nodes. If the node supports the neo + protocol then the neo nodes file will also be written. The files are + named: + * this.id ~ ".nodes" + * this.id ~ ".neo.nodes" + + Params: + directory = The directory the files will be written to. + + ***************************************************************************/ + + public void genConfigFiles ( cstring directory ) + { + shell("mkdir -p " ~ directory); + + auto legacyfile = new File(directory ~ "/" ~ this.id ~ ".nodes", + File.WriteCreate); + scope (exit) legacyfile.close(); + + legacyfile.write(this.node_item.Address ~ ":" ~ + Integer.toString(this.node_item.Port)); + legacyfile.write("\n"); + + auto neofile = new File(directory ~ "/" ~ this.id ~ ".neo.nodes", + File.WriteCreate); + scope (exit) neofile.close(); + + neofile.write(this.node_item.Address ~ ":" ~ + Integer.toString(this.neo_address.port)); + neofile.write("\n"); + } + + /*************************************************************************** + + ITurtleEnv interface method implementation. Should not be called + manually. + + Uses turtle env addition registry to stop tracking errors after all + tests have finished. This is necessary because applications don't do + clean connection shutdown when terminating, resulting in socket errors + being reported on node side. + + ***************************************************************************/ + + public void unregister ( ) + { + this.log_errors = false; + } + + /*************************************************************************** + + Log errors, if logging is enabled. + + ***************************************************************************/ + + private void onError ( Exception exception, IAdvancedSelectClient.Event, + IConnectionHandlerInfo ) + { + if (!this.log_errors) + return; + + this.log.warn("Ignoring exception: {} ({}:{})", + exception.message(), exception.file, exception.line); + } +} From 62c24780a4cfa653289c266735f4f3b4fcaff032 Mon Sep 17 00:00:00 2001 From: Gavin Norman Date: Tue, 14 Aug 2018 11:28:28 +0200 Subject: [PATCH 6/6] Adapt the turtle env node test to use the new TestNode --- integrationtest/turtleenv/main.d | 244 ++++++++++++++++++++++++------- 1 file changed, 188 insertions(+), 56 deletions(-) diff --git a/integrationtest/turtleenv/main.d b/integrationtest/turtleenv/main.d index ca7c38d37..dc7c3ea6b 100644 --- a/integrationtest/turtleenv/main.d +++ b/integrationtest/turtleenv/main.d @@ -1,6 +1,6 @@ /******************************************************************************* - Test of the abstract turtle node extension. + Test of the turtle swarm node extension. Copyright: Copyright (c) 2018 dunnhumby Germany GmbH. All rights reserved. @@ -12,103 +12,235 @@ module integrationtest.turtleenv.main; -import swarm.neo.AddrPort; -import turtle.env.model.Node; +import ocean.transition; +import ocean.task.Scheduler; +import ocean.task.Task; import ocean.util.test.DirectorySandbox; -import ocean.core.Test; -import ocean.io.device.File; +import swarm.Const : ICommandCodes, NodeItem; +import swarm.node.connection.ConnectionHandler; +import turtle.env.model.TestNode; -/// Node used to the turtle test -private class TurtleNode +version (UnitTest){} +else +void main() { - /// Address and the port of the node - AddrPort addrport; - AddrPort neo_address; + auto sandbox = DirectorySandbox.create(); + scope (success) + sandbox.remove(); - this (AddrPort addrport) - { - this.addrport = addrport; - this.neo_address = AddrPort(this.addrport.address()); - this.neo_address.port = cast(ushort)(this.addrport.port() + 100); - } + initScheduler(Scheduler.Configuration.init); + + auto node = new MyNode("127.0.0.1", 10000); + node.start(); + + theScheduler.schedule(new Tests(node)); + theScheduler.eventLoop(); } -/// The turlte TurtleNode class -private class TestNode : Node!(TurtleNode, "turtleNode") +/******************************************************************************* + + Task that performs tests on the test node passed to the ctor. + +*******************************************************************************/ + +class Tests : Task { - /*********************************************************************** + import integrationtest.neo.client.Client; + import ocean.core.Test; + import ocean.io.device.File; - Creates a fake node at the specified address/port. + /// Node instance to test. + private MyNode node; + + /// Client instance to use for checking network availability of the node. + private Client client; + + /*************************************************************************** + + Constructor. Params: - node_item = address/port + node = node to test - ***********************************************************************/ + ***************************************************************************/ - override protected TurtleNode createNode ( AddrPort addrport ) + public this ( MyNode node ) { - return new TurtleNode(addrport); + this.node = node; } - /*********************************************************************** + /*************************************************************************** + + Task entry point. Runs a series of tests on the node then shuts down the + scheduler. + + ***************************************************************************/ + + public override void run ( ) + { + // Test config file generation. + this.node.genConfigFiles("."); + test!("==")(File.get("testnode.nodes"), "127.0.0.1:9999\n"); + test!("==")(File.get("testnode.neo.nodes"), "127.0.0.1:10000\n"); + + // Initialise client and connect. + this.client = new Client(theScheduler.epoll, "127.0.0.1", 10000, + &this.connNotifier); + client.blocking.waitAllNodesConnected(); + + // Try to talk to the node. + auto ok = this.talkToNode(); + test(ok); + + // Stop the node, then try to talk to it (failure expected). + this.node.stop(); + ok = this.talkToNode(); + test(!ok); + + // Restart the node, reconnect the client, then try to talk to the node. + this.node.restart(); + client.blocking.waitAllNodesConnected(); + ok = this.talkToNode(); + test(ok); + + // Finished. + theScheduler.shutdown(); + } + + /*************************************************************************** + + Uses the client to put a record to the node, then read it back. Returns: - address/port on which node is listening + true if everything succeeded, false on error - ***********************************************************************/ + ***************************************************************************/ - override public AddrPort node_addrport ( ) + private bool talkToNode ( ) { - assert(this.node); - return this.node.addrport; + auto ok = client.blocking.put(1, "hello", + ( Client.Neo.Put.Notification, Const!(Client.Neo.Put.Args) ) { }); + if ( !ok ) + return false; + + void[] value; + ok = client.blocking.get(1, value, + ( Client.Neo.Get.Notification, Const!(Client.Neo.Get.Args) ) { }); + if ( !ok || value != "hello" ) + return false; + + return true; } - /*********************************************************************** + /*************************************************************************** - Fake node service stop implementation. + Dummy connection notifier. Required by the client, but unused. - ***********************************************************************/ + ***************************************************************************/ - protected override void stopImpl ( ) + private void connNotifier ( Client.Neo.ConnNotification info ) { } +} - /*********************************************************************** +/******************************************************************************* - Removes all data from the fake node service. + Test node implementing the protocol defined in integrationtest.neo.node. - ***********************************************************************/ +*******************************************************************************/ + +public class MyNode : TestNode!(ConnHandler) +{ + import swarm.neo.AddrPort; - override public void clear ( ) + import integrationtest.neo.node.Node; + import integrationtest.neo.node.Storage; + import integrationtest.neo.node.request.Get; + import integrationtest.neo.node.request.Put; + + /*************************************************************************** + + Constructor. + + Params: + addr = address to bind to + neo_port = port to bind to for neo protocol (legacy protocol binds + to a port one lower) + + ***************************************************************************/ + + public this ( cstring addr, ushort neo_port ) { + // In this simple example node implementation, we don't need any shared + // resources except the reference to the storage. + this.shared_resources = new Storage; + + Options options; + options.epoll = theScheduler.epoll; + options.requests.addHandler!(GetImpl_v0)(); + options.credentials_map["dummy"] = Key.init; + options.shared_resources = this.shared_resources; + + options.requests.addHandler!(GetImpl_v0)(); + options.requests.addHandler!(PutImpl_v0)(); + + const backlog = 1_000; + AddrPort legacy_addr_port; + legacy_addr_port.setAddress(addr); + legacy_addr_port.port = cast(ushort)(neo_port - 1); + super(legacy_addr_port, neo_port, new ConnectionSetupParams, + options, backlog); } - /*********************************************************************** + /*************************************************************************** + + Returns: + identifier string for this node - Suppresses log output from the fake node if used version of proto - supports it. + ***************************************************************************/ + + protected override cstring id ( ) + { + return "testnode"; + } + + /*************************************************************************** + + Scope allocates a request resource acquirer backed by the protected + `shared_resources`. (Passed as a generic Object to avoid templatising + this class and others that depend on it.) + + Params: + handle_request_dg = delegate that receives a resources acquirer and + initiates handling of a request - ***********************************************************************/ + ***************************************************************************/ - override public void log_errors ( bool log_errors ) + override protected void getResourceAcquirer ( + void delegate ( Object resource_acquirer ) handle_request_dg ) { - static if (is(typeof(this.node.log_errors(log_errors)))) - this.node.log_errors(log_errors); + handle_request_dg(this.shared_resources); } } -version (UnitTest){} -else -void main() +/******************************************************************************* + + Legacy protocol connection handler. Required by NodeBase but unused in this + example. + +*******************************************************************************/ + +private class ConnHandler : ConnectionHandlerTemplate!(ICommandCodes) { - auto sandbox = DirectorySandbox.create(); - scope (success) - sandbox.remove(); + import ocean.net.server.connection.IConnectionHandler; + + public this ( void delegate(IConnectionHandler) finaliser, + ConnectionSetupParams params ) + { + super(finaliser, params); + } - auto node = new TestNode(); - node.start("127.0.0.1", 10000); - node.genConfigFiles("."); + override protected void handleCommand () {} - test!("==")(File.get("turtleNode.nodes"), "127.0.0.1:10000\n"); - test!("==")(File.get("turtleNode.neo.nodes"), "127.0.0.1:10100\n"); + override protected void handleNone () {} }