sneeze

Easily join SWIM networks

Usage no npm install needed!

<script type="module">
  import sneeze from 'https://cdn.skypack.dev/sneeze';
</script>

README

sneeze

Npm Travis Gitter NpmFigs

Easily join SWIM networks. See http://www.cs.cornell.edu/~asdas/research/dsn02-SWIM.pdf.

This module is used by seneca-mesh to provide zero-configuration service discovery. A usage example is provided by the 14 microservice ramanujan Twitter clone (including a Docker Swarm configuratiion).

It is also used by the wo hapi plugin for upstream proxy discovery. There's also a nice example in that repo.

Also included is a very useful terminal monitor so that you can see the exact status of your network:

Quick Example

The base node serves as the well-known starting point. As other nodes (A and B) join and leave. All nodes eventually learn about them.

// base.js - start first
var base = require('sneeze')({isbase:true})
base.join()

// nodeA.js - start next
var nodeA = require('sneeze')()
nodeA.on('add',console.log)
nodeA.on('remove',console.log)
nodeA.join({name: 'A'}) // put any data you like in here

// nodeB.js - start last, then stop nodeA
var nodeB = require('sneeze')()
nodeB.on('add',console.log)
nodeB.on('remove',console.log)
nodeB.join({name: 'B'})

Usage

This module is designed to be used by other modules that want to expose functionality to a network of peer nodes. It does not provide communication capabilities. It's sole purpose is to communicate meta data on startup to other nodes. You use that meta data to establish communication - it might the host and port on which you are exposing a web service end point.

Sneeze uses the excellent swim-js module as the implementation of the SWIM algorithm. Communication between nodes of the SWIM network is via UDP, so you need to make sure your network allows UDP transit.

Base nodes

The purpose of a SWIM network is automatic discovery without needing any configuration or service registries. You can join the network just by connecting with any single node. Of course, you have to know about at least one node.

To make this easier, Sneeze provides the concept of base nodes. Base nodes should be delployed at well-known network locations (maybe using a fixed domain name in your local DNS). New Sneeze instances can then join the network simply by connecting to a base node. You can deploy multiple base nodes for redundancy. They are only used for initial joining and are not used for service discovery. This means you can "chaos monkey" your base nodes without causing problems, so long as at least one is live.

IMPORTANT

The host name of each Sneeze instance is used as part of it's network wide identification. Be careful to ensure that the host name is consistent on all machines in your network.

If a Sneeze instance cannot join a network, it will retry a set number of times, and then give up. Often the failure to join is because the rest of the network cannot reply to the new Sneeze instance, due to host name inconsistency. This is common problem with overlay networks that provide load-balancing IPs (for example: Docker Swarm).

Sneeze assumes a flat network structure. Everybody is on the same network, and can reach everybody else.

Using windows? Sneeze uses some native modules, so make sure to configure msbuild.

Basic Examples

The test folder contains some simple examples. See the comments inside the scripts.

See also the more complex examples mentioned above [sneeze].

API

Sneeze(options)

Create a new Sneeze instance, passing in an options object. You can create multiple Sneeze instances in the same process.

This constructor is exposed by the module:

var Sneeze = require('sneeze')

var sneeze_instance = Sneeze()

See below for the options.

sneeze.join(meta)

Join a SWIM network. The meta object is meta data about this instances that will be shared with all other members of the network.

sneeze.members()

Returns an array of member description objects. These are the currently known and healthy members.

sneeze.leave()

Leave the network. You can also leave just by exiting the process - SWIM is designed to handle that. This method is just to give you extra control.

seneca.info

This public member variable is null until the instance sucessfully joins a network. It is descriptive object containing meta data about this instance.

Options

The top level options that you can provide to Sneeze are:

isbase: false

If true, become a base node.

host: 127.0.0.1

Host name of the instance.

port: 39999

Port of the instance. If not a base node, then this chosen randomly if undefined. Normally, you leave this undefined and let Sneeze work out what port to use. NOTE: this is the SWIM UDP port, not the port you use for communication.

bases: ['127.0.0.1:39999']

The list of base nodes, in the format host:port. The Sneeze instance will try to join the network by contacting these nodes.

silent: true

If false, print logs of debugging information about SWIM network events. Useful if your network configuration is causing headaches.

tag: null

You can have multiple Sneeze networks running at the same time. Each network can have its own tag. Members with different tags will ignore each other.

A tag of null means observe all other members, regardless of tag. This is what you need for base nodes, a repl, or monitoring - see below.

identifier: null

You can provide a unique identifier for your instance. This is generated automatically if you do not provide one.

v: null

If you leave and rejoin a network, within the same process, using the same identifier, you should provide different version number v so that any changed configuration is disseminated to the network.

monitor: {}

Print a live monitoring table to the terminal. This lists all known instances, and shows their status. More below.

Monitoring

To turn on monitoring, run an instance (assuming default bases, etc) using:

// file: monitor.js
var Sneeze = require('sneeze')

var sneeze = Sneeze({
  monitor: {
    active: true
  }
})

sneeze.join()

This instance will print a table of all known instances to the terminal window and update the table dynamically as instances come and go. It is very useful for sanity checking your network.

The table columns are:

  • host: SWIM host and UDP port
  • a: number of add events for this node
  • r: number of remove events for this node
  • s: node status: A: alive, S: suspect, F: failed, U: unknown
  • time: milliseconds since the monitor started
  • tag: Sneeze network tag
  • meta: custom node meta data (see below)
  • id: the node identifier

The custom node meta data is picked out from the instance meta data using JsonPath expressions:

var sneeze = Sneeze({
  monitor: {
    active: true,
    meta: ['foo.bar']
  }
})

This will print the value of foo: {bar: 123} in the instance meta data.

Keys

Ctrl-C

Shutdown the monitor process.

p

Prune failed nodes from the table.

Questions?

@rjrodger