summaryrefslogtreecommitdiff
path: root/trunks.go
diff options
context:
space:
mode:
Diffstat (limited to 'trunks.go')
-rw-r--r--trunks.go163
1 files changed, 149 insertions, 14 deletions
diff --git a/trunks.go b/trunks.go
index a610e5e..767d5ee 100644
--- a/trunks.go
+++ b/trunks.go
@@ -5,11 +5,9 @@
package trunks
import (
- "encoding/json"
"fmt"
"net/http"
"os"
- "sort"
"strings"
"time"
@@ -22,16 +20,19 @@ import (
"github.com/shuLhan/share/lib/memfs"
"github.com/shuLhan/share/lib/mlog"
"github.com/shuLhan/share/lib/os/exec"
+ "github.com/shuLhan/share/lib/websocket"
)
const (
DefaultAttackDuration = 10 * time.Second
DefaultAttackRatePerSecond = 500
DefaultAttackTimeout = 30 * time.Second
- DefaultListenAddress = "127.0.0.1:8217"
DefaultMaxAttackDuration = 30 * time.Second
DefaultMaxAttackRate = 3000
+ DefaultListenAddress = "127.0.0.1:8217"
+ DefaultWebSocketListenAddress = "127.0.0.1:8218"
+
// Setting this environment variable will enable trunks development
// mode.
envDevelopment = "TRUNKS_DEV"
@@ -48,11 +49,13 @@ const (
//
type Trunks struct {
Env *Environment
- Httpd *libhttp.Server
+ Httpd *libhttp.Server // The HTTP server.
+ Wsd *websocket.Server // The WebSocket server.
targets []*Target
attackq chan *RunRequest
cancelq chan bool
+ errq chan error
}
//
@@ -73,12 +76,17 @@ func New(env *Environment) (trunks *Trunks, err error) {
Env: env,
attackq: make(chan *RunRequest, 1),
cancelq: make(chan bool, 1),
+ errq: make(chan error, 1),
}
err = trunks.initHttpServer(isDevelopment)
if err != nil {
return nil, fmt.Errorf("%s: %w", logp, err)
}
+ err = trunks.initWebSocketServer()
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", logp, err)
+ }
if isDevelopment {
go watchDocs()
@@ -88,6 +96,67 @@ func New(env *Environment) (trunks *Trunks, err error) {
return trunks, nil
}
+//
+// AttackHttp start attacking the HTTP target defined in req.
+//
+func (trunks *Trunks) AttackHttp(req *RunRequest) (err error) {
+ logp := "AttackHttp"
+
+ if trunks.Env.isAttackRunning() {
+ return errAttackConflict(trunks.Env.getRunningAttack())
+ }
+
+ origTarget := trunks.getTargetByID(req.Target.ID)
+ if origTarget == nil {
+ return errInvalidTarget(req.Target.ID)
+ }
+
+ origHttpTarget := origTarget.getHttpTargetByID(req.HttpTarget.ID)
+ if origTarget == nil {
+ return errInvalidHttpTarget(req.HttpTarget.ID)
+ }
+ if !origHttpTarget.AllowAttack {
+ return errAttackNotAllowed()
+ }
+
+ req = generateRunRequest(trunks.Env, req, origTarget, origHttpTarget)
+
+ req.result, err = newAttackResult(trunks.Env, req)
+ if err != nil {
+ return fmt.Errorf("%s: %w", logp, err)
+ }
+
+ trunks.attackq <- req
+
+ msg := fmt.Sprintf("Attacking %s%s with %d RPS for %s seconds",
+ req.Target.BaseUrl, req.HttpTarget.Path,
+ req.Target.Opts.RatePerSecond, req.Target.Opts.Duration)
+
+ mlog.Outf("%s: %s\n", logp, msg)
+
+ return nil
+}
+
+//
+// AttackHttpCancel cancel any running attack.
+// It will return an error if no attack is running.
+//
+func (trunks *Trunks) AttackHttpCancel() (rr *RunRequest, err error) {
+ rr = trunks.Env.getRunningAttack()
+ if rr == nil {
+ e := &liberrors.E{
+ Code: http.StatusNotFound,
+ Message: "No attack is currently running.",
+ Name: "ERR_ATTACK_CANCEL_NOT_FOUND",
+ }
+ return nil, e
+ }
+
+ trunks.cancelq <- true
+
+ return rr, nil
+}
+
func (trunks *Trunks) RegisterTarget(target *Target) (err error) {
if target == nil {
return
@@ -104,6 +173,35 @@ func (trunks *Trunks) RegisterTarget(target *Target) (err error) {
}
//
+// RunHttp send the HTTP request to the HTTP target defined in RunRequest with
+// optional Headers and Parameters.
+//
+func (trunks *Trunks) RunHttp(req *RunRequest) (res *RunResponse, err error) {
+ origTarget := trunks.getTargetByID(req.Target.ID)
+ if origTarget == nil {
+ return nil, errInvalidTarget(req.Target.ID)
+ }
+
+ origHttpTarget := origTarget.getHttpTargetByID(req.HttpTarget.ID)
+ if origHttpTarget == nil {
+ return nil, errInvalidHttpTarget(req.HttpTarget.ID)
+ }
+
+ if origHttpTarget.Run == nil {
+ req.Target.BaseUrl = origTarget.BaseUrl
+ req.Target.Name = origTarget.Name
+ res, err = trunks.runHttpTarget(req)
+ } else {
+ req := generateRunRequest(trunks.Env, req, origTarget, origHttpTarget)
+ res, err = req.HttpTarget.Run(req)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return res, nil
+}
+
+//
// Start the Trunks HTTP server that provide user interface for running and
// load testing registered Targets.
//
@@ -115,24 +213,62 @@ func (trunks *Trunks) Start() (err error) {
go trunks.workerAttackQueue()
mlog.Outf("trunks: starting HTTP server at %s\n", trunks.Env.ListenAddress)
- return trunks.Httpd.Start()
+ go func() {
+ err := trunks.Httpd.Start()
+ if err != nil {
+ trunks.errq <- err
+ }
+ }()
+ go func() {
+ err := trunks.Wsd.Start()
+ if err != nil {
+ trunks.errq <- err
+ }
+ }()
+
+ err = <-trunks.errq
+
+ return err
}
//
// Stop the Trunks HTTP server.
//
func (trunks *Trunks) Stop() {
+ logp := "trunks.Stop"
mlog.Outf("=== Stopping the Trunks service ...\n")
err := trunks.Httpd.Stop(0)
if err != nil {
- mlog.Errf("!!! Stop: %s\n", err)
+ mlog.Errf("!!! %s: %s", logp, err)
}
+ trunks.Wsd.Stop()
+
if trunks.isLoadTesting() {
trunks.cancelq <- true
<-trunks.cancelq
}
+
+ trunks.errq <- nil
+}
+
+func (trunks *Trunks) addHttpAttackResult(rr *RunRequest) (ok bool) {
+ for _, target := range trunks.targets {
+ if target.ID != rr.Target.ID {
+ continue
+ }
+ for _, httpTarget := range target.HttpTargets {
+ if httpTarget.ID != rr.HttpTarget.ID {
+ continue
+ }
+
+ httpTarget.Results = append(httpTarget.Results, rr.result)
+ httpTarget.sortResults()
+ return true
+ }
+ }
+ return false
}
func (trunks *Trunks) isLoadTesting() (b bool) {
@@ -295,9 +431,7 @@ func (trunks *Trunks) scanResultsDir() {
for _, target := range trunks.targets {
for _, httpTarget := range target.HttpTargets {
- sort.Slice(httpTarget.Results, func(x, y int) bool {
- return httpTarget.Results[x].Name > httpTarget.Results[y].Name
- })
+ httpTarget.sortResults()
}
}
}
@@ -356,11 +490,7 @@ func (trunks *Trunks) workerAttackQueue() {
mlog.Errf("%s %s: %s\n", logp, rr.result.Name, err)
}
- rr.HttpTarget.Results = append(rr.HttpTarget.Results, rr.result)
-
- sort.Slice(rr.HttpTarget.Results, func(x, y int) bool {
- return rr.HttpTarget.Results[x].Name > rr.HttpTarget.Results[y].Name
- })
+ trunks.addHttpAttackResult(rr)
mlog.Outf("%s: %s finished.\n", logp, rr.result.Name)
}
@@ -408,9 +538,14 @@ func watchWww() {
},
Excludes: []string{
`\.*.d.ts$`,
+ `\.git`,
+ `\.wui.bak`,
+ `\.wui.local`,
},
},
Callback: func(ns *io.NodeState) {
+ mlog.Outf("--- %s: %s: %d\n", logp, ns.Node.SysPath, ns.State)
+
for _, cmd := range commands {
mlog.Outf("%s: %s\n", logp, cmd)
err := exec.Run(cmd, nil, nil)