Browse Source

Merge pull request #69 from tendermint/master

Backmerge to jenkins branch
pull/1943/head
Greg Szabo 7 years ago
committed by GitHub
parent
commit
f297602c14
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1276 additions and 1232 deletions
  1. +3
    -2
      .gitignore
  2. +3
    -0
      CODEOWNERS
  3. +1
    -1
      README.md
  4. +0
    -202
      ansible/README.md
  5. +291
    -0
      ansible/README.rst
  6. +0
    -0
      ansible/assets/a_plus_t.png
  7. +1
    -1
      ansible/roles/config/templates/config.toml.j2
  8. +0
    -52
      create-digitalocean-testnet.sh
  9. +0
    -95
      docker/README.md
  10. +71
    -0
      docker/README.rst
  11. +0
    -14
      docker/basecoin/Dockerfile
  12. +0
    -229
      mintnet-kubernetes/README.md
  13. +290
    -0
      mintnet-kubernetes/README.rst
  14. +0
    -0
      mintnet-kubernetes/assets/gce1.png
  15. +0
    -0
      mintnet-kubernetes/assets/gce2.png
  16. +0
    -0
      mintnet-kubernetes/assets/statefulset.png
  17. +0
    -0
      mintnet-kubernetes/assets/t_plus_k.png
  18. +0
    -15
      mintnet-kubernetes/docs/SETUP_K8S_ON_DO.md
  19. +0
    -31
      mintnet-kubernetes/docs/SETUP_K8S_ON_GCE.md
  20. +0
    -4
      terraform-aws/README.md
  21. +0
    -66
      terraform-digitalocean/README.md
  22. +111
    -0
      terraform-digitalocean/README.rst
  23. +1
    -1
      tm-bench/Dockerfile
  24. +1
    -1
      tm-bench/Dockerfile.dev
  25. +1
    -0
      tm-bench/LICENSE
  26. +15
    -14
      tm-bench/Makefile
  27. +0
    -64
      tm-bench/README.md
  28. +159
    -0
      tm-bench/README.rst
  29. +64
    -77
      tm-bench/glide.lock
  30. +12
    -8
      tm-bench/glide.yaml
  31. +5
    -4
      tm-bench/main.go
  32. +61
    -22
      tm-bench/transacter.go
  33. +1
    -1
      tm-monitor/Dockerfile
  34. +1
    -1
      tm-monitor/Dockerfile.dev
  35. +1
    -0
      tm-monitor/LICENSE
  36. +15
    -15
      tm-monitor/Makefile
  37. +0
    -123
      tm-monitor/README.md
  38. +1
    -0
      tm-monitor/README.rst
  39. +110
    -138
      tm-monitor/eventmeter/eventmeter.go
  40. +33
    -10
      tm-monitor/glide.lock
  41. +3
    -4
      tm-monitor/glide.yaml
  42. +2
    -2
      tm-monitor/main.go
  43. +5
    -6
      tm-monitor/mock/eventmeter.go
  44. +3
    -3
      tm-monitor/monitor/monitor_test.go
  45. +5
    -16
      tm-monitor/monitor/node.go
  46. +5
    -8
      tm-monitor/monitor/node_test.go
  47. +1
    -2
      tm-monitor/rpc.go

+ 3
- 2
.gitignore View File

@ -1,5 +1,6 @@
*/vendor
*/.glide
.terraform
terraform.tfstate
terraform.tfstate.backup
terraform.tfstate.d
terraform.tfstate.d

+ 3
- 0
CODEOWNERS View File

@ -0,0 +1,3 @@
* @melekes @Greg-Szabo
*.md @zramsay
*.rst @zramsay

+ 1
- 1
README.md View File

@ -1,2 +1,2 @@
# tools
Tools for working with tendermint and associated technologies
Tools for working with tendermint and associated technologies. See the documentation at: http://tendermint.readthedocs.io/en/master/index.html#tendermint-tools

+ 0
- 202
ansible/README.md View File

