Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@

# Dependency directories (remove the comment below to include it)
# vendor/

.vscode
32 changes: 32 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.PHONY: bootstrap
bootstrap:
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
go get -u github.com/kyoh86/richgo
go get -v github.com/ramya-rao-a/go-outline
go get -v github.com/mdempsky/gocode
go get -v github.com/uudashr/gopkgs/cmd/gopkgs
go get -v golang.org/x/tools/cmd/goimports
go get -v golang.org/x/tools/cmd/goimports

.PHONY: lint
lint: fmt
golangci-lint run

.PHONY: test-flake
test-ci:
@echo "tests:"
richgo test -count=30 -v -cover ./...

.PHONY: install-golang-ci
lint-ci:
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh

.PHONY: generate
generate:
protoc --proto_path=orcareduce/model --go_out=orcareduce/model --go_opt=paths=source_relative datamodel.proto

.PHONY: fmt
fmt:
@echo "fmt:"
scripts/fmt

1 change: 1 addition & 0 deletions OWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mkuchenbecker
12 changes: 12 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module github.com/mkuchenbecker/orcareduce

go 1.13

require (
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/mock v1.3.1
github.com/influxdata/influxdb v1.8.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.5.1
google.golang.org/protobuf v1.24.0 // indirect
)
355 changes: 355 additions & 0 deletions go.sum

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions orcareduce/chaos.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package orcareduce

// Injector is an object capable of both injecting latency and errors. It's primary use is to
// artificially increase the error rate and latency during testing to help simulate degraded
// performance.
//
// func (foo *Foo) DoSomethingReliable() error {
// if err := foo.ChaosInjector.Error(); err != nil {
// return err
// }
// foo.ChaosInjector.Latency() // Blocks the thread for a period of time.
// ... (remainder of function)
// }
type Injector interface {
Latency()
Error() error
}

// ErrorInjector is an object that can inject errors into a process or function. It's primary
// use is to inject errors into otherwise reliable functions for testing purposes.
type ErrorInjector interface {
Error() error
}

// LatencyInjector is an object that blocks the thread for a period of time. It's primary
// use is to inject latency into otherwise performant functions.
type LatencyInjector interface {
Latency()
}
18 changes: 18 additions & 0 deletions orcareduce/chaos/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package chaos

/*
chaos is a package to allow the random insertion of errors and latetency. The purpose of injecting latency and errors
is to build a system with the expectation that any function, no matter how reliable, can become transiently unreliable
and should be tested as such.

NewDefault() is a basic meta-injector that will both inject chaos errors and latency. Use of this package would be:

func (foo *Foo) DoSomethingReliable() error {
if err := foo.ChaosInjector.Error(); err != nil {
return err
}
foo.ChaosInjector.Latency() // Blocks the thread for a period of time.
... (remainder of function)
}

*/
41 changes: 41 additions & 0 deletions orcareduce/chaos/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package chaos

import (
"math/rand"

"github.com/mkuchenbecker/orcareduce/orcareduce"
)

type randomStaticErrors struct {
err error
errorRate float64
}

func (cfg *randomStaticErrors) Error() error {
if rand.Float64() <= cfg.errorRate {
return cfg.err
}
return nil
}

func NewRandomStaticErrorInjector(err error, rate float64) orcareduce.ErrorInjector {
return &randomStaticErrors{err: err, errorRate: rate}
}

type metaErrors struct {
errors []orcareduce.ErrorInjector
}

func (cfg *metaErrors) Error() error {
for _, injector := range cfg.errors {
err := injector.Error()
if err != nil {
return err
}
}
return nil
}

func NewMetaErrorInjector(errors []orcareduce.ErrorInjector) orcareduce.ErrorInjector {
return &metaErrors{errors: errors}
}
43 changes: 43 additions & 0 deletions orcareduce/chaos/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package chaos

import (
"fmt"
"math/rand"
"testing"

"github.com/golang/mock/gomock"
"github.com/mkuchenbecker/orcareduce/orcareduce"
"github.com/mkuchenbecker/orcareduce/orcareduce/mock"
"github.com/stretchr/testify/assert"
)

func TestRandomStaticErrors_100Percent(t *testing.T) {
errGeneric := fmt.Errorf("error")
errorInjector := NewRandomStaticErrorInjector(errGeneric, 1)
for i := 0; i < 100; i++ {
assert.Equal(t, errGeneric, errorInjector.Error())
}
}

func TestRandomStaticErrors_ZeroPercent(t *testing.T) {
errGeneric := fmt.Errorf("error")
errorInjector := NewRandomStaticErrorInjector(errGeneric, 0)
for i := 0; i < 100; i++ {
assert.Equal(t, nil, errorInjector.Error())
}
}

func TestMetaErrors(t *testing.T) {
errGeneric := fmt.Errorf("error")
t.Parallel()
rand.Seed(1)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockError0 := mock.NewMockErrorInjector(mockCtrl)
mockError0.EXPECT().Error().Return(nil).Times(1)
mockError1 := mock.NewMockErrorInjector(mockCtrl)
mockError1.EXPECT().Error().Return(errGeneric).Times(1)

meta := NewMetaErrorInjector([]orcareduce.ErrorInjector{mockError0, mockError1})
assert.Equal(t, errGeneric, meta.Error())
}
72 changes: 72 additions & 0 deletions orcareduce/chaos/latency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package chaos

