https://github.com/santhosh-tekuri/fnet
programmable hosts, firewall, bandwidth to test network failures in unit testing
https://github.com/santhosh-tekuri/fnet
bandwidth clusters golang network-failures split-brain testing
Last synced: about 1 year ago
JSON representation
programmable hosts, firewall, bandwidth to test network failures in unit testing
- Host: GitHub
- URL: https://github.com/santhosh-tekuri/fnet
- Owner: santhosh-tekuri
- License: apache-2.0
- Created: 2019-02-13T09:20:23.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2019-04-09T08:28:04.000Z (about 7 years ago)
- Last Synced: 2025-02-02T07:27:01.287Z (over 1 year ago)
- Topics: bandwidth, clusters, golang, network-failures, split-brain, testing
- Language: Go
- Homepage:
- Size: 175 KB
- Stars: 1
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
#fnet
[](https://opensource.org/licenses/Apache-2.0)
[](https://godoc.org/github.com/santhosh-tekuri/fnet)
[](https://goreportcard.com/report/github.com/santhosh-tekuri/fnet)
[](https://travis-ci.org/santhosh-tekuri/fnet)
[](https://codecov.io/github/santhosh-tekuri/fnet?branch=master)
Package fnet provides programmable firewall, bandwidth to test
network failures in unit testing.
This package (fnet stands for fakenet) is intended for use in unit-testing network related failures.
Your library does not need any dependency on this package for this. only your
tests need this package as dependency.
This package simply wraps `net.Listen`, `net.Dial`, `net.Conn` implementation on
`tcp:localhost`. It enforces firewall/bandwidth before delegating to actual
implementation
Some minimal changes needs to be done in your library for this. Consider following
simple library code, to demonstrate the changes:
~~~go
package myapp
type Server struct{
HostPort string
....
}
func (s *Server) launch() {
lr, err := net.Listen("tcp", s.HostPort)
...
}
type Client struct{
HostPort string
...
}
func (c *client) sendReq(req string) error {
conn, err := net.Dial("tcp", c.HostPort)
...
}
~~~
You have to mock `net.Listen`, `net.Dial` in your code. For this introduce transport interface as shown below:
~~~go
package myapp
type listenFn func(network, address string) (net.Listener, error)
type dialFn func(network, address string) (net.Conn, error)
type Server struct{
HostPort string
listenFn listenFn // intialize to net.Listen
....
}
func (s *Server) launch() {
lr, err := s.listenFn("tcp", s.HostPort)
...
}
type Client struct{
HostPort string
dialFn dialFn // initialize to net.Dial
...
}
func (c *client) sendReq(req string) error {
conn, err := c.dialFn("tcp", c.HostPort)
...
}
// unit test code ---------------------
func TestServer(t *testing.T) {
// create network of 3 hosts
nw := fnet.New()
earth, mars, venus := nw.Host("earth"), nw.Host("mars"), nw.Host("venus")
s1 := &Server{HostPort: "earth:80", listenFn: earth.Listen} // server1 running on earth
s2 := &Server{HostPort: "mars:80", listenFn: mars.Listen} // server2 running on mars
c := &Client{Servers: []{"earth:80", "mars:80"}, dialFn: venus.Dial} // client is running on venus
// make s1 unreachable to client
nw.SetFirewall(fnet.Split([]string{"earth"}, fnet.AllowAll))
if reply, err := c.SendReq("hello"); err!=nil {
t.Fatal("expected to connect s2")
}
// now make s2 unreachable to client, but not s1
nw.SetFirewall(fnet.Split([]string{"mars"}, fnet.AllowAll))
if reply, err := c.SendReq("hello"); err!=nil {
t.Fatal("expected to connect s1")
}
}
~~~
You can mock `net.Listen`, `net.Dial` and `net.DialTimeout` using this package as shown above
Now you can various network failures as shown above using `fnet.Firewall` `fnet.Bandwidth`
## Firewalls
This package provides 3 implementations of firewall:
### AllowAll:
This does not block any network traffic.
This is the default firewall set on newly created network.
### AllowSelf:
This blocks traffic between distinct hosts.
Note that traffic within the host is allowed.
Consider network with hosts m1, m2, m3 and m4,
AllowSelf creates 4 network partitions: m1 | m2 | m3 | m4
### Split:
This implements network partioning. Mutiple partitions
can be defined by chaining. See example below:
~~~go
// Consider network with hosts m1, m2, m3, m4, m5 and m6
// 2 partitions: m1 m2 | m3 m4 m5 m6
firewall := fnet.Split([]string{"m1", "m2"}, fnet.AllowAll)
// 3 partitions: m1 m2 | m3 m4 | m5 m6
firewall := fnet.Split([]string{"m1", "m2"}, fnet.AllowAll)
firewall = fnet.Split([]string{"m3", "m4"}, firewall) // chaining
~~~
You can create your own firewall implementation if needed. It is simple single method interface:
~~~go
type Firewall interface {
Allow(host1, host2 string) bool
}
~~~
## Bandwidth
to set bandwidth between two hosts:
~~~go
nw := fnet.New()
earth, mars := nw.Host("earth"), nw.Host("mars")
nw.SetBandwidth("earth", "mars", fnet.Bandwidth(10*1024*1024)) // 10MB per second between earth and mars
// to revert
nw.SetBandwidth("earth", "mars", fnet.NoLimit)
~~~