@ -1,202 +0,0 @@
# Ansible playbook for Tendermint applications
![Ansible plus Tendermint](img/a_plus_t.png)
* [Prerequisites](#Prerequisites)
* [Ansible setup](#Ansible setup)
* [Running the playbook](#Running the playbook)
The playbooks in this folder run [ansible](http://www.ansible.com/) roles which:
* install and configure basecoin or ethermint
* start/stop basecoin or ethermint and reset their configuration
## Prerequisites
* Ansible 2.0 or higher
* SSH key to the servers
Optional for DigitalOcean droplets:
* DigitalOcean API Token
* python dopy package
Head over to the [Terraform folder](https://github.com/tendermint/tools/tree/master/terraform-digitalocean) for a description on how to get a DigitalOcean API Token.
Optional for Amazon AWS instances:
* Amazon AWS API access key ID and secret access key.
The cloud inventory scripts come from the ansible team at their [GitHub](https://github.com/ansible/ansible) page. You can get the latest version from the contrib/inventory folder.
## Ansible setup
Ansible requires a "command machine" or "local machine" or "orchestrator machine" to run on. This can be your laptop or any machine that can run ansible. (It does not have to be part of the cloud network that hosts your servers.)
Use the official [Ansible installation guide](http://docs.ansible.com/ansible/intro_installation.html) to install Ansible. Here are a few examples on basic installation commands:
Ubuntu/Debian:
```
sudo apt-get install ansible
```
CentOS/RedHat:
```
sudo yum install epel-release
sudo yum install ansible
```
Mac OSX:
If you have (Homebrew)[https://brew.sh] installed, then it's simply
```
brew install ansible
```
If not, you can install it using `pip`:
```
sudo easy_install pip
sudo pip install ansible
```
To make life easier, you can start an SSH Agent and load your SSH key(s). This way ansible will have an uninterrupted way of connecting to your servers.
```
ssh-agent > ~/.ssh/ssh.env
source ~/.ssh/ssh.env
ssh-add private.key
```
Subsequently, as long as the agent is running, you can use `source ~/.ssh/ssh.env` to load the keys to the current session.
Note: On Mac OSX, you can add the `-K` option to ssh-add to store the passphrase in your keychain. The security of this feature is debated but it is convenient.
### Optional cloud dependencies
If you are using a cloud provider to host your servers, you need the below dependencies installed on your local machine.
#### DigitalOcean inventory dependencies:
Ubuntu/Debian:
```
sudo apt-get install python-pip
sudo pip install dopy
```
CentOS/RedHat:
```
sudo yum install python-pip
sudo pip install dopy
```
Mac OSX:
```
sudo pip install dopy
```
#### Amazon AWS inventory dependencies:
Ubuntu/Debian:
```
sudo apt-get install python-boto
```
CentOS/RedHat:
```
sudo yum install python-boto
```
Mac OSX:
```
sudo pip install boto
```
## Refreshing the DigitalOcean inventory
If you just finished creating droplets, the local DigitalOcean inventory cache is not up-to-date. To refresh it, run:
```
DO_API_TOKEN="<The API token received from DigitalOcean>"
python -u inventory/digital_ocean.py --refresh-cache 1> /dev/null
```
## Refreshing the Amazon AWS inventory
If you just finished creating Amazon AWS EC2 instances, the local AWS inventory cache is not up-to-date. To refresh it, run:
```
AWS_ACCESS_KEY_ID='<The API access key ID received from Amazon>'
AWS_SECRET_ACCESS_KEY='<The API secret access key received from Amazon>'
python -u inventory/ec2.py --refresh-cache 1> /dev/null
```
Note: you don't need the access key and secret key set, if you are running ansible on an Amazon AMI instance with the proper IAM permissions set.
## Running the playbooks
The playbooks are locked down to only run if the environment variable `TF_VAR_TESTNET_NAME` is populated. This is a precaution so you don't accidentally run the playbook on all your servers.
The variable `TF_VAR_TESTNET_NAME` contains the testnet name which ansible translates into an ansible group. If you used Terraform to create the servers, it was the testnet name used there.
If the playbook cannot connect to the servers because of public key denial, your SSH Agent is not set up properly. Alternatively you can add the SSH key to ansible using the `--private-key` option.
If you need to connect to the nodes as root but your local username is different, use the ansible option `-u root` to tell ansible to connect to the servers and authenticate as the root user.
If you secured your server and you need to `sudo` for root access, use the the `-b` or `--become` option to tell ansible to sudo to root after connecting to the server. In the Terraform-DigitalOcean example, if you created the ec2-user by adding the `noroot=true` option (or if you are simply on Amazon AWS), you need to add the options `-u ec2-user -b` to ansible to tell it to connect as the ec2-user and then sudo to root to run the playbook.
### DigitalOcean
```
DO_API_TOKEN="<The API token received from DigitalOcean>"
TF_VAR_TESTNET_NAME="testnet-servers"
ansible-playbook -i inventory/digital_ocean.py install.yml -e service=basecoin
```
### Amazon AWS
```
AWS_ACCESS_KEY_ID='<The API access key ID received from Amazon>'
AWS_SECRET_ACCESS_KEY='<The API secret access key received from Amazon>'
TF_VAR_TESTNET_NAME="testnet-servers"
ansible-playbook -i inventory/ec2.py install.yml -e service=basecoin
```
### Installing custom versions
By default ansible installs the tendermint, basecoin or ethermint binary versions from the latest release in the repository. If you build your own version of the binaries, you can tell ansible to install that instead.
```
GOPATH="<your go path>"
go get -u github.com/tendermint/basecoin/cmd/basecoin
DO_API_TOKEN="<The API token received from DigitalOcean>"
TF_VAR_TESTNET_NAME="testnet-servers"
ansible-playbook -i inventory/digital_ocean.py install.yml -e service=basecoin -e release_install=false
```
Alternatively you can change the variable settings in `group_vars/all`.
## Other commands and roles
There are few extra playbooks to make life easier managing your servers.
* install.yml - Install basecoin or ethermint applications. (Tendermint gets installed automatically.) Use the `service` parameter to define which application to install. Defaults to `basecoin`.
* reset.yml - Stop the application, reset the configuration and data, then start the application again. You need to pass `-e service=<servicename>`, like `-e service=basecoin`. It will restart the underlying tendermint application too.
* restart.yml - Restart a service on all nodes. You need to pass `-e service=<servicename>`, like `-e service=basecoin`. It will restart the underlying tendermint application too.
* stop.yml - Stop the application. You need to pass `-e service=<servicename>`.
* status.yml - Check the service status and print it. You need to pass `-e service=<servicename>`.
* start.yml - Start the application. You need to pass `-e service=<servicename>`.
* ubuntu16-patch.yml - Ubuntu 16.04 does not have the minimum required python package installed to be able to run ansible. If you are using ubuntu, run this playbook first on the target machines. This will install the python pacakge that is required for ansible to work correctly on the remote nodes.
* upgrade.yml - Upgrade the `service` on your testnet. It will stop the service and restart it at the end. It will only work if the upgraded version is backward compatible with the installed version.
* upgrade-reset.yml - Upgrade the `service` on your testnet and reset the database. It will stop the service and restart it at the end. It will work for upgrades where the new version is not backward-compatible with the installed version - however it will reset the testnet to its default.
The roles are self-sufficient under the `roles/` folder.
* install - install the application defined in the `service` parameter. It can install release packages and update them with custom-compiled binaries.
* unsafe_reset - delete the database for a service, including the tendermint database.
* config - configure the application defined in `service`. It also configures the underlying tendermint service. Check `group_vars/all` for options.
* stop - stop an application. Requires the `service` parameter set.
* status - check the status of an application. Requires the `service` parameter set.
* start - start an application. Requires the `service` parameter set.
## Default variables
Default variables are documented under `group_vars/all`. You can the parameters there to deploy a previously created genesis.json file (instead of dynamically creating it) or if you want to deploy custom built binaries instead of deploying a released version.

+ 291
- 0
ansible/README.rst View File

@ -0,0 +1,291 @@
Using Ansible
=============
.. figure:: assets/a_plus_t.png
:alt: Ansible plus Tendermint
Ansible plus Tendermint
The playbooks in `our ansible directory <https://github.com/tendermint/tools/tree/master/ansible>`__
run ansible `roles <http://www.ansible.com/>`__ which:
- install and configure basecoin or ethermint
- start/stop basecoin or ethermint and reset their configuration
Prerequisites
-------------
- Ansible 2.0 or higher
- SSH key to the servers
Optional for DigitalOcean droplets:
- DigitalOcean API Token
- python dopy package
For a description on how to get a DigitalOcean API Token, see the explanation
in the `using terraform tutorial <./terraform-digitalocean.html>`__.
Optional for Amazon AWS instances:
- Amazon AWS API access key ID and secret access key.
The cloud inventory scripts come from the ansible team at their
`GitHub <https://github.com/ansible/ansible>`__ page. You can get the
latest version from the ``contrib/inventory`` folder.
Setup
-----
Ansible requires a "command machine" or "local machine" or "orchestrator
machine" to run on. This can be your laptop or any machine that can run
ansible. (It does not have to be part of the cloud network that hosts
your servers.)
Use the official `Ansible installation
guide <http://docs.ansible.com/ansible/intro_installation.html>`__ to
install Ansible. Here are a few examples on basic installation commands:
Ubuntu/Debian:
::
sudo apt-get install ansible
CentOS/RedHat:
::
sudo yum install epel-release
sudo yum install ansible
Mac OSX: If you have `Homebrew <https://brew.sh>`__ installed, then it's:
::
brew install ansible
If not, you can install it using ``pip``:
::
sudo easy_install pip
sudo pip install ansible
To make life easier, you can start an SSH Agent and load your SSH
key(s). This way ansible will have an uninterrupted way of connecting to
your servers.
::
ssh-agent > ~/.ssh/ssh.env
source ~/.ssh/ssh.env
ssh-add private.key
Subsequently, as long as the agent is running, you can use
``source ~/.ssh/ssh.env`` to load the keys to the current session. Note:
On Mac OSX, you can add the ``-K`` option to ssh-add to store the
passphrase in your keychain. The security of this feature is debated but
it is convenient.
Optional cloud dependencies
~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you are using a cloud provider to host your servers, you need the
below dependencies installed on your local machine.
DigitalOcean inventory dependencies:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ubuntu/Debian:
::
sudo apt-get install python-pip
sudo pip install dopy
CentOS/RedHat:
::
sudo yum install python-pip
sudo pip install dopy
Mac OSX:
::
sudo pip install dopy
Amazon AWS inventory dependencies:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ubuntu/Debian:
::
sudo apt-get install python-boto
CentOS/RedHat:
::
sudo yum install python-boto
Mac OSX:
::
sudo pip install boto
Refreshing the DigitalOcean inventory
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you just finished creating droplets, the local DigitalOcean inventory
cache is not up-to-date. To refresh it, run:
::
DO_API_TOKEN="<The API token received from DigitalOcean>"
python -u inventory/digital_ocean.py --refresh-cache 1> /dev/null
Refreshing the Amazon AWS inventory
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you just finished creating Amazon AWS EC2 instances, the local AWS
inventory cache is not up-to-date. To refresh it, run:
::
AWS_ACCESS_KEY_ID='<The API access key ID received from Amazon>'
AWS_SECRET_ACCESS_KEY='<The API secret access key received from Amazon>'
python -u inventory/ec2.py --refresh-cache 1> /dev/null
Note: you don't need the access key and secret key set, if you are
running ansible on an Amazon AMI instance with the proper IAM
permissions set.
Running the playbooks
---------------------
The playbooks are locked down to only run if the environment variable
``TF_VAR_TESTNET_NAME`` is populated. This is a precaution so you don't
accidentally run the playbook on all your servers.
The variable ``TF_VAR_TESTNET_NAME`` contains the testnet name which
ansible translates into an ansible group. If you used Terraform to
create the servers, it was the testnet name used there.
If the playbook cannot connect to the servers because of public key
denial, your SSH Agent is not set up properly. Alternatively you can add
the SSH key to ansible using the ``--private-key`` option.
If you need to connect to the nodes as root but your local username is
different, use the ansible option ``-u root`` to tell ansible to connect
to the servers and authenticate as the root user.
If you secured your server and you need to ``sudo`` for root access, use
the the ``-b`` or ``--become`` option to tell ansible to sudo to root
after connecting to the server. In the Terraform-DigitalOcean example,
if you created the ec2-user by adding the ``noroot=true`` option (or if
you are simply on Amazon AWS), you need to add the options
``-u ec2-user -b`` to ansible to tell it to connect as the ec2-user and
then sudo to root to run the playbook.
DigitalOcean
~~~~~~~~~~~~
::
DO_API_TOKEN="<The API token received from DigitalOcean>"
TF_VAR_TESTNET_NAME="testnet-servers"
ansible-playbook -i inventory/digital_ocean.py install.yml -e service=basecoin
Amazon AWS
~~~~~~~~~~
::
AWS_ACCESS_KEY_ID='<The API access key ID received from Amazon>'
AWS_SECRET_ACCESS_KEY='<The API secret access key received from Amazon>'
TF_VAR_TESTNET_NAME="testnet-servers"
ansible-playbook -i inventory/ec2.py install.yml -e service=basecoin
Installing custom versions
~~~~~~~~~~~~~~~~~~~~~~~~~~
By default ansible installs the tendermint, basecoin or ethermint binary
versions from the latest release in the repository. If you build your
own version of the binaries, you can tell ansible to install that
instead.
::
GOPATH="<your go path>"
go get -u github.com/tendermint/basecoin/cmd/basecoin
DO_API_TOKEN="<The API token received from DigitalOcean>"
TF_VAR_TESTNET_NAME="testnet-servers"
ansible-playbook -i inventory/digital_ocean.py install.yml -e service=basecoin -e release_install=false
Alternatively you can change the variable settings in
``group_vars/all``.
Other commands and roles
------------------------
There are few extra playbooks to make life easier managing your servers.
- install.yml - Install basecoin or ethermint applications. (Tendermint
gets installed automatically.) Use the ``service`` parameter to
define which application to install. Defaults to ``basecoin``.
- reset.yml - Stop the application, reset the configuration and data,
then start the application again. You need to pass
``-e service=<servicename>``, like ``-e service=basecoin``. It will
restart the underlying tendermint application too.
- restart.yml - Restart a service on all nodes. You need to pass
``-e service=<servicename>``, like ``-e service=basecoin``. It will
restart the underlying tendermint application too.
- stop.yml - Stop the application. You need to pass
``-e service=<servicename>``.
- status.yml - Check the service status and print it. You need to pass
``-e service=<servicename>``.
- start.yml - Start the application. You need to pass
``-e service=<servicename>``.
- ubuntu16-patch.yml - Ubuntu 16.04 does not have the minimum required
python package installed to be able to run ansible. If you are using
ubuntu, run this playbook first on the target machines. This will
install the python pacakge that is required for ansible to work
correctly on the remote nodes.
- upgrade.yml - Upgrade the ``service`` on your testnet. It will stop
the service and restart it at the end. It will only work if the
upgraded version is backward compatible with the installed version.
- upgrade-reset.yml - Upgrade the ``service`` on your testnet and reset
the database. It will stop the service and restart it at the end. It
will work for upgrades where the new version is not
backward-compatible with the installed version - however it will
reset the testnet to its default.
The roles are self-sufficient under the ``roles/`` folder.
- install - install the application defined in the ``service``
parameter. It can install release packages and update them with
custom-compiled binaries.
- unsafe\_reset - delete the database for a service, including the
tendermint database.
- config - configure the application defined in ``service``. It also
configures the underlying tendermint service. Check
``group_vars/all`` for options.
- stop - stop an application. Requires the ``service`` parameter set.
- status - check the status of an application. Requires the ``service``
parameter set.
- start - start an application. Requires the ``service`` parameter set.
Default variables
-----------------
Default variables are documented under ``group_vars/all``. You can the
parameters there to deploy a previously created genesis.json file
(instead of dynamically creating it) or if you want to deploy custom
built binaries instead of deploying a released version.

ansible/img/a_plus_t.png → ansible/assets/a_plus_t.png View File


+ 1
- 1
ansible/roles/config/templates/config.toml.j2 View File

@ -24,7 +24,7 @@ laddr = "tcp://0.0.0.0:46657"
[mempool]
recheck = false
broadcast = false
broadcast = true
wal_dir = ""
[consensus]


+ 0
- 52
create-digitalocean-testnet.sh View File

@ -1,52 +0,0 @@
#!/bin/bash
# This is an example set of commands that uses Terraform and Ansible to create a basecoin testnet on Digital Ocean.
# Prerequisites: terraform, ansible, DigitalOcean API token, ssh-agent running with the same SSH keys added that are set up during terraform
# Optional: GOPATH if you build the app yourself
#export DO_API_TOKEN="<This contains the DigitalOcean API token>"
#export GOPATH="<your go path>"
###
# Find out TF_VAR_TESTNET_NAME (testnet name)
###
if [ $# -gt 0 ]; then
TF_VAR_TESTNET_NAME="$1"
fi
if [ -z "$TF_VAR_TESTNET_NAME" ]; then
echo "Usage: $0 <TF_VAR_TESTNET_NAME>"
echo "or"
echo "export TF_VAR_TESTNET_NAME=<TF_VAR_TESTNET_NAME> ; $0"
exit
fi
###
# Build Digital Ocean infrastructure
###
SERVERS=2
cd terraform-digitalocean
terraform init
terraform env new "$TF_VAR_TESTNET_NAME"
#The next step copies additional terraform rules that only apply in the Tendermint network.
#cp -r ../devops/terraform-tendermint/* .
terraform apply -var servers=$SERVERS -var DO_API_TOKEN="$DO_API_TOKEN"
cd ..
###
# Build applications (optional)
###
if [ -n "$GOPATH" ]; then
go get -u github.com/tendermint/tendermint/cmd/tendermint
go get -u github.com/tendermint/basecoin/cmd/basecoin
ANSIBLE_ADDITIONAL_VARS="-e tendermint_release_install=false -e basecoin_release_intall=false"
fi
###
# Deploy application
###
#Note that SSH Agent needs to be running with SSH keys added or ansible-playbook requires the --private-key option.
cd ansible
python -u inventory/digital_ocean.py --refresh-cache 1> /dev/null
ansible-playbook -i inventory/digital_ocean.py install.yml -u root -e app_options_file=ansible/app_options_files/dev_money $ANSIBLE_ADDITIONAL_VARS
cd ..

+ 0
- 95
docker/README.md View File

@ -1,95 +0,0 @@
# Docker container description for Tendermint applications
* Overview (#Overview)
* Tendermint (#Tendermint)
* Basecoin (#Basecoin)
* Ethermint (#Ethermint)
## Overview
This folder contains Docker container descriptions. Using this folder you can build your own Docker images with the tendermint application.
It is assumed that you set up docker already.
If you don't want to build the images yourself, you should be able to download them from Docker Hub.
## Tendermint
Build the container:
Copy the `tendermint` binary to the `tendermint` folder.
```
docker build -t tendermint tendermint
```
The application configuration will be stored at `/tendermint` in the container. The ports 46656 and 46657 will be open for ABCI applications to connect.
Initialize tendermint configuration and keep it after the container is finished in a docker volume called `data`:
```
docker run --rm -v data:/tendermint tendermint init
```
If you want the docker volume to be a physical directory on your filesystem, you have to give an absolute path to docker and make sure the permissions allow the application to write it.
Get the public key of tendermint:
```
docker run --rm -v data:/tendermint tendermint show_validator
```
Run the docker tendermint application with:
```
docker run --rm -d -v data:/tendermint tendermint node
```
## Basecoin
Build the container:
Copy the `basecoin` binary to the `basecoin` folder.
```
docker build -t basecoin basecoin
```
The application configuration will be stored at `/basecoin`.
Initialize basecoin configuration and keep it after the container is finished:
```
docker run --rm -v basecoindata:/basecoin basecoin init deadbeef
```
Use your own basecoin account instead of `deadbeef` in the `init` command.
Get the public key of basecoin:
We use a trick here: since the basecoin and the tendermint configuration folders are similar, the `tendermint` command can extract the public key for us if we feed the basecoin configuration folder to tendermint.
```
docker run --rm -v basecoindata:/tendermint tendermint show_validator
```
Run the docker tendermint application with:
This is a two-step process:
* Run the basecoin container.
* Run the tendermint container and expose the ports that allow clients to connect. The --proxy_app should contain the basecoin application's IP address and port.
```
docker run --rm -d -v basecoindata:/basecoin basecoin start --without-tendermint
docker run --rm -d -v data:/tendermint -p 46656-46657:46656-46657 tendermint node --proxy_app tcp://172.17.0.2:46658
```
## Ethermint
Build the container:
Copy the `ethermint` binary and the setup folder to the `ethermint` folder.
```
docker build -t ethermint ethermint
```
The application configuration will be stored at `/ethermint`.
The files required for initializing ethermint (the files in the source `setup` folder) are under `/setup`.
Initialize ethermint configuration:
```
docker run --rm -v ethermintdata:/ethermint ethermint init /setup/genesis.json
```
Start ethermint as a validator node:
This is a two-step process:
* Run the ethermint container. You will have to define where tendermint runs as the ethermint binary connects to it explicitly.
* Run the tendermint container and expose the ports that allow clients to connect. The --proxy_app should contain the ethermint application's IP address and port.
```
docker run --rm -d -v ethermintdata:/ethermint ethermint --tendermint_addr tcp://172.17.0.3:46657
docker run --rm -d -v data:/tendermint -p 46656-46657:46656-46657 tendermint node --proxy_app tcp://172.17.0.2:46658
```

+ 71
- 0
docker/README.rst View File

@ -0,0 +1,71 @@
Using Docker
============
It is assumed that you have already `setup docker <https://docs.docker.com/engine/installation/>`__.
Tendermint
----------
The application configuration and data will be stored at ``/tendermint`` in the
container. This directory will also be exposed as a volume. The ports 46656 and
46657 will be open for ABCI applications to connect.
Initialize tendermint:
::
mkdir /tmdata
docker run --rm -v /tmdata:/tendermint tendermint/tendermint init
Change ``/tmdata`` folder to any destination where you want to store Tendermint
configuration and data.
Tendermint docker image is stored on `docker hub <https://hub.docker.com/r/tendermint/tendermint/>`__.
Get the public key of tendermint:
::
docker run --rm -v /tmdata:/tendermint tendermint/tendermint show_validator
Run the docker tendermint application with:
::
docker run --rm -d -v /tmdata:/tendermint tendermint/tendermint node
Building images by yourself:
`This folder <https://github.com/tendermint/tendermint/tree/master/DOCKER>`__
contains Docker container descriptions. Using this folder you can build your
own Docker images with the tendermint application.
Ethermint
---------
The application configuration will be stored at ``/ethermint``.
Initialize ethermint:
::
mkdir /ethermintdata
wget -O /ethermintdata/genesis.json https://github.com/tendermint/ethermint/raw/master/setup/genesis.json
docker run --rm -v /ethermintdata:/ethermint tendermint/ethermint ethermint --datadir /ethermint init /ethermint/genesis.json
Start ethermint as a validator node: This is a two-step process: \* Run the
tendermint container and expose the ports that allow clients to connect. \* Run
the ethermint container. You will have to define where tendermint runs as the
ethermint binary connects to it explicitly. The --proxy\_app should contain the
ethermint application's IP address and port.
::
docker run --rm -d -v /tmdata:/tendermint tendermint/tendermint node --proxy_app=tcp://172.17.0.3:46658
docker run --rm -d -v /ethermintdata:/ethermint tendermint/ethermint ethermint --tendermint_addr tcp://172.17.0.2:46657
Building images by yourself:
`This folder <https://github.com/tendermint/ethermint/blob/master/scripts/docker>`__
contains Docker container descriptions. Using this folder you can build your
own Docker images with the ethermint application.

+ 0
- 14
docker/basecoin/Dockerfile View File

@ -1,14 +0,0 @@
FROM busybox
#Use --build-arg to change where the basecoin binary resides
ARG BASECOIN_BINARY=basecoin
ENV BCHOME /basecoin
COPY $BASECOIN_BINARY /usr/bin/basecoin
RUN adduser -h $BCHOME -D basecoin
VOLUME [ $BCHOME ]
EXPOSE 46658
USER basecoin
ENTRYPOINT ["/usr/bin/basecoin"]
CMD ["start","--without-tendermint"]
WORKDIR $BCHOME
STOPSIGNAL SIGTERM

+ 0
- 229
mintnet-kubernetes/README.md View File

@ -1,229 +0,0 @@
# Tendermint network powered by Kubernetes
![Tendermint plus Kubernetes](img/t_plus_k.png)
* [QuickStart (MacOS)](#quickstart-macos)
* [QuickStart (Linux)](#quickstart-linux)
* [Usage](#usage)
* [Security](#security)
* [Fault tolerance](#fault-tolerance)
* [Starting process](#starting-process)
This should primarily be used for testing purposes or for tightly-defined
chains operated by a single stakeholder (see [the security
precautions](#security)). If your desire is to launch an application with many
stakeholders, consider using our set of Ansible scripts.
## QuickStart (MacOS)
[Requirements](https://github.com/kubernetes/minikube#requirements)
```
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/kubectl
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.18.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
minikube start
git clone https://github.com/tendermint/tools.git && cd tools/mintnet-kubernetes/examples/basecoin && make create
```
## QuickStart (Linux)
[Requirements](https://github.com/kubernetes/minikube#requirements)
```
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/kubectl
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.18.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
minikube start
git clone https://github.com/tendermint/tools.git && cd tools/mintnet-kubernetes/examples/basecoin && make create
```
### Verify everything works
**Using a shell:**
1. wait until all the pods are `Running`.
```
kubectl get pods -w -o wide -L tm
```
2. query the Tendermint app logs from the first pod.
```
kubectl logs -c tm -f tm-0
```
3. use [Rest API](https://tendermint.com/docs/internals/rpc) to fetch the
status of the second pod's Tendermint app. Note we are using `kubectl exec`
because pods are not exposed (and should not be) to the outer network.
```
kubectl exec -c tm tm-0 -- curl -s http://tm-1.basecoin:46657/status | json_pp
```
**Using the dashboard:**
```
minikube dashboard
```
### Clean up
```
make destroy
```
## Usage
### (1/4) Setup a Kubernetes cluster
- locally using [Minikube](https://github.com/kubernetes/minikube)
- on GCE with a single click in the web UI
- on AWS using [Kubernetes Operations](https://github.com/kubernetes/kops/blob/master/docs/aws.md)
- on Linux machines (Digital Ocean) using [kubeadm](https://kubernetes.io/docs/getting-started-guides/kubeadm/)
- on AWS, Azure, GCE or bare metal using [Kargo (Ansible)](https://kubernetes.io/docs/getting-started-guides/kargo/)
Please refer to [the official
documentation](https://kubernetes.io/docs/getting-started-guides/) for overview
and comparison of different options. See our guides for [Google Cloud
Engine](docs/SETUP_K8S_ON_GCE.md) or [Digital Ocean](docs/SETUP_K8S_ON_DO.md).
**Make sure you have Kubernetes >= 1.5, because you will be using StatefulSets,
which is a beta feature in 1.5.**
### (2/4) Create a configuration file
Download a template:
```
curl -Lo app.yaml https://github.com/tendermint/tools/raw/master/mintnet-kubernetes/app.template.yaml
```
Open `app.yaml` in your favorite editor and configure your app container
(navigate to `- name: app`). Kubernetes DSL (Domain Specific Language) is very
simple, so it should be easy. You will need to set Docker image, command and/or
run arguments. Replace variables prefixed with `YOUR_APP` with corresponding
values. Set genesis time to now and preferable chain ID in ConfigMap.
Please note if you are changing `replicas` number, do not forget to update
`validators` set in ConfigMap. You will be able to scale the cluster up or down
later, but new pods (nodes) won't become validators automatically.
### (3/4) Deploy your application
```
kubectl create -f ./app.yaml
```
### (4/4) Observe your cluster
**web UI** <-> https://github.com/kubernetes/dashboard
The easiest way to access Dashboard is to use kubectl. Run the following command in your desktop environment:
```
kubectl proxy
```
kubectl will handle authentication with apiserver and make Dashboard available at [http://localhost:8001/ui](http://localhost:8001/ui)
**shell**
List all the pods:
```
kubectl get pods -o wide -L tm
```
StatefulSet details:
```
kubectl describe statefulsets tm
```
First pod details:
```
kubectl describe pod tm-0
```
Tendermint app logs from the first pod:
```
kubectl logs tm-0 -c tm -f
```
App logs from the first pod:
```
kubectl logs tm-0 -c app -f
```
Status of the second pod's Tendermint app:
```
kubectl exec -c tm tm-0 -- curl -s http://tm-1.<YOUR_APP_NAME>:46657/status | json_pp
```
## Security
Due to the nature of Kubernetes, where you typically have a single master, the
master could be a SPOF (Single Point Of Failure). Therefore, you need to make
sure only authorized people can access it. And these people themselves had
taken basic measures in order not to get hacked.
These are the best practices:
- all access to the master is over TLS
- access to the API Server is X.509 certificate or token based
- etcd is not exposed directly to the cluster
- ensure that images are free of vulnerabilities ([1](https://github.com/coreos/clair))
- ensure that only authorized images are used in your environment
- disable direct access to Kubernetes nodes (no SSH)
- define resource quota
Resources:
- https://kubernetes.io/docs/admin/accessing-the-api/
- http://blog.kubernetes.io/2016/08/security-best-practices-kubernetes-deployment.html
- https://blog.openshift.com/securing-kubernetes/
## Fault tolerance
Having a single master (API server) is a bad thing also because if something
happens to it, you risk being left without an access to the application.
To avoid that you can [run Kubernetes in multiple
zones](https://kubernetes.io/docs/admin/multiple-zones/), each zone running an
[API server](https://kubernetes.io/docs/admin/high-availability/) and load
balance requests between them. Do not forget to make sure only one instance of
scheduler and controller-manager are running at once.
Running in multiple zones is a lightweight version of a broader [Cluster
Federation feature](https://kubernetes.io/docs/admin/federation/). Federated
deployments could span across multiple regions (not zones). We haven't tried
this feature yet, so any feedback is highly appreciated! Especially, related to
additional latency and cost of exchanging data between the regions.
Resources:
- https://kubernetes.io/docs/admin/high-availability/
## Starting process
![StatefulSet](img/statefulset.png)
Init containers (`tm-gen-validator`) are run before all other containers,
creating public-private key pair for each pod. Every `tm` container then asks
other pods for their public keys, which are served with nginx (`pub-key`
container). When `tm` container have all the keys, it forms a genesis file and
starts Tendermint process.
## TODO
- [ ] run tendermint from tmuser
```
securityContext:
fsGroup: 999
```

+ 290
- 0
mintnet-kubernetes/README.rst View File

@ -0,0 +1,290 @@
Using Kubernetes
================
.. figure:: assets/t_plus_k.png
:alt: Tendermint plus Kubernetes
Tendermint plus Kubernetes
This should primarily be used for testing purposes or for
tightly-defined chains operated by a single stakeholder (see `the
security precautions <#security>`__). If your desire is to launch an
application with many stakeholders, consider using our set of Ansible
scripts.
Quick Start
-----------
For either platform, see the `requirements <https://github.com/kubernetes/minikube#requirements>`__
MacOS
^^^^^
::
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/kubectl
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.18.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
minikube start
git clone https://github.com/tendermint/tools.git && cd tools/mintnet-kubernetes/examples/basecoin && make create
Linux
^^^^^
::
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/kubectl
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.18.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
minikube start
git clone https://github.com/tendermint/tools.git && cd tools/mintnet-kubernetes/examples/basecoin && make create
Verify it worked
~~~~~~~~~~~~~~~~
**Using a shell:**
First wait until all the pods are ``Running``:
``kubectl get pods -w -o wide -L tm``
then query the Tendermint app logs from the first pod:
``kubectl logs -c tm -f tm-0``
finally, use our `Rest API <../specification/rpc.html>`__ to fetch the status of the second pod's Tendermint app.
Note we are using ``kubectl exec`` because pods are not exposed (and should not be) to the
outer network:
``kubectl exec -c tm tm-0 -- curl -s http://tm-1.basecoin:46657/status | json_pp``
**Using the dashboard:**
::
minikube dashboard
Clean up
~~~~~~~~
::
make destroy
Usage
-----
Setup a Kubernetes cluster
^^^^^^^^^^^^^^^^^^^^^^^^^^
- locally using `Minikube <https://github.com/kubernetes/minikube>`__
- on GCE with a single click in the web UI
- on AWS using `Kubernetes
Operations <https://github.com/kubernetes/kops/blob/master/docs/aws.md>`__
- on Linux machines (Digital Ocean) using
`kubeadm <https://kubernetes.io/docs/getting-started-guides/kubeadm/>`__
- on AWS, Azure, GCE or bare metal using `Kargo
(Ansible) <https://kubernetes.io/docs/getting-started-guides/kargo/>`__
Please refer to `the official
documentation <https://kubernetes.io/docs/getting-started-guides/>`__
for overview and comparison of different options.
Kubernetes on Digital Ocean
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Available options:
- `kubeadm (alpha) <https://kubernetes.io/docs/getting-started-guides/kubeadm/>`__
- `kargo <https://kubernetes.io/docs/getting-started-guides/kargo/>`__
- `rancher <http://rancher.com/>`__
- `terraform <https://github.com/hermanjunge/kubernetes-digitalocean-terraform>`__
As you can see, there is no single tool for creating a cluster on DO.
Therefore, choose the one you know and comfortable working with. If you know
and used `terraform <https://www.terraform.io/>`__ before, then choose it. If you
know Ansible, then pick kargo. If none of these seem familiar to you, go with
``kubeadm``. Rancher is a beautiful UI for deploying and managing containers in
production.
Kubernetes on Google Cloud Engine
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Review the `Official Documentation <https://kubernetes.io/docs/getting-started-guides/gce/>`__ for Kubernetes on Google Compute
Engine.
**Create a cluster**
The recommended way is to use `Google Container
Engine <https://cloud.google.com/container-engine/>`__. You should be able
to create a fully fledged cluster with just a few clicks.
**Connect to it**
Install ``gcloud`` as a part of `Google Cloud SDK <https://cloud.google.com/sdk/>`__.
Make sure you have credentials for GCloud by running ``gcloud auth login``.
In order to make API calls against GCE, you must also run ``gcloud auth
application-default login``.
Press ``Connect``:
.. figure:: assets/gce1.png
and execute the first command in your shell. Then start a proxy by
executing ``kubectl` proxy``.
.. figure:: assets/gce2.png
Now you should be able to run ``kubectl`` command to create resources, get
resource info, logs, etc.
**Make sure you have Kubernetes >= 1.5, because you will be using
StatefulSets, which is a beta feature in 1.5.**
Create a configuration file
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Download a template:
::
curl -Lo app.yaml https://github.com/tendermint/tools/raw/master/mintnet-kubernetes/app.template.yaml
Open ``app.yaml`` in your favorite editor and configure your app
container (navigate to ``- name: app``). Kubernetes DSL (Domain Specific
Language) is very simple, so it should be easy. You will need to set
Docker image, command and/or run arguments. Replace variables prefixed
with ``YOUR_APP`` with corresponding values. Set genesis time to now and
preferable chain ID in ConfigMap.
Please note if you are changing ``replicas`` number, do not forget to
update ``validators`` set in ConfigMap. You will be able to scale the
cluster up or down later, but new pods (nodes) won't become validators
automatically.
Deploy your application
^^^^^^^^^^^^^^^^^^^^^^^
::
kubectl create -f ./app.yaml
Observe your cluster
^^^^^^^^^^^^^^^^^^^^
`web UI <https://github.com/kubernetes/dashboard>`__
The easiest way to access Dashboard is to use ``kubectl``. Run the following
command in your desktop environment:
::
kubectl proxy
``kubectl`` will handle authentication with apiserver and make Dashboard
available at http://localhost:8001/ui
**shell**
List all the pods:
::
kubectl get pods -o wide -L tm
StatefulSet details:
::
kubectl describe statefulsets tm
First pod details:
::
kubectl describe pod tm-0
Tendermint app logs from the first pod:
::
kubectl logs tm-0 -c tm -f
App logs from the first pod:
::
kubectl logs tm-0 -c app -f
Status of the second pod's Tendermint app:
::
kubectl exec -c tm tm-0 -- curl -s http://tm-1.<YOUR_APP_NAME>:46657/status | json_pp
Security
--------
Due to the nature of Kubernetes, where you typically have a single
master, the master could be a SPOF (Single Point Of Failure). Therefore,
you need to make sure only authorized people can access it. And these
people themselves had taken basic measures in order not to get hacked.
These are the best practices:
- all access to the master is over TLS
- access to the API Server is X.509 certificate or token based
- etcd is not exposed directly to the cluster
- ensure that images are free of vulnerabilities
(`1 <https://github.com/coreos/clair>`__)
- ensure that only authorized images are used in your environment
- disable direct access to Kubernetes nodes (no SSH)
- define resource quota
Resources:
- https://kubernetes.io/docs/admin/accessing-the-api/
- http://blog.kubernetes.io/2016/08/security-best-practices-kubernetes-deployment.html
- https://blog.openshift.com/securing-kubernetes/
Fault tolerance
---------------
Having a single master (API server) is a bad thing also because if
something happens to it, you risk being left without an access to the
application.
To avoid that you can `run Kubernetes in multiple
zones <https://kubernetes.io/docs/admin/multiple-zones/>`__, each zone
running an `API
server <https://kubernetes.io/docs/admin/high-availability/>`__ and load
balance requests between them. Do not forget to make sure only one
instance of scheduler and controller-manager are running at once.
Running in multiple zones is a lightweight version of a broader `Cluster
Federation feature <https://kubernetes.io/docs/admin/federation/>`__.
Federated deployments could span across multiple regions (not zones). We
haven't tried this feature yet, so any feedback is highly appreciated!
Especially, related to additional latency and cost of exchanging data
between the regions.
Resources:
- https://kubernetes.io/docs/admin/high-availability/
Starting process
----------------
.. figure:: assets/statefulset.png
:alt: StatefulSet
StatefulSet
Init containers (``tm-gen-validator``) are run before all other
containers, creating public-private key pair for each pod. Every ``tm``
container then asks other pods for their public keys, which are served
with nginx (``pub-key`` container). When ``tm`` container have all the
keys, it forms a genesis file and starts the Tendermint process.

mintnet-kubernetes/img/gce1.png → mintnet-kubernetes/assets/gce1.png View File


mintnet-kubernetes/img/gce2.png → mintnet-kubernetes/assets/gce2.png View File


mintnet-kubernetes/img/statefulset.png → mintnet-kubernetes/assets/statefulset.png View File


mintnet-kubernetes/img/t_plus_k.png → mintnet-kubernetes/assets/t_plus_k.png View File


+ 0
- 15
mintnet-kubernetes/docs/SETUP_K8S_ON_DO.md View File

@ -1,15 +0,0 @@
# Setup a Kubernetes cluster on Digital Ocean (DO)
Available options:
1. [kubeadm (alpha)](https://kubernetes.io/docs/getting-started-guides/kubeadm/)
2. [kargo](https://kubernetes.io/docs/getting-started-guides/kargo/)
3. [rancher](http://rancher.com/)
4. [terraform](https://github.com/hermanjunge/kubernetes-digitalocean-terraform)
As you can see, there is no single tool for creating a cluster on DO.
Therefore, choose the one you know and comfortable working with. If you know
and used [terraform](https://www.terraform.io/) before, then choose it. If you
know Ansible, then pick kargo. If none of these seem familiar to you, go with
kubeadm. Rancher is a beautiful UI for deploying and managing containers in
production.

+ 0
- 31
mintnet-kubernetes/docs/SETUP_K8S_ON_GCE.md View File

@ -1,31 +0,0 @@
# Setup a Kubernetes cluster on Google Cloud Engine (GCE)
Main article: [Running Kubernetes on Google Compute
Engine](https://kubernetes.io/docs/getting-started-guides/gce/)
## 1. Create a cluster
The recommended way is to use [Google Container
Engine](https://cloud.google.com/container-engine/) (GKE). You should be able
to create a fully fledged cluster with just a few clicks.
## 2. Connect to it
Install `gcloud` as a part of [Google Cloud SDK](https://cloud.google.com/sdk/).
Make sure you have credentials for GCloud by running `gcloud auth login`.
In order to make API calls against GCE, you must also run `gcloud auth
application-default login`
Press `Connect` button:
![Connect button](../img/gce1.png)
![Connect pop-up](../img/gce2.png)
and execute the first command in your shell. Then start a proxy by
executing `kubectl proxy`.
Now you should be able to run `kubectl` command to create resources, get
resource info, logs, etc.

+ 0
- 4
terraform-aws/README.md View File

@ -1,4 +0,0 @@
# Terraform for Amazon AWS
To be done...

+ 0
- 66
terraform-digitalocean/README.md View File

@ -1,66 +0,0 @@
# Terraform for Digital Ocean
This is a generic [Terraform](https://www.terraform.io/) configuration that sets up DigitalOcean droplets.
# Prerequisites
* Install [HashiCorp Terraform](https://www.terraform.io) on a linux machine.
* Create a [DigitalOcean API token](https://cloud.digitalocean.com/settings/api/tokens) with read and write capability.
* Create a private/public key pair for SSH. This is needed to log onto your droplets as well as by Ansible to connect for configuration changes.
* Set up the public SSH key at the [DigitalOcean security page](https://cloud.digitalocean.com/settings/security). [Here](https://www.digitalocean.com/community/tutorials/how-to-use-ssh-keys-with-digitalocean-droplets)'s a tutorial.
* Find out your SSH key ID at DigitalOcean by querying the below command on your linux box:
```
DO_API_TOKEN="<The API token received from DigitalOcean>"
curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $DO_API_TOKEN" "https://api.digitalocean.com/v2/account/keys"
```
# How to run
## Initialization
If this is your first time using terraform, you have to initialize it by running the below command. (Note: initialization can be run multiple times)
```
terraform init
```
After initialization it's good measure to create a new Terraform environment for the droplets so they are always managed together.
```
TESTNET_NAME="testnet-servers"
terraform env new "$TESTNET_NAME"
```
Note this `terraform env` command is only available in terraform `v0.9` and up.
## Execution
The below command will create 4 nodes in DigitalOcean. They will be named `testnet-servers-node0` to `testnet-servers-node3` and they will be tagged as `testnet-servers`.
```
DO_API_TOKEN="<The API token received from DigitalOcean>"
SSH_IDS="[ \"<The SSH ID received from the curl call above.>\" ]"
terraform apply -var TESTNET_NAME="testnet-servers" -var servers=4 -var DO_API_TOKEN="$DO_API_TOKEN" -var ssh_keys="$SSH_IDS"
```
Note: `ssh_keys` is a list of strings. You can add multiple keys. For example: `["1234567","9876543"]`.
Alternatively you can use the default settings. The number of default servers is 4 and the testnet name is `tf-testnet1`. Variables can also be defined as environment variables instead of the command-line. Environment variables that start with `TF_VAR_` will be translated into the Terraform configuration. For example the number of servers can be overriden by setting the `TF_VAR_servers` variable.
```
TF_VAR_DO_API_TOKEN="<The API token received from DigitalOcean>"
TF_VAR_TESTNET_NAME="testnet-servers"
terraform-apply
```
## Security
DigitalOcean uses the root user by default on its droplets. This is fine as long as SSH keys are used. However some people still would like to disable root and use an alternative user to connect to the droplets - then `sudo` from there.
Terraform can do this but it requires SSH agent running on the machine where terraform is run, with one of the SSH keys of the droplets added to the agent. (This will be neede for ansible too, so it's worth setting it up here. Check out the [ansible](https://github.com/tendermint/tools/tree/master/ansible) page for more information.)
After setting up the SSH key, run `terraform apply` with `-var noroot=true` to create your droplets. Terraform will create a user called `ec2-user` and move the SSH keys over, this way disabling SSH login for root. It also adds the `ec2-user` to the sudoers file, so after logging in as ec2-user you can `sudo` to `root`.
DigitalOcean announced firewalls but the current version of Terraform (0.9.8 as of this writing) does not support it yet. Fortunately it is quite easy to set it up through the web interface (and not that bad through the [RESTful API](https://developers.digitalocean.com/documentation/v2/#firewalls) either). When adding droplets to a firewall rule, you can add tags. All droplets in a testnet are tagged with the testnet name so it's enough to define the testnet name in the firewall rule. It is not necessary to add the nodes one-by-one. Also, the firewall rule "remembers" the testnet name tag so if you change the servers but keep the name, the firewall rules will still apply.
# What's next
After setting up the nodes, head over to the [ansible folder](https://github.com/tendermint/tools/tree/master/ansible) to set up tendermint and basecoin.

+ 111
- 0
terraform-digitalocean/README.rst View File

@ -0,0 +1,111 @@
Using Terraform
===============
This is a generic `Terraform <https://www.terraform.io/>`__
configuration that sets up DigitalOcean droplets. See the
`terraform-digitalocean <https://github.com/tendermint/tools/tree/master/terraform-digitalocean>`__
for the required files.
Prerequisites
-------------
- Install `HashiCorp Terraform <https://www.terraform.io>`__ on a linux
machine.
- Create a `DigitalOcean API
token <https://cloud.digitalocean.com/settings/api/tokens>`__ with
read and write capability.
- Create a private/public key pair for SSH. This is needed to log onto
your droplets as well as by Ansible to connect for configuration
changes.
- Set up the public SSH key at the `DigitalOcean security
page <https://cloud.digitalocean.com/settings/security>`__.
`Here <https://www.digitalocean.com/community/tutorials/how-to-use-ssh-keys-with-digitalocean-droplets>`__'s
a tutorial.
- Find out your SSH key ID at DigitalOcean by querying the below
command on your linux box:
::
DO_API_TOKEN="<The API token received from DigitalOcean>"
curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $DO_API_TOKEN" "https://api.digitalocean.com/v2/account/keys"
Initialization
--------------
If this is your first time using terraform, you have to initialize it by
running the below command. (Note: initialization can be run multiple
times)
::
terraform init
After initialization it's good measure to create a new Terraform
environment for the droplets so they are always managed together.
::
TESTNET_NAME="testnet-servers"
terraform env new "$TESTNET_NAME"
Note this ``terraform env`` command is only available in terraform
``v0.9`` and up.
Execution
---------
The below command will create 4 nodes in DigitalOcean. They will be
named ``testnet-servers-node0`` to ``testnet-servers-node3`` and they
will be tagged as ``testnet-servers``.
::
DO_API_TOKEN="<The API token received from DigitalOcean>"
SSH_IDS="[ \"<The SSH ID received from the curl call above.>\" ]"
terraform apply -var TESTNET_NAME="testnet-servers" -var servers=4 -var DO_API_TOKEN="$DO_API_TOKEN" -var ssh_keys="$SSH_IDS"
Note: ``ssh_keys`` is a list of strings. You can add multiple keys. For
example: ``["1234567","9876543"]``.
Alternatively you can use the default settings. The number of default
servers is 4 and the testnet name is ``tf-testnet1``. Variables can also
be defined as environment variables instead of the command-line.
Environment variables that start with ``TF_VAR_`` will be translated
into the Terraform configuration. For example the number of servers can
be overriden by setting the ``TF_VAR_servers`` variable.
::
TF_VAR_DO_API_TOKEN="<The API token received from DigitalOcean>"
TF_VAR_TESTNET_NAME="testnet-servers"
terraform-apply
Security
--------
DigitalOcean uses the root user by default on its droplets. This is fine
as long as SSH keys are used. However some people still would like to
disable root and use an alternative user to connect to the droplets -
then ``sudo`` from there. Terraform can do this but it requires SSH
agent running on the machine where terraform is run, with one of the SSH
keys of the droplets added to the agent. (This will be neede for ansible
too, so it's worth setting it up here. Check out the
`ansible <https://github.com/tendermint/tools/tree/master/ansible>`__
page for more information.) After setting up the SSH key, run
``terraform apply`` with ``-var noroot=true`` to create your droplets.
Terraform will create a user called ``ec2-user`` and move the SSH keys
over, this way disabling SSH login for root. It also adds the
``ec2-user`` to the sudoers file, so after logging in as ec2-user you
can ``sudo`` to ``root``.
DigitalOcean announced firewalls but the current version of Terraform
(0.9.8 as of this writing) does not support it yet. Fortunately it is
quite easy to set it up through the web interface (and not that bad
through the `RESTful
API <https://developers.digitalocean.com/documentation/v2/#firewalls>`__
either). When adding droplets to a firewall rule, you can add tags. All
droplets in a testnet are tagged with the testnet name so it's enough to
define the testnet name in the firewall rule. It is not necessary to add
the nodes one-by-one. Also, the firewall rule "remembers" the testnet
name tag so if you change the servers but keep the name, the firewall
rules will still apply.

+ 1
- 1
tm-bench/Dockerfile View File

@ -1,4 +1,4 @@
FROM alpine:3.5
FROM alpine:3.6
WORKDIR /app
COPY tm-bench /app/tm-bench


+ 1
- 1
tm-bench/Dockerfile.dev View File

@ -7,6 +7,6 @@ COPY Makefile /go/src/github.com/tendermint/tools/tm-bench/
COPY glide.yaml /go/src/github.com/tendermint/tools/tm-bench/
COPY glide.lock /go/src/github.com/tendermint/tools/tm-bench/
RUN make get_deps
RUN make get_vendor_deps
COPY . /go/src/github.com/tendermint/tools/tm-bench

+ 1
- 0
tm-bench/LICENSE View File

@ -1,3 +1,4 @@
Tendermint Bench
Copyright 2017 Tendermint
Apache License


+ 15
- 14
tm-bench/Makefile View File

@ -1,47 +1,48 @@
DIST_DIRS := find * -type d -exec
VERSION := $(shell perl -ne '/^var version.*"([^"]+)".*$$/ && print "v$$1\n"' main.go)
GOTOOLS = \
github.com/Masterminds/glide \
github.com/mitchellh/gox
tools:
go get -v $(GOTOOLS)
go get $(GOTOOLS)
get_deps: tools
get_vendor_deps:
@hash glide 2>/dev/null || go get github.com/Masterminds/glide
glide install
build:
go build -ldflags "-X main.version=${VERSION}"
go build
install:
go install -ldflags "-X main.version=${VERSION}"
go install
test:
go test
go test -race
build-all: tools
rm -rf ./dist
gox -verbose \
-ldflags "-X main.version=${VERSION}" \
-os="linux darwin windows freebsd openbsd netbsd" \
-arch="amd64 386 armv5 armv6 armv7 arm64" \
-osarch="!darwin/arm64" \
-ldflags "-s -w" \
-arch="amd64 386 arm arm64" \
-os="linux darwin windows freebsd" \
-osarch="!darwin/arm !darwin/arm64" \
-output="dist/{{.OS}}-{{.Arch}}/{{.Dir}}" .
dist: build-all
cd dist && \
$(DIST_DIRS) cp ../LICENSE {} \; && \
$(DIST_DIRS) cp ../README.md {} \; && \
$(DIST_DIRS) cp ../README.rst {} \; && \
$(DIST_DIRS) tar -zcf tm-bench-${VERSION}-{}.tar.gz {} \; && \
$(DIST_DIRS) zip -r tm-bench-${VERSION}-{}.zip {} \; && \
shasum -a256 ./*.tar.gz > "./tm-bench_${VERSION}_SHA256SUMS" && \
cd ..
build-docker:
rm -f ./tm-bench
docker run -it --rm -v "$(PWD):/go/src/app" -w "/go/src/app" -e "CGO_ENABLED=0" golang:alpine go build -ldflags "-X main.version=${VERSION}" -o tm-bench
docker run -it --rm -v "$(PWD):/go/src/app" -w "/go/src/app" -e "CGO_ENABLED=0" golang:alpine go build -ldflags "-s -w" -o tm-bench
docker build -t "tendermint/bench" .
clean:
rm -f ./tm-bench
rm -rf ./dist
.PHONY: tools get_deps build install test build-all dist clean build-docker
.PHONY: tools get_vendor_deps build install test build-all dist clean build-docker

+ 0
- 64
tm-bench/README.md View File

@ -1,64 +0,0 @@
# Tendermint blockchain benchmarking tool (tm-bench)
`tm-bench` is a simple benchmarking tool for [Tendermint
core](https://github.com/tendermint/tendermint) nodes.
```
λ tm-bench -T 10 -r 1000 localhost:46657
Stats Avg Stdev Max
Block latency 6.18ms 3.19ms 14ms
Blocks/sec 0.828 0.378 1
Txs/sec 963 493 1811
```
* [QuickStart using Docker](#quickstart-using-docker)
* [QuickStart using binaries](#quickstart-using-binaries)
* [Usage](#usage)
## QuickStart using Docker
```
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint init
docker run -it --rm -v "/tmp:/tendermint" -p "46657:46657" --name=tm tendermint/tendermint
docker run -it --rm --link=tm tendermint/bench tm:46657
```
## QuickStart using binaries
Linux:
```
curl -L https://s3-us-west-2.amazonaws.com/tendermint/0.8.0/tendermint_linux_amd64.zip && sudo unzip -d /usr/local/bin tendermint_linux_amd64.zip && sudo chmod +x tendermint
tendermint init
tendermint node --app_proxy=dummy
tm-bench localhost:46657
```
Max OS:
```
curl -L https://s3-us-west-2.amazonaws.com/tendermint/0.8.0/tendermint_darwin_amd64.zip && sudo unzip -d /usr/local/bin tendermint_darwin_amd64.zip && sudo chmod +x tendermint
tendermint init
tendermint node --app_proxy=dummy
tm-bench localhost:46657
```
## Usage
```
tm-bench [-c 1] [-T 10] [-r 1000] [endpoints]
Examples:
tm-bench localhost:46657
Flags:
-T int
Exit after the specified amount of time in seconds (default 10)
-c int
Connections to keep open per endpoint (default 1)
-r int
Txs per second to send in a connection (default 1000)
-v Verbose output
```

+ 159
- 0
tm-bench/README.rst View File

@ -0,0 +1,159 @@
Benchmarking and Monitoring
===========================
tm-bench
--------
Tendermint blockchain benchmarking tool: https://github.com/tendermint/tools/tree/master/tm-bench
For example, the following:
::
tm-bench -T 10 -r 1000 localhost:46657
will output:
::
Stats Avg Stdev Max
Block latency 6.18ms 3.19ms 14ms
Blocks/sec 0.828 0.378 1
Txs/sec 963 493 1811
Quick Start
^^^^^^^^^^^
Docker
~~~~~~
::
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint:0.12.1 init
docker run -it --rm -v "/tmp:/tendermint" -p "46657:46657" --name=tm tendermint/tendermint:0.12.1
docker run -it --rm --link=tm tendermint/bench tm:46657
Binaries
~~~~~~~~
If **Linux**, start with:
::
curl -L https://s3-us-west-2.amazonaws.com/tendermint/0.12.1/tendermint_linux_amd64.zip && sudo unzip -d /usr/local/bin tendermint_linux_amd64.zip && sudo chmod +x tendermint
if **Mac OS**, start with:
::
curl -L https://s3-us-west-2.amazonaws.com/tendermint/0.12.1/tendermint_darwin_amd64.zip && sudo unzip -d /usr/local/bin tendermint_darwin_amd64.zip && sudo chmod +x tendermint
then run:
::
tendermint init
tendermint node --app_proxy=dummy
tm-bench localhost:46657
with the last command being in a seperate window.
Usage
^^^^^
::
tm-bench [-c 1] [-T 10] [-r 1000] [endpoints]
Examples:
tm-bench localhost:46657
Flags:
-T int
Exit after the specified amount of time in seconds (default 10)
-c int
Connections to keep open per endpoint (default 1)
-r int
Txs per second to send in a connection (default 1000)
-v Verbose output
Development
^^^^^^^^^^^
::
make get_vendor_deps
make test
tm-monitor
----------
Tendermint blockchain monitoring tool; watches over one or more nodes, collecting and providing various statistics to the user: https://github.com/tendermint/tools/tree/master/tm-monitor
Quick Start
^^^^^^^^^^^
Docker
~~~~~~
::
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint init
docker run -it --rm -v "/tmp:/tendermint" -p "46657:46657" --name=tm tendermint/tendermint
docker run -it --rm --link=tm tendermint/monitor tm:46657
Binaries
~~~~~~~~
This will be the same as you did for ``tm-bench`` above, except for the last line which should be:
::
tm-monitor localhost:46657
Usage
^^^^^
::
tm-monitor [-v] [-no-ton] [-listen-addr="tcp://0.0.0.0:46670"] [endpoints]
Examples:
# monitor single instance
tm-monitor localhost:46657
# monitor a few instances by providing comma-separated list of RPC endpoints
tm-monitor host1:46657,host2:46657
Flags:
-listen-addr string
HTTP and Websocket server listen address (default "tcp://0.0.0.0:46670")
-no-ton
Do not show ton (table of nodes)
-v verbose logging
RPC UI
^^^^^^
Run ``tm-monitor`` and visit http://localhost:46670
You should see the list of the available RPC endpoints:
::
http://localhost:46670/status
http://localhost:46670/status/network
http://localhost:46670/monitor?endpoint=_
http://localhost:46670/status/node?name=_
http://localhost:46670/unmonitor?endpoint=_
The API is available as GET requests with URI encoded parameters, or as JSONRPC
POST requests. The JSONRPC methods are also exposed over websocket.
Development
^^^^^^^^^^^
::
make get_vendor_deps
make test

+ 64
- 77
tm-bench/glide.lock View File

@ -1,113 +1,85 @@
hash: 795aa94747f3d877df3ea1ec134e9a34e1c46713dd6eb59b6fdd6a33cb698234
updated: 2017-04-20T19:19:22.26004087-04:00
hash: 46f5e4380fdca1f3c0211f1f54f2c0dcd2213aabbb96cdaaaa201a55156bfb97
updated: 2017-12-07T17:26:11.121786018Z
imports:
- name: github.com/btcsuite/btcd
version: 583684b21bfbde9b5fc4403916fd7c807feb0289
version: 2e60448ffcc6bf78332d1fe590260095f554dd78
subpackages:
- btcec
- name: github.com/BurntSushi/toml
version: 99064174e013895bbd9b025c31100bd1d9b590ca
- name: github.com/go-kit/kit
version: b6f30a2e0632f5722fb26d8765d726335b79d3e6
version: ebf82f4a7270657af57182f212579e5ec8c9fac8
subpackages:
- log
- log/level
- log/term
- term
- name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-playground/locales
version: e4cbcb5d0652150d40ad0646651076b6bd2be4f6
subpackages:
- currency
- name: github.com/go-playground/universal-translator
version: 71201497bace774495daed26a3874fd339e0b538
- name: github.com/go-stack/stack
version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82
version: 259ab82a6cad3992b4e21ff5cac294ccb06474bc
- name: github.com/golang/protobuf
version: 69b215d01a5606c843240eab4937eab3acee6530
version: 1e59b77b52bf8e4b449a57e6f79f21226d571845
subpackages:
- proto
- name: github.com/golang/snappy
version: 553a641470496b2327abcac10b36396bd98e45c9
- ptypes
- ptypes/any
- ptypes/duration
- ptypes/timestamp
- name: github.com/gorilla/websocket
version: 3ab3a8b8831546bd18fd182c20687ca853b2bb13
- name: github.com/jmhodges/levigo
version: c42d9e0ca023e2198120196f842701bb4c55d7b9
version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b
- name: github.com/kr/logfmt
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/mattn/go-colorable
version: d898aa9fb31c91f35dd28ca75db377eff023c076
- name: github.com/mattn/go-isatty
version: dda3de49cbfcec471bd7a70e6cc01fcc3ff90109
- name: github.com/pkg/errors
version: bfd5150e4e41705ded2129ec33379de1cb90b513
version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: github.com/rcrowley/go-metrics
version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c
- name: github.com/syndtr/goleveldb
version: 3c5717caf1475fd25964109a0fc640bd150fce43
subpackages:
- leveldb
- leveldb/cache
- leveldb/comparer
- leveldb/errors
- leveldb/filter
- leveldb/iterator
- leveldb/journal
- leveldb/memdb
- leveldb/opt
- leveldb/storage
- leveldb/table
- leveldb/util
version: e181e095bae94582363434144c61a9653aff6e50
- name: github.com/tendermint/abci
version: 56e13d87f4e3ec1ea756957d6b23caa6ebcf0998
version: 76ef8a0697c6179220a74c479b36c27a5b53008a
subpackages:
- client
- example/dummy
- types
- name: github.com/tendermint/ed25519
version: 1f52c6f8b8a5c7908aff4497c186af344b428925
version: d8387025d2b9d158cf4efb07e7ebf814bcce2057
subpackages:
- edwards25519
- extra25519
- name: github.com/tendermint/go-common
version: f9e3db037330c8a8d61d3966de8473eaf01154fa
- name: github.com/tendermint/go-config
version: 620dcbbd7d587cf3599dedbf329b64311b0c307a
- name: github.com/tendermint/go-crypto
version: 0ca2c6fdb0706001ca4c4b9b80c9f428e8cf39da
- name: github.com/tendermint/go-data
version: e7fcc6d081ec8518912fcdc103188275f83a3ee5
- name: github.com/tendermint/go-db
version: 9643f60bc2578693844aacf380a7c32e4c029fee
- name: github.com/tendermint/go-events
version: fddee66d90305fccb6f6d84d16c34fa65ea5b7f6
- name: github.com/tendermint/go-flowrate
version: a20c98e61957faa93b4014fbd902f20ab9317a6a
subpackages:
- flowrate
- name: github.com/tendermint/go-logger
version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2
- name: github.com/tendermint/go-merkle
version: 714d4d04557fd068a7c2a1748241ce8428015a96
- name: github.com/tendermint/go-p2p
version: 17124989a93774833df33107fbf17157a7f8ef31
subpackages:
- upnp
- name: github.com/tendermint/go-rpc
version: 1a42f946dc6bcd88f9f58c7f2fb86f785584d793
subpackages:
- client
- types
version: dd20358a264c772b4a83e477b0cfce4c88a7001d
- name: github.com/tendermint/go-wire
version: 2f3b7aafe21c80b19b6ee3210ecb3e3d07c7a471
- name: github.com/tendermint/log15
version: ae0f3d6450da9eac7074b439c8e1c3cabf0d5ce6
version: b6fc872b42d41158a60307db4da051dd6f179415
subpackages:
- term
- data
- name: github.com/tendermint/tendermint
version: 083fe959e25421fca3d41298d9111167a3b47122
version: c7f923c5b0d0f0f26566281aa251259d1bef3a6c
subpackages:
- config
- consensus/types
- p2p
- p2p/upnp
- rpc/core/types
- rpc/lib/client
- rpc/lib/types
- types
- name: github.com/tendermint/tmlibs
version: b854baa1fce7101c90b1d301b3359bb412f981c0
subpackages:
- common
- events
- flowrate
- log
- merkle
- name: github.com/tendermint/tools
version: 12ce526668e384100afd32686ec7db3749423d51
version: c36867e971f4d717e7ceb2c3d451a64e9a101b99
subpackages:
- tm-monitor/eventmeter
- tm-monitor/monitor
- name: golang.org/x/crypto
version: 453249f01cfeb54c3d549ddb75ff152ca243f9d8
version: 94eea52f7b742c7cbe0b03b22f0c4c8631ece122
subpackages:
- curve25519
- nacl/box
@ -118,7 +90,7 @@ imports:
- ripemd160
- salsa20/salsa
- name: golang.org/x/net
version: 906cda9512f77671ab44f8c8563b13a8e707b230
version: faacc1b5e36e3ff02cbec9661c69ac63dd5a83ad
subpackages:
- context
- http2
@ -127,21 +99,36 @@ imports:
- internal/timeseries
- lex/httplex
- trace
- name: golang.org/x/sys
version: 76cc09b634294339fa19ec41b5f2a0b3932cea8b
- name: golang.org/x/text
version: be25de41fadfae372d6470bda81ca6beb55ef551
subpackages:
- secure/bidirule
- transform
- unicode/bidi
- unicode/norm
- name: google.golang.org/genproto
version: 7f0da29060c682909f650ad8ed4e515bd74fa12a
subpackages:
- unix
- googleapis/rpc/status
- name: google.golang.org/grpc
version: 8b2e129857480cb0f07ef7d9d10b8b252c7ac984
version: f7bf885db0b7479a537ec317c6e48ce53145f3db
subpackages:
- balancer
- codes
- connectivity
- credentials
- grpclb/grpc_lb_v1/messages
- grpclog
- internal
- keepalive
- metadata
- naming
- peer
- resolver
- stats
- status
- tap
- transport
- name: gopkg.in/go-playground/validator.v9
version: 61caf9d3038e1af346dbf5c2e16f6678e1548364
testImports: []

+ 12
- 8
tm-bench/glide.yaml View File

@ -1,16 +1,20 @@
package: github.com/tendermint/tools/tm-bench
import:
- package: github.com/go-kit/kit
subpackages:
- log/term
- package: github.com/gorilla/websocket
- package: github.com/pkg/errors
- package: github.com/tendermint/go-rpc
version: develop
- package: github.com/rcrowley/go-metrics
- package: github.com/tendermint/tendermint
version: v0.12.1
subpackages:
- client
- rpc/lib/types
- types
- package: github.com/tendermint/tmlibs
subpackages:
- log
- package: github.com/tendermint/tools
version: develop
version: c36867e971f4d717e7ceb2c3d451a64e9a101b99
subpackages:
- tm-monitor/monitor
- package: github.com/go-kit/kit
subpackages:
- log
- term

+ 5
- 4
tm-bench/main.go View File

@ -8,14 +8,15 @@ import (
"text/tabwriter"
"time"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/term"
metrics "github.com/rcrowley/go-metrics"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/tools/tm-monitor/monitor"
)
var version = "0.1.0"
var version = "0.2.1"
var logger = log.NewNopLogger()
@ -63,7 +64,7 @@ Examples:
}
return term.FgBgColor{}
}
logger = term.NewLogger(os.Stdout, log.NewLogfmtLogger, colorFn)
logger = log.NewTMLoggerWithColorFn(log.NewSyncWriter(os.Stdout), colorFn)
}
fmt.Printf("Running %ds test @ %s\n", duration, flag.Arg(0))
@ -123,7 +124,7 @@ func startNodes(endpoints []string, blockCh chan<- tmtypes.Header, blockLatencyC
for i, e := range endpoints {
n := monitor.NewNode(e)
n.SetLogger(log.With(logger, "node", e))
n.SetLogger(logger.With("node", e))
n.SendBlocksTo(blockCh)
n.SendBlockLatenciesTo(blockLatencyCh)
if err := n.Start(); err != nil {


+ 61
- 22
tm-bench/transacter.go View File

@ -1,27 +1,33 @@
package main
import (
"crypto/md5"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"math/rand"
"net"
"net/http"
"net/url"
"os"
"sync"
"time"
"github.com/go-kit/kit/log"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
rpctypes "github.com/tendermint/go-rpc/types"
rpctypes "github.com/tendermint/tendermint/rpc/lib/types"
"github.com/tendermint/tmlibs/log"
)
const (
sendTimeout = 500 * time.Millisecond
sendTimeout = 10 * time.Second
// see https://github.com/tendermint/go-rpc/blob/develop/server/handlers.go#L313
pingPeriod = (30 * 9 / 10) * time.Second
// the size of a transaction in bytes.
txSize = 250
)
type transacter struct {
@ -56,6 +62,8 @@ func (t *transacter) SetLogger(l log.Logger) {
func (t *transacter) Start() error {
t.stopped = false
rand.Seed(time.Now().Unix())
for i := 0; i < t.Connections; i++ {
c, _, err := connect(t.Target)
if err != nil {
@ -90,8 +98,8 @@ func (t *transacter) receiveLoop(connIndex int) {
for {
_, _, err := c.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {
t.logger.Log("err", errors.Wrap(err, "failed to read response"))
if !websocket.IsCloseError(err, websocket.CloseNormalClosure) {
t.logger.Error("failed to read response", "err", err)
}
return
}
@ -104,7 +112,18 @@ func (t *transacter) receiveLoop(connIndex int) {
// sendLoop generates transactions at a given rate.
func (t *transacter) sendLoop(connIndex int) {
c := t.conns[connIndex]
logger := log.With(t.logger, "addr", c.RemoteAddr())
c.SetPingHandler(func(message string) error {
err := c.WriteControl(websocket.PongMessage, []byte(message), time.Now().Add(sendTimeout))
if err == websocket.ErrCloseSent {
return nil
} else if e, ok := err.(net.Error); ok && e.Temporary() {
return nil
}
return err
})
logger := t.logger.With("addr", c.RemoteAddr())
var txNumber = 0
@ -116,24 +135,38 @@ func (t *transacter) sendLoop(connIndex int) {
t.wg.Done()
}()
// hash of the host name is a part of each tx
var hostnameHash [md5.Size]byte
hostname, err := os.Hostname()
if err != nil {
hostname = "127.0.0.1"
}
hostnameHash = md5.Sum([]byte(hostname))
for {
select {
case <-txsTicker.C:
startTime := time.Now()
for i := 0; i < t.Rate; i++ {
// each transaction embeds connection index and tx number
tx := generateTx(connIndex, txNumber)
// each transaction embeds connection index, tx number and hash of the hostname
tx := generateTx(connIndex, txNumber, hostnameHash)
paramsJson, err := json.Marshal(map[string]interface{}{"tx": hex.EncodeToString(tx)})
if err != nil {
fmt.Printf("failed to encode params: %v\n", err)
os.Exit(1)
}
rawParamsJson := json.RawMessage(paramsJson)
c.SetWriteDeadline(time.Now().Add(sendTimeout))
err := c.WriteJSON(rpctypes.RPCRequest{
err = c.WriteJSON(rpctypes.RPCRequest{
JSONRPC: "2.0",
ID: "",
ID: "tm-bench",
Method: "broadcast_tx_async",
Params: []interface{}{hex.EncodeToString(tx)},
Params: rawParamsJson,
})
if err != nil {
fmt.Printf("%v. Try increasing the connections count and reducing the rate.\n", errors.Wrap(err, "txs send failed"))
fmt.Printf("%v. Try reducing the connections count and increasing the rate.\n", errors.Wrap(err, "txs send failed"))
os.Exit(1)
}
@ -142,12 +175,12 @@ func (t *transacter) sendLoop(connIndex int) {
timeToSend := time.Now().Sub(startTime)
time.Sleep(time.Second - timeToSend)
logger.Log("event", fmt.Sprintf("sent %d transactions", t.Rate), "took", timeToSend)
logger.Info(fmt.Sprintf("sent %d transactions", t.Rate), "took", timeToSend)
case <-pingsTicker.C:
// Right now go-rpc server closes the connection in the absence of pings
// go-rpc server closes the connection in the absence of pings
c.SetWriteDeadline(time.Now().Add(sendTimeout))
if err := c.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
logger.Log("err", errors.Wrap(err, "failed to write ping message"))
logger.Error("failed to write ping message", "err", err)
}
}
@ -157,7 +190,7 @@ func (t *transacter) sendLoop(connIndex int) {
c.SetWriteDeadline(time.Now().Add(sendTimeout))
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
logger.Log("err", errors.Wrap(err, "failed to write close message"))
logger.Error("failed to write close message", "err", err)
}
return
@ -170,12 +203,18 @@ func connect(host string) (*websocket.Conn, *http.Response, error) {
return websocket.DefaultDialer.Dial(u.String(), nil)
}
func generateTx(a int, b int) []byte {
tx := make([]byte, 250)
binary.PutUvarint(tx[:32], uint64(a))
binary.PutUvarint(tx[32:64], uint64(b))
if _, err := rand.Read(tx[234:]); err != nil {
panic(errors.Wrap(err, "failed to generate transaction"))
func generateTx(connIndex int, txNumber int, hostnameHash [md5.Size]byte) []byte {
tx := make([]byte, txSize)
binary.PutUvarint(tx[:8], uint64(connIndex))
binary.PutUvarint(tx[8:16], uint64(txNumber))
copy(tx[16:32], hostnameHash[:16])
binary.PutUvarint(tx[32:40], uint64(time.Now().Unix()))
// 40-* random data
if _, err := rand.Read(tx[40:]); err != nil {
panic(errors.Wrap(err, "failed to read random bytes"))
}
return tx
}

+ 1
- 1
tm-monitor/Dockerfile View File

@ -1,4 +1,4 @@
FROM alpine:3.5
FROM alpine:3.6
WORKDIR /app
COPY tm-monitor /app/tm-monitor


+ 1
- 1
tm-monitor/Dockerfile.dev View File

@ -7,6 +7,6 @@ COPY Makefile /go/src/github.com/tendermint/tools/tm-monitor/
COPY glide.yaml /go/src/github.com/tendermint/tools/tm-monitor/
COPY glide.lock /go/src/github.com/tendermint/tools/tm-monitor/
RUN make get_deps
RUN make get_vendor_deps
COPY . /go/src/github.com/tendermint/tools/tm-monitor

+ 1
- 0
tm-monitor/LICENSE View File

@ -1,3 +1,4 @@
Tendermint Monitor
Copyright 2017 Tendermint
Apache License


+ 15
- 15
tm-monitor/Makefile View File

@ -1,48 +1,48 @@
DIST_DIRS := find * -type d -exec
VERSION := $(shell perl -ne '/^var version.*"([^"]+)".*$$/ && print "v$$1\n"' main.go)
GOTOOLS = \
github.com/Masterminds/glide \
github.com/mitchellh/gox
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
PACKAGES=$(shell go list ./... | grep -v '/vendor')
tools:
go get -v $(GOTOOLS)
go get $(GOTOOLS)
get_deps: tools
get_vendor_deps:
@hash glide 2>/dev/null || go get github.com/Masterminds/glide
glide install
build:
go build -ldflags "-X main.version=${VERSION}"
go build
install:
go install -ldflags "-X main.version=${VERSION}"
go install
test:
@go test $(PACKAGES)
@go test -race $(PACKAGES)
build-all: tools
rm -rf ./dist
gox -verbose \
-ldflags "-X main.version=${VERSION}" \
-os="linux darwin windows freebsd openbsd netbsd" \
-arch="amd64 386 armv5 armv6 armv7 arm64" \
-osarch="!darwin/arm64" \
-ldflags "-s -w" \
-arch="amd64 386 arm arm64" \
-os="linux darwin windows freebsd" \
-osarch="!darwin/arm !darwin/arm64" \
-output="dist/{{.OS}}-{{.Arch}}/{{.Dir}}" .
dist: build-all
cd dist && \
$(DIST_DIRS) cp ../LICENSE {} \; && \
$(DIST_DIRS) cp ../README.md {} \; && \
$(DIST_DIRS) tar -zcf tm-monitor-${VERSION}-{}.tar.gz {} \; && \
$(DIST_DIRS) zip -r tm-monitor-${VERSION}-{}.zip {} \; && \
shasum -a256 ./*.tar.gz > "./tm-monitor_${VERSION}_SHA256SUMS" && \
cd ..
build-docker:
rm -f ./tm-monitor
docker run -it --rm -v "$(PWD):/go/src/github.com/tendermint/tools/tm-monitor" -w "/go/src/github.com/tendermint/tools/tm-monitor" -e "CGO_ENABLED=0" golang:alpine go build -ldflags "-X main.version=${VERSION}" -o tm-monitor
docker run -it --rm -v "$(PWD):/go/src/github.com/tendermint/tools/tm-monitor" -w "/go/src/github.com/tendermint/tools/tm-monitor" -e "CGO_ENABLED=0" golang:alpine go build -ldflags "-s -w" -o tm-monitor
docker build -t "tendermint/monitor" .
clean:
rm -f ./tm-monitor
rm -rf ./dist
.PHONY: tools get_deps build install test build-all dist clean build-docker
.PHONY: tools get_vendor_deps build install test build-all dist clean build-docker

+ 0
- 123
tm-monitor/README.md View File

@ -1,123 +0,0 @@
# Tendermint monitor (tm-monitor)
Tendermint monitor watches over one or more [Tendermint
core](https://github.com/tendermint/tendermint) applications (nodes),
collecting and providing various statistics to the user.
* [QuickStart using Docker](#quickstart-using-docker)
* [QuickStart using binaries](#quickstart-using-binaries)
* [Usage](#usage)
* [RPC UI](#rpc-ui)
## QuickStart using Docker
```
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint init
docker run -it --rm -v "/tmp:/tendermint" -p "46657:46657" --name=tm tendermint/tendermint
docker run -it --rm --link=tm tendermint/monitor tm:46657
```
## QuickStart using binaries
Linux:
```
curl -L https://s3-us-west-2.amazonaws.com/tendermint/0.8.0/tendermint_linux_amd64.zip && sudo unzip -d /usr/local/bin tendermint_linux_amd64.zip && sudo chmod +x tendermint
tendermint init
tendermint node --app_proxy=dummy
tm-monitor localhost:46657
```
Max OS:
```
curl -L https://s3-us-west-2.amazonaws.com/tendermint/0.8.0/tendermint_darwin_amd64.zip && sudo unzip -d /usr/local/bin tendermint_darwin_amd64.zip && sudo chmod +x tendermint
tendermint init
tendermint node --app_proxy=dummy
tm-monitor localhost:46657
```
## Usage
```
tm-monitor [-v] [-no-ton] [-listen-addr="tcp://0.0.0.0:46670"] [endpoints]
Examples:
# monitor single instance
tm-monitor localhost:46657
# monitor a few instances by providing comma-separated list of RPC endpoints
tm-monitor host1:46657,host2:46657
Flags:
-listen-addr string
HTTP and Websocket server listen address (default "tcp://0.0.0.0:46670")
-no-ton
Do not show ton (table of nodes)
-v verbose logging
```
[![asciicast](https://asciinema.org/a/105974.png)](https://asciinema.org/a/105974)
### RPC UI
Run `tm-monitor` and visit [http://localhost:46670](http://localhost:46670).
You should see the list of the available RPC endpoints:
```
http://localhost:46670/status
http://localhost:46670/status/network
http://localhost:46670/monitor?endpoint=_
http://localhost:46670/status/node?name=_
http://localhost:46670/unmonitor?endpoint=_
```
The API is available as GET requests with URI encoded parameters, or as JSONRPC
POST requests. The JSONRPC methods are also exposed over websocket.
### Ideas
- currently we get IPs and dial, but should reverse so the nodes dial the
netmon, both for node privacy and easier reconfig (validators changing
ip/port). It would be good to have both. For testnets with others we def need
them to dial the monitor. But I want to be able to run the monitor from my
laptop without openning ports.
If we don't want to open all the ports, maybe something like this would be a
good fit for us: tm-monitor agent running on each node, collecting all the
metrics. Each tm-monitor agent monitors local TM node and sends stats to a
single master tm-monitor master. That way we'll only need to open a single
port for UI on the node with tm-monitor master. And I believe it could be
done with a single package with a few subcommands.
```
# agent collecting metrics from localhost (default)
tm-monitor agent --master="192.168.1.17:8888"
# agent collecting metrics from another TM node (useful for testing, development)
tm-monitor agent --master="192.168.1.17:8888" --node="192.168.1.18:46657"
# master accepting stats from agents
tm-monitor master [--ton] OR [--ui] (`--ui` mode by default)
# display table of nodes in the terminal (useful for testing, development, playing with TM)
# --nodes="localhost:46657" by default
tm-monitor
# display table of nodes in the terminal (useful for testing, development, playing with TM)
tm-monitor --nodes="192.168.1.18:46657,192.168.1.19:46657"
```
- uptime over last day, month, year. There are different meanings for uptime.
One is to constantly ping the nodes and make sure they respond to eg.
/status. A more fine-grained one is to check for votes in the block commits.
- show network size + auto discovery. You can get a list of connected peers at
/net_info. But no single one will be connected to the whole network, so need
to tease out all the unique peers from calling /net_info on all of them.
Unless you have some prior information about how many peers in the net ...
More: we could add `-auto-discovery` option and try to connect to every node.
- input plugin for https://github.com/influxdata/telegraf, so the user is able
to get the metrics and send them whenever he wants to (grafana, prometheus,
etc.).
Feel free to vote on the ideas or add your own by saying hello on
[Slack](http://forum.tendermint.com:3000/) or by opening an issue.

+ 1
- 0
tm-monitor/README.rst View File

@ -0,0 +1 @@
NOTE: Please see the ``tm-bench`` directory for the README about tm-monitor. You can also find the documentation at: http://tendermint.readthedocs.io

+ 110
- 138
tm-monitor/eventmeter/eventmeter.go View File

@ -1,30 +1,28 @@
// eventmeter - generic system to subscribe to events and record their frequency.
package eventmeter
import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
metrics "github.com/rcrowley/go-metrics"
client "github.com/tendermint/tendermint/rpc/lib/client"
"github.com/tendermint/tmlibs/events"
"github.com/tendermint/tmlibs/log"
)
//------------------------------------------------------
// Generic system to subscribe to events and record their frequency
//------------------------------------------------------
const (
// Get ping/pong latency and call LatencyCallbackFunc with this period.
latencyPeriod = 1 * time.Second
//------------------------------------------------------
// Meter for a particular event
// Closure to enable side effects from receiving an event
type EventCallbackFunc func(em *EventMetric, data events.EventData)
// Check if the WS client is connected every
connectionCheckPeriod = 100 * time.Millisecond
)
// Metrics for a given event
// EventMetric exposes metrics for an event.
type EventMetric struct {
ID string `json:"id"`
Started time.Time `json:"start_time"`
@ -42,15 +40,15 @@ type EventMetric struct {
Rate15 float64 `json:"rate_15" wire:"unsafe"`
RateMean float64 `json:"rate_mean" wire:"unsafe"`
// so the event can have effects in the event-meter's consumer.
// runs in a go routine
// so the event can have effects in the eventmeter's consumer. runs in a go
// routine.
callback EventCallbackFunc
}
func (metric *EventMetric) Copy() *EventMetric {
metric2 := *metric
metric2.meter = metric.meter.Snapshot()
return &metric2
metricCopy := *metric
metricCopy.meter = metric.meter.Snapshot()
return &metricCopy
}
// called on GetMetric
@ -63,35 +61,32 @@ func (metric *EventMetric) fillMetric() *EventMetric {
return metric
}
//------------------------------------------------------
// Websocket client and event meter for many events
// EventCallbackFunc is a closure to enable side effects from receiving an
// event.
type EventCallbackFunc func(em *EventMetric, data interface{})
const maxPingsPerPong = 30 // if we haven't received a pong in this many attempted pings we kill the conn
// Get the eventID and data out of the raw json received over the go-rpc websocket
// EventUnmarshalFunc is a closure to get the eventType and data out of the raw
// JSON received over the RPC WebSocket.
type EventUnmarshalFunc func(b json.RawMessage) (string, events.EventData, error)
// Closure to enable side effects from receiving a pong
// LatencyCallbackFunc is a closure to enable side effects from receiving a latency.
type LatencyCallbackFunc func(meanLatencyNanoSeconds float64)
// Closure to notify consumer that the connection died
// DisconnectCallbackFunc is a closure to notify a consumer that the connection
// has died.
type DisconnectCallbackFunc func()
// Each node gets an event meter to track events for that node
// EventMeter tracks events, reports latency and disconnects.
type EventMeter struct {
wsc *client.WSClient
mtx sync.Mutex
events map[string]*EventMetric
// to record ws latency
timer metrics.Timer
lastPing time.Time
receivedPong bool
unmarshalEvent EventUnmarshalFunc
latencyCallback LatencyCallbackFunc
disconnectCallback DisconnectCallbackFunc
unmarshalEvent EventUnmarshalFunc
subscribed bool
quit chan struct{}
@ -99,54 +94,44 @@ type EventMeter struct {
}
func NewEventMeter(addr string, unmarshalEvent EventUnmarshalFunc) *EventMeter {
em := &EventMeter{
wsc: client.NewWSClient(addr, "/websocket"),
return &EventMeter{
wsc: client.NewWSClient(addr, "/websocket", client.PingPeriod(1*time.Second)),
events: make(map[string]*EventMetric),
timer: metrics.NewTimer(),
receivedPong: true,
unmarshalEvent: unmarshalEvent,
logger: log.NewNopLogger(),
}
return em
}
// SetLogger lets you set your own logger
// SetLogger lets you set your own logger.
func (em *EventMeter) SetLogger(l log.Logger) {
em.logger = l
em.wsc.SetLogger(l.With("module", "rpcclient"))
}
// String returns a string representation of event meter.
func (em *EventMeter) String() string {
return em.wsc.Address
}
// Start boots up event meter.
func (em *EventMeter) Start() error {
if _, err := em.wsc.Reset(); err != nil {
return err
}
if _, err := em.wsc.Start(); err != nil {
return err
}
em.wsc.Conn.SetPongHandler(func(m string) error {
// NOTE: https://github.com/gorilla/websocket/issues/97
em.mtx.Lock()
defer em.mtx.Unlock()
em.receivedPong = true
em.timer.UpdateSince(em.lastPing)
if em.latencyCallback != nil {
go em.latencyCallback(em.timer.Mean())
}
return nil
})
em.quit = make(chan struct{})
go em.receiveRoutine()
go em.disconnectRoutine()
return em.resubscribe()
err := em.subscribe()
if err != nil {
return err
}
em.subscribed = true
return nil
}
// Stop stops the EventMeter.
// Stop stops event meter.
func (em *EventMeter) Stop() {
close(em.quit)
@ -155,88 +140,70 @@ func (em *EventMeter) Stop() {
}
}
// StopAndCallDisconnectCallback stops the EventMeter and calls
// disconnectCallback if present.
func (em *EventMeter) StopAndCallDisconnectCallback() {
if em.wsc.IsRunning() {
em.wsc.Stop()
}
em.mtx.Lock()
defer em.mtx.Unlock()
if em.disconnectCallback != nil {
go em.disconnectCallback()
}
}
func (em *EventMeter) Subscribe(eventID string, cb EventCallbackFunc) error {
// Subscribe for the given event type. Callback function will be called upon
// receiving an event.
func (em *EventMeter) Subscribe(eventType string, cb EventCallbackFunc) error {
em.mtx.Lock()
defer em.mtx.Unlock()
if _, ok := em.events[eventID]; ok {
if _, ok := em.events[eventType]; ok {
return fmt.Errorf("subscribtion already exists")
}
if err := em.wsc.Subscribe(eventID); err != nil {
if err := em.wsc.Subscribe(context.TODO(), eventType); err != nil {
return err
}
metric := &EventMetric{
ID: eventID,
Started: time.Now(),
MinDuration: 1 << 62,
meter: metrics.NewMeter(),
callback: cb,
meter: metrics.NewMeter(),
callback: cb,
}
em.events[eventID] = metric
em.events[eventType] = metric
return nil
}
func (em *EventMeter) Unsubscribe(eventID string) error {
// Unsubscribe from the given event type.
func (em *EventMeter) Unsubscribe(eventType string) error {
em.mtx.Lock()
defer em.mtx.Unlock()
if err := em.wsc.Unsubscribe(eventID); err != nil {
if err := em.wsc.Unsubscribe(context.TODO(), eventType); err != nil {
return err
}
// XXX: should we persist or save this info first?
delete(em.events, eventID)
delete(em.events, eventType)
return nil
}
// Fill in the latest data for an event and return a copy
func (em *EventMeter) GetMetric(eventID string) (*EventMetric, error) {
// GetMetric fills in the latest data for an event and return a copy.
func (em *EventMeter) GetMetric(eventType string) (*EventMetric, error) {
em.mtx.Lock()
defer em.mtx.Unlock()
metric, ok := em.events[eventID]
metric, ok := em.events[eventType]
if !ok {
return nil, fmt.Errorf("Unknown event %s", eventID)
return nil, fmt.Errorf("unknown event: %s", eventType)
}
return metric.fillMetric().Copy(), nil
}
// Return the average latency over the websocket
func (em *EventMeter) Latency() float64 {
em.mtx.Lock()
defer em.mtx.Unlock()
return em.timer.Mean()
}
// RegisterLatencyCallback allows you to set latency callback.
func (em *EventMeter) RegisterLatencyCallback(f LatencyCallbackFunc) {
em.mtx.Lock()
defer em.mtx.Unlock()
em.latencyCallback = f
}
// RegisterDisconnectCallback allows you to set disconnect callback.
func (em *EventMeter) RegisterDisconnectCallback(f DisconnectCallbackFunc) {
em.mtx.Lock()
defer em.mtx.Unlock()
em.disconnectCallback = f
}
//------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
// Private
func (em *EventMeter) resubscribe() error {
for eventID, _ := range em.events {
if err := em.wsc.Subscribe(eventID); err != nil {
func (em *EventMeter) subscribe() error {
for eventType, _ := range em.events {
if err := em.wsc.Subscribe(context.TODO(), eventType); err != nil {
return err
}
}
@ -244,40 +211,27 @@ func (em *EventMeter) resubscribe() error {
}
func (em *EventMeter) receiveRoutine() {
pingTime := time.Second * 1
pingTicker := time.NewTicker(pingTime)
pingAttempts := 0 // if this hits maxPingsPerPong we kill the conn
var err error
latencyTicker := time.NewTicker(latencyPeriod)
for {
select {
case <-pingTicker.C:
if pingAttempts, err = em.pingForLatency(pingAttempts); err != nil {
em.logger.Error("err", errors.Wrap(err, "failed to write ping message on websocket"))
em.StopAndCallDisconnectCallback()
return
} else if pingAttempts >= maxPingsPerPong {
em.logger.Error("err", errors.Errorf("Have not received a pong in %v", time.Duration(pingAttempts)*pingTime))
em.StopAndCallDisconnectCallback()
return
}
case r := <-em.wsc.ResultsCh:
if r == nil {
em.logger.Error("err", errors.New("Expected some event, received nil"))
em.StopAndCallDisconnectCallback()
return
case resp := <-em.wsc.ResponsesCh:
if resp.Error != nil {
em.logger.Error("expected some event, got error", "err", resp.Error.Error())
continue
}
eventID, data, err := em.unmarshalEvent(r)
eventType, data, err := em.unmarshalEvent(*resp.Result)
if err != nil {
em.logger.Error("err", errors.Wrap(err, "failed to unmarshal event"))
em.logger.Error("failed to unmarshal event", "err", err)
continue
}
if eventID != "" {
em.updateMetric(eventID, data)
if eventType != "" { // FIXME how can it be an empty string?
em.updateMetric(eventType, data)
}
case <-latencyTicker.C:
if em.wsc.IsActive() {
em.callLatencyCallback(em.wsc.PingPongLatencyTimer.Mean())
}
case <-em.wsc.Quit:
em.logger.Error("err", errors.New("WSClient closed unexpectedly"))
em.StopAndCallDisconnectCallback()
return
case <-em.quit:
return
@ -285,29 +239,31 @@ func (em *EventMeter) receiveRoutine() {
}
}
func (em *EventMeter) pingForLatency(pingAttempts int) (int, error) {
em.mtx.Lock()
defer em.mtx.Unlock()
// ping to record latency
if !em.receivedPong {
return pingAttempts + 1, nil
}
em.lastPing = time.Now()
em.receivedPong = false
err := em.wsc.Conn.WriteMessage(websocket.PingMessage, []byte{})
if err != nil {
return pingAttempts, err
func (em *EventMeter) disconnectRoutine() {
ticker := time.NewTicker(connectionCheckPeriod)
for {
select {
case <-ticker.C:
if em.wsc.IsReconnecting() && em.subscribed { // notify user about disconnect only once
em.callDisconnectCallback()
em.subscribed = false
} else if !em.wsc.IsReconnecting() && !em.subscribed { // resubscribe
em.subscribe()
em.subscribed = true
}
case <-em.wsc.Quit:
return
case <-em.quit:
return
}
}
return 0, nil
}
func (em *EventMeter) updateMetric(eventID string, data events.EventData) {
func (em *EventMeter) updateMetric(eventType string, data events.EventData) {
em.mtx.Lock()
defer em.mtx.Unlock()
metric, ok := em.events[eventID]
metric, ok := em.events[eventType]
if !ok {
// we already unsubscribed, or got an unexpected event
return
@ -328,3 +284,19 @@ func (em *EventMeter) updateMetric(eventID string, data events.EventData) {
go metric.callback(metric.Copy(), data)
}
}
func (em *EventMeter) callDisconnectCallback() {
em.mtx.Lock()
if em.disconnectCallback != nil {
go em.disconnectCallback()
}
em.mtx.Unlock()
}
func (em *EventMeter) callLatencyCallback(meanLatencyNanoSeconds float64) {
em.mtx.Lock()
if em.latencyCallback != nil {
go em.latencyCallback(meanLatencyNanoSeconds)
}
em.mtx.Unlock()
}

+ 33
- 10
tm-monitor/glide.lock View File

@ -1,5 +1,5 @@
hash: 80c204190057df1e74d32ecd7095e8a1a865c3a06671f1a31d5240e1e3ff2c64
updated: 2017-05-20T17:49:23.646798165-04:00
hash: 156fcaac82d95af15aa920438cd12ab6ba1ac0ea5dfe8a5ca7eae94eeae625be
updated: 2017-12-06T18:01:20.739645218Z
imports:
- name: github.com/btcsuite/btcd
version: 583684b21bfbde9b5fc4403916fd7c807feb0289
@ -13,22 +13,32 @@ imports:
- log/term
- name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-playground/locales
version: e4cbcb5d0652150d40ad0646651076b6bd2be4f6
subpackages:
- currency
- name: github.com/go-playground/universal-translator
version: 71201497bace774495daed26a3874fd339e0b538
- name: github.com/go-stack/stack
version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82
- name: github.com/golang/protobuf
version: 69b215d01a5606c843240eab4937eab3acee6530
subpackages:
- proto
- ptypes
- ptypes/any
- ptypes/duration
- ptypes/timestamp
- name: github.com/gorilla/websocket
version: 3ab3a8b8831546bd18fd182c20687ca853b2bb13
version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b
- name: github.com/kr/logfmt
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/pkg/errors
version: bfd5150e4e41705ded2129ec33379de1cb90b513
version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: github.com/rcrowley/go-metrics
version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c
- name: github.com/tendermint/abci
version: 864d1f80b36b440bde030a5c18d8ac3aa8c2949d
version: 76ef8a0697c6179220a74c479b36c27a5b53008a
subpackages:
- client
- example/dummy
@ -39,15 +49,16 @@ imports:
- edwards25519
- extra25519
- name: github.com/tendermint/go-crypto
version: 7dff40942a64cdeefefa9446b2d104750b349f8a
version: dd20358a264c772b4a83e477b0cfce4c88a7001d
- name: github.com/tendermint/go-wire
version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb
version: b6fc872b42d41158a60307db4da051dd6f179415
subpackages:
- data
- name: github.com/tendermint/tendermint
version: 267f134d44e76efb2adef5f0c993da8a5d5bd1b8
version: c7f923c5b0d0f0f26566281aa251259d1bef3a6c
subpackages:
- config
- consensus/types
- p2p
- p2p/upnp
- rpc/core/types
@ -56,7 +67,7 @@ imports:
- rpc/lib/types
- types
- name: github.com/tendermint/tmlibs
version: 306795ae1d8e4f4a10dcc8bdb32a00455843c9d5
version: b854baa1fce7101c90b1d301b3359bb412f981c0
subpackages:
- common
- events
@ -84,19 +95,31 @@ imports:
- internal/timeseries
- lex/httplex
- trace
- name: google.golang.org/genproto
version: 7f0da29060c682909f650ad8ed4e515bd74fa12a
subpackages:
- googleapis/rpc/status
- name: google.golang.org/grpc
version: 8b2e129857480cb0f07ef7d9d10b8b252c7ac984
version: f7bf885db0b7479a537ec317c6e48ce53145f3db
subpackages:
- balancer
- codes
- connectivity
- credentials
- grpclb/grpc_lb_v1/messages
- grpclog
- internal
- keepalive
- metadata
- naming
- peer
- resolver
- stats
- status
- tap
- transport
- name: gopkg.in/go-playground/validator.v9
version: 61caf9d3038e1af346dbf5c2e16f6678e1548364
testImports:
- name: github.com/davecgh/go-spew
version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9


+ 3
- 4
tm-monitor/glide.yaml View File

@ -1,21 +1,20 @@
package: github.com/tendermint/tools/tm-monitor
import:
- package: github.com/go-kit/kit
subpackages:
- log
- package: github.com/gorilla/websocket
- package: github.com/pkg/errors
- package: github.com/rcrowley/go-metrics
- package: github.com/tendermint/go-crypto
- package: github.com/tendermint/tendermint
version: develop
version: v0.12.1
subpackages:
- rpc/core/types
- rpc/lib/client
- rpc/lib/server
- types
- package: github.com/tendermint/tmlibs
version: v0.4.1
subpackages:
- common
- events
- log
testImport:


+ 2
- 2
tm-monitor/main.go View File

@ -11,7 +11,7 @@ import (
monitor "github.com/tendermint/tools/tm-monitor/monitor"
)
var version = "0.2.1"
var version = "0.3.1"
var logger = log.NewNopLogger()
@ -47,7 +47,7 @@ Examples:
}
if noton {
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "tm-monitor")
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
}
m := startMonitor(flag.Arg(0))


tm-monitor/mock/mock.go → tm-monitor/mock/eventmeter.go View File


+ 3
- 3
tm-monitor/monitor/monitor_test.go View File

@ -61,10 +61,10 @@ func startMonitor(t *testing.T) *monitor.Monitor {
func createValidatorNode(t *testing.T) (n *monitor.Node, emMock *mock.EventMeter) {
emMock = &mock.EventMeter{}
stubs := make(map[string]ctypes.TMResult)
stubs := make(map[string]interface{})
pubKey := crypto.GenPrivKeyEd25519().PubKey()
stubs["validators"] = &ctypes.ResultValidators{BlockHeight: blockHeight, Validators: []*tmtypes.Validator{tmtypes.NewValidator(pubKey, 0)}}
stubs["status"] = &ctypes.ResultStatus{PubKey: pubKey}
stubs["validators"] = ctypes.ResultValidators{BlockHeight: blockHeight, Validators: []*tmtypes.Validator{tmtypes.NewValidator(pubKey, 0)}}
stubs["status"] = ctypes.ResultStatus{PubKey: pubKey}
rpcClientMock := &mock.RpcClient{stubs}
n = monitor.NewNodeWithEventMeterAndRpcClient("tcp://127.0.0.1:46657", emMock, rpcClientMock)


+ 5
- 16
tm-monitor/monitor/node.go View File

@ -125,11 +125,11 @@ func (n *Node) Stop() {
// implements eventmeter.EventCallbackFunc
func newBlockCallback(n *Node) em.EventCallbackFunc {
return func(metric *em.EventMetric, data events.EventData) {
return func(metric *em.EventMetric, data interface{}) {
block := data.(tmtypes.TMEventData).Unwrap().(tmtypes.EventDataNewBlockHeader).Header
n.Height = uint64(block.Height)
n.logger.Info("event", "new block", "height", block.Height, "numTxs", block.NumTxs)
n.logger.Info("new block", "height", block.Height, "numTxs", block.NumTxs)
if n.blockCh != nil {
n.blockCh <- *block
@ -141,7 +141,7 @@ func newBlockCallback(n *Node) em.EventCallbackFunc {
func latencyCallback(n *Node) em.LatencyCallbackFunc {
return func(latency float64) {
n.BlockLatency = latency / 1000000.0 // ns to ms
n.logger.Info("event", "new block latency", "latency", n.BlockLatency)
n.logger.Info("new block latency", "latency", n.BlockLatency)
if n.blockLatencyCh != nil {
n.blockLatencyCh <- latency
@ -158,17 +158,6 @@ func disconnectCallback(n *Node) em.DisconnectCallbackFunc {
if n.disconnectCh != nil {
n.disconnectCh <- true
}
if err := n.RestartEventMeterBackoff(); err != nil {
n.logger.Info("err", errors.Wrap(err, "restart failed"))
} else {
n.Online = true
n.logger.Info("status", "online")
if n.disconnectCh != nil {
n.disconnectCh <- false
}
}
}
}
@ -180,7 +169,7 @@ func (n *Node) RestartEventMeterBackoff() error {
time.Sleep(d * time.Second)
if err := n.em.Start(); err != nil {
n.logger.Info("err", errors.Wrap(err, "restart failed"))
n.logger.Info("restart failed", "err", err)
} else {
// TODO: authenticate pubkey
return nil
@ -231,7 +220,7 @@ func (n *Node) checkIsValidator() {
}
}
} else {
n.logger.Info("err", errors.Wrap(err, "check is validator failed"))
n.logger.Info("check is validator failed", "err", err)
}
}


+ 5
- 8
tm-monitor/monitor/node_test.go View File

@ -37,7 +37,7 @@ func TestNodeNewBlockReceived(t *testing.T) {
n.SendBlocksTo(blockCh)
blockHeader := &tmtypes.Header{Height: 5}
emMock.Call("eventCallback", &em.EventMetric{}, tmtypes.EventDataNewBlockHeader{blockHeader})
emMock.Call("eventCallback", &em.EventMetric{}, tmtypes.TMEventData{tmtypes.EventDataNewBlockHeader{blockHeader}})
assert.Equal(uint64(5), n.Height)
assert.Equal(*blockHeader, <-blockCh)
@ -68,10 +68,7 @@ func TestNodeConnectionLost(t *testing.T) {
emMock.Call("disconnectCallback")
assert.Equal(true, <-disconnectCh)
assert.Equal(false, <-disconnectCh)
// we're back in a race
assert.Equal(true, n.Online)
assert.Equal(false, n.Online)
}
func TestNumValidators(t *testing.T) {
@ -89,10 +86,10 @@ func TestNumValidators(t *testing.T) {
func startValidatorNode(t *testing.T) (n *monitor.Node, emMock *mock.EventMeter) {
emMock = &mock.EventMeter{}
stubs := make(map[string]ctypes.TMResult)
stubs := make(map[string]interface{})
pubKey := crypto.GenPrivKeyEd25519().PubKey()
stubs["validators"] = &ctypes.ResultValidators{BlockHeight: blockHeight, Validators: []*tmtypes.Validator{tmtypes.NewValidator(pubKey, 0)}}
stubs["status"] = &ctypes.ResultStatus{PubKey: pubKey}
stubs["validators"] = ctypes.ResultValidators{BlockHeight: blockHeight, Validators: []*tmtypes.Validator{tmtypes.NewValidator(pubKey, 0)}}
stubs["status"] = ctypes.ResultStatus{PubKey: pubKey}
rpcClientMock := &mock.RpcClient{stubs}
n = monitor.NewNodeWithEventMeterAndRpcClient("tcp://127.0.0.1:46657", emMock, rpcClientMock)


+ 1
- 2
tm-monitor/rpc.go View File

@ -12,9 +12,8 @@ import (
func startRPC(listenAddr string, m *monitor.Monitor, logger log.Logger) {
routes := routes(m)
// serve http and ws
mux := http.NewServeMux()
wm := rpc.NewWebsocketManager(routes, nil) // TODO: evsw
wm := rpc.NewWebsocketManager(routes, nil)
mux.HandleFunc("/websocket", wm.WebsocketHandler)
rpc.RegisterRPCFuncs(mux, routes, logger)
if _, err := rpc.StartHTTPServer(listenAddr, mux, logger); err != nil {


Loading…
Cancel
Save