diff --git a/integrationtest/turtleenv/main.d b/integrationtest/turtleenv/main.d index ca7c38d3..f062b36e 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. @@ -13,87 +13,110 @@ module integrationtest.turtleenv.main; import swarm.neo.AddrPort; -import turtle.env.model.Node; +import turtle.env.model.TestNode; +import ocean.transition; import ocean.util.test.DirectorySandbox; import ocean.core.Test; import ocean.io.device.File; +import swarm.node.connection.ConnectionHandler; -/// Node used to the turtle test -private class TurtleNode +/// Connection handler stub +private class MyConnHandler : ISwarmConnectionHandler { - /// Address and the port of the node - AddrPort addrport; - AddrPort neo_address; + public this ( FinalizeDg finalize_dg, ConnectionSetupParams setup ) + { + super(finalize_dg, setup); + } - this (AddrPort addrport) + protected override void handleCommand ( ) { - this.addrport = addrport; - this.neo_address = AddrPort(this.addrport.address()); - this.neo_address.port = cast(ushort)(this.addrport.port() + 100); } } -/// The turlte TurtleNode class -private class TestNode : Node!(TurtleNode, "turtleNode") +/// Concrete turtle swarm node class +private class MyNode : TestNode!(MyConnHandler) { - /*********************************************************************** + import swarm.neo.node.IRequestHandler : IRequest; - Creates a fake node at the specified address/port. - - Params: - node_item = address/port + /// Dummy request handler class. Required by base class + private static class DummyRequest : IRequest + { + import swarm.neo.node.RequestOnConn; + import swarm.neo.request.Command; - ***********************************************************************/ + static const name = "dummy"; + static const Command command = Command.init; - override protected TurtleNode createNode ( AddrPort addrport ) - { - return new TurtleNode(addrport); + override void handle ( RequestOnConn connection, Object resources, + Const!(void)[] init_payload ) + { + } } - /*********************************************************************** + /*************************************************************************** - Returns: - address/port on which node is listening + 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) - override public AddrPort node_addrport ( ) + ***************************************************************************/ + + public this ( AddrPort node ) { - assert(this.node); - return this.node.addrport; + ushort neo_port = node.port + 100; + auto epoll = new EpollSelectDispatcher; + auto setup = new ConnectionSetupParams; + setup.epoll = epoll; + Options options; + options.epoll = epoll; + options.credentials_map["test"] = Key.init; + options.requests.addHandler!(DummyRequest); + int backlog = 1024; + super(node, neo_port, setup, options, backlog); } /*********************************************************************** - Fake node service stop implementation. + Removes all data from the fake node service. ***********************************************************************/ - protected override void stopImpl ( ) + override public void clear ( ) { } - /*********************************************************************** + /*************************************************************************** - Removes all data from the fake node service. + Returns: + identifier string for this node - ***********************************************************************/ + ***************************************************************************/ - override public void clear ( ) + protected override cstring id ( ) { + return "turtleNode"; } - /*********************************************************************** + /*************************************************************************** - Suppresses log output from the fake node if used version of proto - supports it. + 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 ) + protected override 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); } } @@ -105,8 +128,10 @@ void main() scope (success) sandbox.remove(); - auto node = new TestNode(); - node.start("127.0.0.1", 10000); + AddrPort addr; + addr.setAddress("127.0.0.1"); + addr.port = 10000; + auto node = new MyNode(addr); node.genConfigFiles("."); test!("==")(File.get("turtleNode.nodes"), "127.0.0.1:10000\n"); diff --git a/src/turtle/env/model/TestNode.d b/src/turtle/env/model/TestNode.d new file mode 100644 index 00000000..b13b1292 --- /dev/null +++ b/src/turtle/env/model/TestNode.d @@ -0,0 +1,249 @@ +/******************************************************************************* + + Abstract fake node for integration with turtle's registry of env additions. + + 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 which can + be inspected and modified by test cases). It is not relevant when running + tests *on* a node implementation itself. + +*******************************************************************************/ + +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 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 shared by all turtle nodes of this type. + static private Logger log; + + /*************************************************************************** + + Static constructor. + + ***************************************************************************/ + + static this ( ) + { + log = Log.lookup("fakedmq"); + } + + /*************************************************************************** + + 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 " ~ this.id ~ " per turtle test app"); + already_created = true; + + super(node, neo_port, conn_setup_params, options, backlog); + this.error_callback = &this.onError; + } + + /*************************************************************************** + + 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); + theScheduler.processEvents(); + } + + /*************************************************************************** + + Restarts the fake node, reopening the listening socket on the same port + determined in the initial call to start(). + + Notes: + 1. Restarting the node *does not* clear any data in its storage + engine. To do that, call clear(). + 2. You must call stop() first, before calling restart(). + + ***************************************************************************/ + + public void restart ( ) + { + verify(this.state == State.Stopped, "Node has not been stopped"); + + this.register(theScheduler.epoll); + // TODO: do we need to do anything to restart the listeners? (they've been shutdown) + + 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.stopListener(theScheduler.epoll); + this.shutdown(); + 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. + + 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(); + + auto node_address = format("{}.{}.{}.{}", + this.addr_port.address_bytes[0], + this.addr_port.address_bytes[1], + this.addr_port.address_bytes[2], + this.addr_port.address_bytes[3]); + + legacyfile.write(node_address ~ ":" ~ + Integer.toString(this.addr_port.port)); + legacyfile.write("\n"); + + auto neofile = new File(directory ~ "/" ~ this.id ~ ".neo.nodes", + File.WriteCreate); + scope (exit) neofile.close(); + + neofile.write(node_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 socker errors + being reported on node side. + + ***************************************************************************/ + + public void unregister ( ) + { + this.log_errors = false; + } + + /*************************************************************************** + + Make any error fatal + + ***************************************************************************/ + + private void onError ( Exception exception, IAdvancedSelectClient.Event, + IConnectionHandlerInfo ) + { + if (!this.log_errors) + return; + + log.warn("Ignoring exception: {} ({}:{})", + exception.message(), exception.file, exception.line); + + // socket errors can be legitimate, for example if client has terminated + // the connection early + if (cast(IOWarning) exception) + return; + + // can be removed in next major version + version(none) + { + // anything else is unexpected, die at once + abort(); + } + } + + /*************************************************************************** + + Removes all data from the fake node service. + + ***************************************************************************/ + + abstract public void clear ( ); +}