import (
"math/rand"
"time"

"github.com/mkuchenbecker/orcareduce/orcareduce"
)

type sleepFunction func(time.Duration)

type staticLatency struct {
latency time.Duration
sleep sleepFunction
}

func (cfg *staticLatency) Latency() {
time.Sleep(cfg.latency)
}

func NewStaticLatency(latency time.Duration) orcareduce.LatencyInjector {
return &staticLatency{
sleep: time.Sleep,
latency: latency,
}
}

type dynamicLatency struct {
maxLatency time.Duration
sleep sleepFunction
}

func (cfg *dynamicLatency) Latency() {
val := rand.Int63n(int64(cfg.maxLatency))
cfg.sleep(time.Duration(val))
}

func NewDynamicLatency(maxLatency time.Duration) orcareduce.LatencyInjector {
return &dynamicLatency{
sleep: time.Sleep,
maxLatency: maxLatency,
}
}

type randomLatency struct {
latency orcareduce.LatencyInjector
percent float64
}

func (cfg *randomLatency) Latency() {
if rand.Float64() < cfg.percent {
cfg.latency.Latency()
}
}

func NewRandomLatency(latency orcareduce.LatencyInjector, percent float64) orcareduce.LatencyInjector {
return &randomLatency{latency: latency, percent: percent}
}

type metaLatency struct {
latency []orcareduce.LatencyInjector
}

func (this *metaLatency) Latency() {
for _, latency := range this.latency {
latency.Latency()
}
}

func NewMetaLatency(latency []orcareduce.LatencyInjector) orcareduce.LatencyInjector {
return &metaLatency{latency: latency}
}
66 changes: 66 additions & 0 deletions orcareduce/chaos/latency_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package chaos

import (
"math/rand"
"testing"
"time"

"github.com/golang/mock/gomock"
"github.com/mkuchenbecker/orcareduce/orcareduce"
"github.com/mkuchenbecker/orcareduce/orcareduce/mock"
"github.com/stretchr/testify/assert"
)

func TestStaticLatency(t *testing.T) {
t.Parallel()
latency := 100 * time.Millisecond
static := NewStaticLatency(latency).(*staticLatency)

static.sleep = func(duration time.Duration) {
assert.Equal(t, latency, duration)
}

static.Latency()
}

func TestDynamicLatency(t *testing.T) {
t.Parallel()
rand.Seed(1)
maxLatency := 100 * time.Millisecond
dynamic := NewDynamicLatency(maxLatency).(*dynamicLatency)

dynamic.sleep = func(duration time.Duration) {
assert.True(t, maxLatency >= duration)
}

dynamic.Latency()
}

func TestRandomLatency(t *testing.T) {
t.Parallel()
rand.Seed(1)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockLatency := mock.NewMockLatencyInjector(mockCtrl)

random := NewRandomLatency(mockLatency, 1)
mockLatency.EXPECT().Latency().Times(1)
random.Latency()

random = NewRandomLatency(mockLatency, 0)
random.Latency()
}

func TestMetaLatency(t *testing.T) {
t.Parallel()
rand.Seed(1)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockLatency0 := mock.NewMockLatencyInjector(mockCtrl)
mockLatency0.EXPECT().Latency().Times(1)
mockLatency1 := mock.NewMockLatencyInjector(mockCtrl)
mockLatency1.EXPECT().Latency().Times(1)

meta := NewMetaLatency([]orcareduce.LatencyInjector{mockLatency0, mockLatency1})
meta.Latency()
}
41 changes: 41 additions & 0 deletions orcareduce/chaos/meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package chaos

import (
"fmt"
"time"

"github.com/mkuchenbecker/orcareduce/orcareduce"
)

type metaInjector struct {
latency orcareduce.LatencyInjector
errors orcareduce.ErrorInjector
}

func (cfg *metaInjector) Latency() {
cfg.latency.Latency()
}

func (cfg *metaInjector) Error() error {
return cfg.errors.Error()
}

func NewDefault() orcareduce.Injector {
return &metaInjector{
latency: &metaLatency{
latency: []orcareduce.LatencyInjector{
NewStaticLatency(20 * time.Millisecond),
NewDynamicLatency(20 * time.Millisecond),
NewRandomLatency(NewDynamicLatency(100*time.Millisecond), 0.1),
NewRandomLatency(NewDynamicLatency(1000*time.Millisecond), 0.01),
NewRandomLatency(NewDynamicLatency(5000*time.Millisecond), 0.001),
NewRandomLatency(NewDynamicLatency(30*time.Second), 0.0001),
},
},
errors: &metaErrors{
errors: []orcareduce.ErrorInjector{
NewRandomStaticErrorInjector(fmt.Errorf("[chaos] encountered an error"), 0.1),
},
},
}
}
26 changes: 26 additions & 0 deletions orcareduce/chaos/meta_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package chaos

import (
"math/rand"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestDefaultChaos(t *testing.T) {
rand.Seed(5) // Static seed so the test is deterministic.
chaos := NewDefault()

now := time.Now()
chaos.Latency()
assert.True(t, time.Since(now) >= time.Millisecond*20)

errCount := 0
for i := 0; i < 1000; i++ {
if err := chaos.Error(); err != nil {
errCount++
}
}
assert.Equal(t, 105, errCount)
}
Empty file added orcareduce/database.go
Empty file.
Loading