diff options
| author | Shulhan <ms@kilabit.info> | 2021-03-21 23:24:46 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2021-03-21 23:24:46 +0700 |
| commit | 7fed1f75a32c161ba0d296dbb7b6dc14e831336e (patch) | |
| tree | a2d4f65b11a20aefaad4738f3a02a0b908ccb1b8 | |
| parent | 360bf84d2af244c7926c2aa7c1f23d0c280b8238 (diff) | |
| download | gorankusu-7fed1f75a32c161ba0d296dbb7b6dc14e831336e.tar.xz | |
all: implement interface and API to cancel running attack
On the top of the page, it will display currently running attack with
target name and HTTP target name. User can cancel the attack by
clicking "Cancel" button on the right.
| -rw-r--r-- | _www/index.css | 17 | ||||
| -rw-r--r-- | _www/index.html | 6 | ||||
| -rw-r--r-- | _www/index.js | 82 | ||||
| -rw-r--r-- | environment.go | 8 | ||||
| -rw-r--r-- | errors.go | 11 | ||||
| -rw-r--r-- | example/example.go | 4 | ||||
| -rw-r--r-- | trunks.go | 49 |
7 files changed, 142 insertions, 35 deletions
diff --git a/_www/index.css b/_www/index.css index f80f449..ce0eb27 100644 --- a/_www/index.css +++ b/_www/index.css @@ -30,6 +30,7 @@ body { padding: 1em; position: fixed; width: 14em; + border-right: 1px solid lightgrey; } .nav .nav-item { cursor: pointer; @@ -48,6 +49,10 @@ body { height: calc(100% - 4em); overflow: auto; } +.main .mainState { + border: 1px solid lightgrey; + padding: 1em; +} .input { margin-bottom: 1em; } @@ -62,12 +67,22 @@ body { width: calc(100% - 18em); } .mono { - background-color: cornsilk; + background-color: lightgrey; font-family: monospace; padding: 1em; overflow: auto; } +.HttpTarget { + margin-top: 2em; +} + +.HttpTarget h3 { + border-left: 10px solid gold; + margin-left: 0; + padding: 0.5em; +} + .results > .result-name { margin-bottom: 1em; } diff --git a/_www/index.html b/_www/index.html index 56754af..f753ed4 100644 --- a/_www/index.html +++ b/_www/index.html @@ -13,7 +13,7 @@ <h1>Trunks</h1> <div class="nav-item"> - <h3 onclick="environment()">Environment</h3> + <h3 onclick="environmentRender()">Environment</h3> </div> <div id="nav-content"></div> @@ -24,6 +24,10 @@ </div> <div class="main"> + <div class="mainState"> + Attack running: + <span id="stateAttack"> - </span> + </div> <div id="main-content"></div> </div> diff --git a/_www/index.js b/_www/index.js index 044bd57..e8a5e37 100644 --- a/_www/index.js +++ b/_www/index.js @@ -20,8 +20,15 @@ let _requestTypes = { } async function main() { + await environmentGet() + let fres = await fetch("/_trunks/api/targets") let res = await fres.json() + if (res.code != 200) { + notifError(res.message) + return + } + let targets = res.data let w = "" @@ -39,15 +46,26 @@ async function main() { document.getElementById("nav-content").innerHTML = w } -async function environment() { - let el = document.getElementById("main-content") - +async function environmentGet() { let fres = await fetch("/_trunks/api/environment") let res = await fres.json() + if (res.code != 200) { + notifError(res.message) + return + } _env = res.data - el.innerHTML = ` + if (_env.AttackRunning) { + updateStateAttack( + _env.AttackRunning.Target, + _env.AttackRunning.HttpTarget, + ) + } +} + +async function environmentRender() { + document.getElementById("main-content").innerHTML = ` <h2> Environment </h2> <div class="environment"> <div class="input"> @@ -250,6 +268,10 @@ async function run(targetID, httpTargetID) { }) let res = await fres.json() + if (res.code != 200) { + notifError(res.message) + return + } let elResponse = document.getElementById(httpTargetID + "_response") elResponse.innerHTML = JSON.stringify(res, null, 2) @@ -280,6 +302,30 @@ async function attack(targetID, httpTargetID) { }) let res = await fres.json() + if (res.code != 200) { + notifError(res.message) + return + } + + updateStateAttack(target, httpTarget) + + notif(res.message) +} + +async function attackCancel() { + let fres = await fetch("/_trunks/api/target/attack", { + method: "DELETE", + }) + + let res = await fres.json() + if (res.code != 200) { + notifError(res.message) + return + } + + updateStateAttack(null, null) + + notif(res.message) } async function attackResultDelete(name) { @@ -325,6 +371,11 @@ async function attackResultGet(button, name) { let url = "/_trunks/api/target/attack/result?name=" + name let fres = await fetch(url) let res = await fres.json() + if (res.code != 200) { + notifError(res.message) + return + } + let result = res.data el.innerHTML = ` @@ -363,22 +414,37 @@ function onChangeHttpParam(targetID, httpTargetID, key, val) { function notif(msg) { let root = document.getElementById("notif") - let item = document.createElement("div"); + let item = document.createElement("div") item.innerHTML = msg root.appendChild(item) - setTimeout(function() { + setTimeout(function () { root.removeChild(item) }, 5000) } function notifError(msg) { let root = document.getElementById("notif-error") - let item = document.createElement("div"); + let item = document.createElement("div") item.innerHTML = msg root.appendChild(item) - setTimeout(function() { + setTimeout(function () { root.removeChild(item) }, 5000) } + +function updateStateAttack(target, httpTarget) { + let el = document.getElementById("stateAttack") + if (httpTarget) { + el.innerHTML = ` + ${target.Name} / ${httpTarget.Name} + + <button onclick="attackCancel('${target.ID}', '${httpTarget.ID}')"> + Cancel + </button> + ` + } else { + el.innerHTML = "-" + } +} diff --git a/environment.go b/environment.go index ced921a..7531148 100644 --- a/environment.go +++ b/environment.go @@ -46,7 +46,7 @@ type Environment struct { // AttackRunning will be set to non-nil if there is a load // testing currently running. - AttackRunning *AttackResult + AttackRunning *RunRequest mtx sync.Mutex } @@ -71,11 +71,11 @@ func (env *Environment) init() (err error) { return nil } -func (env *Environment) getRunningAttack() (ar *AttackResult) { +func (env *Environment) getRunningAttack() (rr *RunRequest) { env.mtx.Lock() - ar = env.AttackRunning + rr = env.AttackRunning env.mtx.Unlock() - return ar + return rr } func (env *Environment) isAttackRunning() (yorn bool) { @@ -12,14 +12,15 @@ import ( libhttp "github.com/shuLhan/share/lib/http" ) -func errAttackConflict(ar *AttackResult) error { +func errAttackConflict(rr *RunRequest) error { res := &libhttp.EndpointResponse{ E: liberrors.E{ - Code: http.StatusConflict, - Message: "another attack is already running", - Name: "ERR_ATTACK_CONFLICT", + Code: http.StatusConflict, + Message: fmt.Sprintf(`Another attack is already running: "%s/%s`, + rr.Target.Name, rr.HttpTarget.Name), + Name: "ERR_ATTACK_CONFLICT", }, - Data: ar, + Data: rr, } return res } diff --git a/example/example.go b/example/example.go index 190b39a..9a91ebb 100644 --- a/example/example.go +++ b/example/example.go @@ -95,8 +95,8 @@ func (ex *Example) registerTargets() (err error) { Name: "Example", Opts: &trunks.AttackOptions{ BaseUrl: fmt.Sprintf("http://%s", ex.trunks.Env.ListenAddress), - Duration: 5 * time.Second, - RatePerSecond: 10, + Duration: 300 * time.Second, + RatePerSecond: 1, }, Vars: map[string]string{ "A": "1", @@ -284,7 +284,7 @@ func (trunks *Trunks) apiTargetAttack(epr *libhttp.EndpointRequest) (resbody []b trunks.attackq <- req - msg := fmt.Sprintf("attacking %s/%s with %d RPS for %d seconds", + msg := fmt.Sprintf("Attacking %s/%s with %d RPS for %s seconds", req.Target.Opts.BaseUrl, req.HttpTarget.Path, req.Target.Opts.RatePerSecond, req.Target.Opts.Duration) @@ -292,13 +292,32 @@ func (trunks *Trunks) apiTargetAttack(epr *libhttp.EndpointRequest) (resbody []b res := libhttp.EndpointResponse{} res.Code = http.StatusOK + res.Name = "OK_ATTACK" res.Message = msg return json.Marshal(res) } func (trunks *Trunks) apiTargetAttackCancel(epr *libhttp.EndpointRequest) (resbody []byte, err error) { - return resbody, nil + res := &libhttp.EndpointResponse{} + + rr := trunks.Env.getRunningAttack() + if rr == nil { + res.Code = http.StatusNotFound + res.Message = "No attack is currently running." + res.Name = "ERR_ATTACK_CANCEL_NOT_FOUND" + return nil, res + } + + trunks.cancelq <- true + + res.Code = http.StatusOK + res.Name = "OK_ATTACK_CANCEL" + res.Message = fmt.Sprintf(`Attack on target "%s / %s" has been canceled`, + rr.Target.Name, rr.HttpTarget.Name) + res.Data = rr + + return json.Marshal(res) } func (trunks *Trunks) apiTargetAttackResultGet(epr *libhttp.EndpointRequest) (resbody []byte, err error) { @@ -495,6 +514,8 @@ func (trunks *Trunks) workerAttackQueue() (err error) { logp := "workerAttackQueue" for rr := range trunks.attackq { + trunks.Env.AttackRunning = rr + rr.HttpTarget.PreAttack(rr) isCancelled := false @@ -534,22 +555,22 @@ func (trunks *Trunks) workerAttackQueue() (err error) { // Inform the caller that the attack has been canceled. trunks.cancelq <- true } + } else { + err := rr.result.finish() + if err != nil { + mlog.Errf("%s %s: %s\n", logp, rr.result.Name, err) + } - return nil - } + rr.HttpTarget.Results = append(rr.HttpTarget.Results, rr.result) - err := rr.result.finish() - if err != nil { - 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 + }) - sort.Slice(rr.HttpTarget.Results, func(x, y int) bool { - return rr.HttpTarget.Results[x].Name > rr.HttpTarget.Results[y].Name - }) + mlog.Outf("%s: %s finished.\n", logp, rr.result.Name) + } - mlog.Outf("%s: %s finished.\n", logp, rr.result.Name) + trunks.Env.AttackRunning = nil } return nil } |
