Skip to content

Commit

Permalink
Add turtle.env.model.TestNode
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Gavin Norman committed Aug 14, 2018
1 parent ff2bfc3 commit a212c52
Show file tree
Hide file tree
Showing 2 changed files with 274 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/turtle/env/model/Node.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
273 changes: 273 additions & 0 deletions src/turtle/env/model/TestNode.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
/*******************************************************************************
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 shared by all turtle nodes of this type.
static private Logger log;

/***************************************************************************
Static constructor.
***************************************************************************/

static this ( )
{
log = Log.lookup("testnode");
}

/***************************************************************************
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;

auto addr_buf = new char[AddrPort.AddrBufLength];
super(node.asNodeItem(addr_buf), 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);
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, call clear().
***************************************************************************/

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;

log.warn("Ignoring exception: {} ({}:{})",
exception.message(), exception.file, exception.line);
}

/***************************************************************************
Removes all data from the fake node service.
***************************************************************************/

abstract public void clear ( );
}

0 comments on commit a212c52

Please sign in to comment.