diff --git a/README.md b/README.md index ef03fec..a1376f2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Faces Demo This is the Faces demo application. It has a single-page web GUI that presents -a grid of cells, each of which _should_ show a smiling face on a green +a grid of cells, each of which _should_ show a grinning face on a light blue background. Spoiler alert: installed exactly as committed to this repo, that isn't what you'll get -- many, many things can go wrong, and will. The point of the demo is let you try to fix things. @@ -16,10 +16,14 @@ In here you will find: - These things are installed in a demo configuration: read and think **carefully** before using this demo as background for a production installation! In particular: - - We use `sed` to force everything to just one replica when installing - Emissary -- **DON'T** do that in production. - - We only configure HTTP, not HTTPS. Again, **DON'T** do this in - production. + + - We deploy Emissary with only one replica of everything, using a + currently-unofficial chart to also skip support for `v1` and `v2` + Emissary CRDs. + + - We only configure HTTP, not HTTPS. + + These are likely both bad ideas for a production installation. - `DEMO.md`, a Markdown file for the resilience demo presented live for a couple of events. The easiest way to use `DEMO.md` is to run it with @@ -53,6 +57,35 @@ In here you will find: - To run the demo as we've given it before, check out [DEMO.md]. The easiest way to use that is to run it with [demosh]. +## Architecture + +The Faces architecture is fairly simple: + +- The `faces-gui` workload, reached on the `/faces/` path, just returns the + HTML and Javascript for the GUI. The GUI is a single-page webapp that + displays a grid of cells: for each cell, the GUI calls the `face` workload. + +- The `face` workload, reached on the `/face/` path, calls the `smiley` + workload to get a smiley face and the `color` workload to get a color. It + then composes the responses together and returns the smiley/color + combination to the GUI for display. + +- The `smiley` workload returns a smiley face. By default, this is a grinning + smiley, U+1F603, but you can set the `SMILEY` environment variable to any + key in the `Smileys` map from `constants.go` to get a different smiley. + +- The `color` workload returns a color. By default, this is a light blue, but + you can set the `COLOR` environment variable to any key in the `Colors` map + from `constants.go` to get a different color, or to any arbitrary hex color + code (e.g. `#ff0000` for bright red). + + The named colors in the `Colors` map are meant to work for normal color + vision and for various kinds of colorblindness, and are taken from the + "Bright" color scheme shown in the "Qualitative // Color Schemes" section of + https://personal.sron.nl/~pault/. For (much) more information, read the + comments in `pkg/faces/constants.go`. Feedback here is welcome, since the + Faces authors have normal color vision... + [Linkerd]: https://linkerd.io [Emissary-ingress]: https://www.getambassador.io/docs/emissary/ [DEMO.md]: DEMO.md diff --git a/assets/html/index.html b/assets/html/index.html index b80af38..710f123 100644 --- a/assets/html/index.html +++ b/assets/html/index.html @@ -25,22 +25,6 @@ font-family: sans-serif; } - .green { - color: rgb(55 117 59); - } - - .red { - color: rgb(125 42 83); /* dark magenta */ - } - - .blue { - color: rgb(151 202 234); /* light blue */ - } - - .grey { - color: grey; - } - .key { font-family: sans-serif; font-size: 32px; @@ -74,7 +58,7 @@ .cell { display: inline-block; - border: 1px solid grey; + border: 2px solid grey; border-radius: 16px; height: 120px; width: 120px; @@ -96,16 +80,16 @@ } .cell-pod-info { - height: 120px; - width: 120px; - border-radius: 16px; + display: inline-block; + position: relative; + text-align: center; } .cell-pod-id { position: absolute; - top: 1px; - left: 1px; - width: 118px; + top: 4px; + left: 4px; + width: 120px; border-radius: 14px 14px 0px 0px; font-family: sans-serif; background: white; @@ -179,24 +163,8 @@ border-right: 2px solid grey; padding-left: .5em; padding-right: .5em; - max-width: 252px !important; - width: 252px !important; - } - - .bg-green { - background-color: rgb(55 117 59); - } - - .bg-red { - background-color: rgb(125 42 83); /* dark magenta */ - } - - .bg-blue { - background-color: rgb(151 202 234); /* light blue */ - } - - .bg-grey { - background-color: grey; + max-width: 258px !important; + width: 258px !important; } .inline { @@ -297,7 +265,7 @@

Faces

logmsg(color, msg) { let now = new Date().toISOString() console.log(`${now} ${msg}`) - // this.logdiv.innerHTML = `${now}: ${msg}
` + this.logdiv.innerHTML + // this.logdiv.innerHTML = `${now}: ${msg}
` + this.logdiv.innerHTML } // success, fail, and info are wrappers around logmsg to avoid @@ -308,7 +276,7 @@

Faces

} fail(msg) { - this.logmsg(Cell.colors.red, msg) + this.logmsg(Cell.colors.purple, msg) } info(msg) { @@ -369,7 +337,7 @@

Faces

} start() { - this.userDiv.innerHTML = `User: ${this.user}` + this.userDiv.innerHTML = `User: ${this.user}` } // stop() { @@ -435,12 +403,12 @@

Faces

if (status == 599) { // Latched error state. smiley = Cell.smilies.neutral - bgColor = Cell.colors.hotpink + bgColor = Cell.colors.yellow bumpCounter = true } else if (status == 429) { smiley = Cell.smilies.sleeping - bgColor = Cell.colors.pink + bgColor = Cell.colors.red bumpCounter = true } else if (status == 200) { @@ -449,7 +417,7 @@

Faces

} if (bgColor == "504") { - bgColor = Cell.colors.pink + bgColor = Cell.colors.red } this.countDiv.innerHTML = 0 @@ -457,9 +425,9 @@

Faces

} else { // This should probably never happen. - borderColor = Cell.colors.red + borderColor = Cell.colors.purple smiley = Cell.smilies.upset - bgColor = Cell.colors.red + bgColor = Cell.colors.purple bumpCounter = true } @@ -510,7 +478,7 @@

Faces

// New pod! let podDiv = document.createElement("div") podDiv.id = `cell-pod-${shortName}` - podDiv.className = "cell" + podDiv.className = "cell-pod-info" let podIDDiv = document.createElement("div") podIDDiv.id = `cell-pod-id-${shortName}` @@ -520,7 +488,7 @@

Faces

let podInfoDiv = document.createElement("div") podInfoDiv.id = `cell-pod-info-${shortName}` - podInfoDiv.className = "cell-pod-info" + podInfoDiv.className = "cell" podDiv.appendChild(podInfoDiv) let podSmileySpan = document.createElement("span") @@ -563,23 +531,26 @@

Faces

"neutral": "😐", "screaming": "😱", "sleeping": "😴", - "smiling": "😃", + "grinning": "😃", "thinking": "🤔", "tongue": "😛", "upset": "😬", "yay": "🎉", }; + // There are many many notes about these colors in pkg/faces/constants.go. + // Go read that: the short version is that colorblindness is a thing, so + // don't muck with these too much. static colors = { - "grey": "grey", - "purple": "rgb(48 34 130)", - "green": "rgb(55 117 59)", - "cyan": "rgb(55 117 59)", // too similar to pink and grey, avoid - "blue": "rgb(151 202 234)", // light blue - "yellow": "rgb(218 204 130)", - "pink": "rgb(191 108 120)", - "hotpink": "rgb(158 75 149)", - "red": "rgb(125 42 83)", // dark magenta + "grey": "#BBBBBB", + "black": "#000000", + "white": "#FFFFFF", + "darkblue": "#4477AA", + "blue": "#66CCEE", + "green": "#228833", + "yellow": "#CCBB44", + "red": "#EE6677", + "purple": "#AA3377", } constructor(logger, sw, podSet, fetchURL, enclosingDiv, row, col) { @@ -596,7 +567,7 @@

Faces

let cellDiv = document.createElement("div") cellDiv.id = `cell-${row}-${col}` cellDiv.className = "cell" - cellDiv.style.background = "grey" + cellDiv.style.background = Cell.colors.grey cellDiv.style.opacity = 0.5 let smileySpan = document.createElement("span") @@ -672,7 +643,10 @@

Faces

// ...then figure out what we got. let { curStatus, anyTimeouts, - smiley, bgColor, borderColor } = this.parseResults(xhr); + smiley, bgColor, borderColor, errors } = this.parseResults(xhr); + + // let msg = `[${xhrName}] (${latency}ms): ${smiley} ${bgColor} ${borderColor} -- ${errors}` + // this.success(msg); // Update the pod, if we can... let pod = xhr.getResponseHeader("x-faces-pod"); @@ -721,10 +695,6 @@

Faces

$(`cell-count-${this.row}-${this.col}`).innerHTML = "" } - // FINALLY: show 'em what we got. - // let msg = `[${xhrName}] XHR result (${latency}ms): ${errors} -- ${text}` - // this.success(msg); - if ((smiley != undefined) || (bgColor != undefined)) { $(`cell-${this.row}-${this.col}`).style.opacity = 0.0 @@ -767,7 +737,7 @@

Faces

setTimeout(() => { $(`smiley-${this.row}-${this.col}`).innerHTML = Cell.smilies.confused $(`cell-${this.row}-${this.col}`).style.opacity = 1.0 - $(`cell-${this.row}-${this.col}`).style.background = Cell.colors.red + $(`cell-${this.row}-${this.col}`).style.background = Cell.colors.purple $(`cell-${this.row}-${this.col}`).style.borderColor = "grey" }, 50) @@ -818,9 +788,9 @@

Faces

smiley = obj.smiley; bgColor = obj.color; - if (obj.errors != undefined) { + if ((obj.errors != undefined) && (obj.errors.length > 0)) { errors = obj.errors.join(","); - borderColor = Cell.colors.red + borderColor = Cell.colors.purple } else { errors = "success!"; @@ -836,9 +806,9 @@

Faces

catch (e) { // Whoops, something went wrong. If it's a SyntaxError, that // probably means we got bad JSON. Otherwise, it's... who knows? - borderColor = Cell.colors.red + borderColor = Cell.colors.purple smiley = Cell.smilies.confused; - bgColor = Cell.colors.red; + bgColor = Cell.colors.purple; if (e instanceof SyntaxError) { errors = "parse error"; @@ -860,16 +830,16 @@

Faces

} else if (curStatus == 599) { // This is our latched-error status. Show it as a neutral face - // on a hot pink background. + // on a yellow background. smiley = Cell.smilies.neutral; - bgColor = Cell.colors.hotpink; + bgColor = Cell.colors.yellow; } else if (Math.floor(curStatus / 100) == 5) { // Some other 5yz, so an unknown kind of server error. (In // practice, this is probably a 503 because there's some kind // of connectivity error, but whatever, we don't care. smiley = Cell.smilies.confused; - bgColor = Cell.colors.red; + bgColor = Cell.colors.purple; errors = "server error"; } @@ -881,24 +851,37 @@

Faces

bgColor = Cell.colors.purple } - return { curStatus, anyTimeouts, smiley, bgColor, borderColor }; + return { curStatus, anyTimeouts, smiley, bgColor, borderColor, errors }; } } class Key { constructor(keyDiv) { let keyEntries = [ - [ "smiling", Cell.colors.blue, Cell.colors.grey, "24px", "Success!" ], - [ "confused", Cell.colors.red, Cell.colors.grey, "", "Face service error" ], - [ "sleeping", Cell.colors.pink, Cell.colors.grey, "", "Timeout" ], - [ "kaboom", Cell.colors.red, Cell.colors.grey, "24px", "Service overwhelmed" ], - [ "smiling", Cell.colors.grey, "transparent", "", "Color service error" ], - [ "", Cell.colors.blue, Cell.colors.red, "24px", "Smiley service error" ], - [ "-", "-", "-", "", "Slow service" ] + [ "Success!", + "grinning", Cell.colors.blue, Cell.colors.grey, "24px" ], + + [ "Face service error", + "confused", Cell.colors.purple, Cell.colors.grey, "" ], + + [ "Timeout", + "sleeping", Cell.colors.red, Cell.colors.grey, "" ], + + [ "Service overwhelmed", + "kaboom", Cell.colors.yellow, Cell.colors.purple, "24px" ], + + [ "Color service error", + "grinning", Cell.colors.grey, Cell.colors.purple, "" ], + + [ "Smiley service error", + "", Cell.colors.blue, Cell.colors.purple, "24px" ], + + [ "Slow service", + "-", "-", "-", "" ] ] for (let i = 0; i < keyEntries.length; i++) { - let [ smileyName, bgColor, borderColor, margin, text ] = keyEntries[i] + let [ text, smileyName, bgColor, borderColor, margin ] = keyEntries[i] if (smileyName != "-") { let smiley = "" @@ -998,11 +981,11 @@

Faces

if (cellCount > 4) { let smiley = Cell.smilies.sleeping - let bgColor = Cell.colors.pink + let bgColor = Cell.colors.red if (cell.lastStatus == 599) { - smiley = Cell.smilies.screaming - bgColor = Cell.colors.hotpink + smiley = Cell.smilies.neutral + bgColor = Cell.colors.yellow } $(`smiley-${cell.row}-${cell.col}`).innerHTML = smiley @@ -1044,7 +1027,7 @@

Faces

mark(shape, fgColor, bgColor) { this.markerdiv.innerHTML += ` -
+
@@ -1114,7 +1097,7 @@

Faces

else if (xhr.status != 200) { text = `Unknown status ${xhr.status} after ${latency}ms` shape = "19.000,49.000 31.000,49.000 31.000,1.000 19.000,1.000" - fgColor = Cell.colors.red + fgColor = Cell.colors.purple } else { // Parse JSON! @@ -1130,12 +1113,12 @@

Faces

if (e instanceof SyntaxError) { text = `Could not parse QotM: ${e.message}\n${xhr.responseText}` shape = "19.000,49.000 31.000,49.000 31.000,1.000 19.000,1.000" - fgColor = Cell.colors.red + fgColor = Cell.colors.purple } else { text = `Missing QotM? ${e.message}` shape = "19.000,49.000 31.000,49.000 31.000,1.000 19.000,1.000" - fgColor = Cell.colors.red + fgColor = Cell.colors.purple } } } @@ -1160,7 +1143,7 @@

Faces

// this.success(msg); let nowISO = now.toISOString() - this.xhrdiv.innerHTML = `

${nowISO}: ${msg}

` + this.xhrdiv.innerHTML = `

${nowISO}: ${msg}

` this.markers.mark(shape, fgColor, bgColor) }) @@ -1178,7 +1161,7 @@

Faces

// this.fail(msg); let nowISO = now.toISOString() - this.xhrdiv.innerHTML = `

${nowISO}: Failed!

` + this.xhrdiv.innerHTML = `

${nowISO}: Failed!

` // FIXME: this looks like the wrong arguments for calling this.markers.mark this.markers.mark("red") diff --git a/assets/logo-128.png b/assets/logo-128.png index 70f696e..46445f8 100644 Binary files a/assets/logo-128.png and b/assets/logo-128.png differ diff --git a/faces-chart/Chart.yaml b/faces-chart/Chart.yaml index 11a64a9..e6eeadf 100644 --- a/faces-chart/Chart.yaml +++ b/faces-chart/Chart.yaml @@ -3,8 +3,8 @@ apiVersion: "v2" appVersion: %VERSION% description: | This is the Faces demo application. It has a single-page web GUI that - presents a grid of cells, each of which _should_ show a smiling face on - a green background. Spoiler alert: installed exactly as committed to this + presents a grid of cells, each of which _should_ show a grinning face on a + light blue background. Spoiler alert: installed exactly as committed to this repo, that isn't what you'll get -- many, many things can go wrong, and will. The point of the demo is let you try to fix things. type: application diff --git a/faces-chart/templates/color.yaml b/faces-chart/templates/color.yaml index 1b6ae5c..a91d436 100644 --- a/faces-chart/templates/color.yaml +++ b/faces-chart/templates/color.yaml @@ -37,8 +37,10 @@ spec: env: - name: FACES_SERVICE value: "color" + {{- if .Values.color.color }} - name: COLOR value: {{ .Values.color.color }} + {{- end -}} {{- include "partials.color-errorFraction" . }} {{- include "partials.color-delayBuckets" . }} resources: diff --git a/faces-chart/templates/color2.yaml b/faces-chart/templates/color2.yaml index f7a76af..95c83ea 100644 --- a/faces-chart/templates/color2.yaml +++ b/faces-chart/templates/color2.yaml @@ -38,8 +38,10 @@ spec: env: - name: FACES_SERVICE value: "color" + {{- if .Values.color2.color }} - name: COLOR value: {{ .Values.color2.color }} + {{- end -}} {{- include "partials.color2-errorFraction" . }} {{- include "partials.color2-delayBuckets" . }} resources: diff --git a/faces-chart/templates/smiley.yaml b/faces-chart/templates/smiley.yaml index 37ba530..ae5b66e 100644 --- a/faces-chart/templates/smiley.yaml +++ b/faces-chart/templates/smiley.yaml @@ -37,8 +37,10 @@ spec: env: - name: FACES_SERVICE value: "smiley" + {{- if .Values.smiley.smiley }} - name: SMILEY value: {{ .Values.smiley.smiley }} + {{- end -}} {{- include "partials.smiley-errorFraction" . }} {{- include "partials.smiley-delayBuckets" . }} resources: diff --git a/faces-chart/templates/smiley2.yaml b/faces-chart/templates/smiley2.yaml index 771127d..f61477c 100644 --- a/faces-chart/templates/smiley2.yaml +++ b/faces-chart/templates/smiley2.yaml @@ -38,8 +38,10 @@ spec: env: - name: FACES_SERVICE value: "smiley" + {{- if .Values.smiley2.smiley }} - name: SMILEY value: {{ .Values.smiley2.smiley }} + {{- end -}} {{- include "partials.smiley2-errorFraction" . }} {{- include "partials.smiley2-delayBuckets" . }} resources: diff --git a/faces-chart/values.yaml b/faces-chart/values.yaml index c5c2ca7..feeeda0 100644 --- a/faces-chart/values.yaml +++ b/faces-chart/values.yaml @@ -39,7 +39,7 @@ smiley: imagePullPolicy: "" # If not set, uses backend.imagePullPolicy errorFraction: "" # If not set, uses backend.errorFraction delayBuckets: "" # If not set, uses backend.delayBuckets - smiley: "Smiling" # Override if desired + smiley: "" # Override if desired smiley2: enabled: False # If set to True, enables the second smiley workload @@ -58,7 +58,7 @@ color: imagePullPolicy: "" # If not set, uses backend.imagePullPolicy errorFraction: "" # If not set, uses backend.errorFraction delayBuckets: "" # If not set, uses backend.delayBuckets - color: "rgb(151 202 234)" # Override if desired, defaults to colorblind-friendly light blue from the Tol palette + color: "" # Override if desired, defaults to colorblind-friendly light blue from the Tol palette color2: enabled: False # If set to True, enables the second color workload @@ -68,4 +68,4 @@ color2: imagePullPolicy: "" # If not set, uses backend.imagePullPolicy errorFraction: "" # If not set, uses backend.errorFraction delayBuckets: "" # If not set, uses backend.delayBuckets - color: "rgb(55 117 59)" # Override if desired, defaults to colorblind-friendly green from the Tol palette + color: "green" # Override if desired, defaults to colorblind-friendly green from the Tol palette diff --git a/pkg/faces/colorserver.go b/pkg/faces/colorserver.go index 6b7f613..d59806c 100644 --- a/pkg/faces/colorserver.go +++ b/pkg/faces/colorserver.go @@ -48,9 +48,10 @@ func NewColorServer(serverName string) *ColorServer { func (srv *ColorServer) SetupFromEnvironment() { srv.BaseServer.SetupFromEnvironment() - srv.color = utils.StringFromEnv("COLOR", "green") + colorName := utils.StringFromEnv("COLOR", "blue") + srv.color = Colors.Lookup(colorName) - fmt.Printf("%s %s: color %s\n", time.Now().Format(time.RFC3339), srv.Name, srv.color) + fmt.Printf("%s %s: color %s => %s\n", time.Now().Format(time.RFC3339), srv.Name, colorName, srv.color) } func (srv *ColorServer) colorGetHandler(r *http.Request, rstat *BaseRequestStatus) *BaseServerResponse { @@ -61,7 +62,7 @@ func (srv *ColorServer) colorGetHandler(r *http.Request, rstat *BaseRequestStatu return &BaseServerResponse{ StatusCode: http.StatusTooManyRequests, Data: map[string]interface{}{ - "color": Defaults["color-ratelimit"], + "color": Colors.Lookup(Defaults["color-ratelimit"]), "rate": fmt.Sprintf("%.1f RPS", srv.CurrentRate()), "errors": []string{errstr}, }, diff --git a/pkg/faces/constants.go b/pkg/faces/constants.go index b55a5b8..a02772f 100644 --- a/pkg/faces/constants.go +++ b/pkg/faces/constants.go @@ -17,42 +17,116 @@ package faces -var Smileys = map[string]string{ - "Smiling": "😃", - "Sleeping": "😴", - "Cursing": "🤬", - "Kaboom": "🤯", - "HeartEyes": "😍", - "Neutral": "😐", - "RollingEyes": "🙄", - "Screaming": "😱", +type SmileyMap struct { + smileys map[string]string } -// colorblind-friendly colors from the Tol palette -var Colors = map[string]string{ - "grey": "grey", - "purple": "rgb(48 34 130)", - "green": "rgb(55 117 59)", - "cyan": "rgb(55 117 59)", // too similar to pink and grey, avoid - "blue": "rgb(151 202 234)", // light blue - "yellow": "rgb(218 204 130)", - "pink": "rgb(191 108 120)", - "hotpink": "rgb(158 75 149)", - "red": "rgb(125 42 83)", // dark magenta +var Smileys = SmileyMap{ + smileys: map[string]string{ + "Grinning": "😃", + "Sleeping": "😴", + "Cursing": "🤬", + "Kaboom": "🤯", + "HeartEyes": "😍", + "Neutral": "😐", + "RollingEyes": "🙄", + "Screaming": "😱", + "Vomiting": "🤮", + }, +} + +// Lookup a smiley by name. If found, return the HTML entity for the smiley +// and true; if not found, return an empty string and false. +func (sm *SmileyMap) Lookup(name string) (string, bool) { + if smiley, ok := sm.smileys[name]; ok { + return smiley, true + } + + return "", false +} + +type Palette struct { + colors map[string]string +} + +// These colors are from the "Bright" color scheme shown in the "Qualitative +// Color Schemes" section of https://personal.sron.nl/~pault/. The notes about +// color pairs to avoid are from using https://davidmathlogic.com/colorblind/ +// and from using the Python colorspacious module to compute distances between +// color pairs. +// +// The color names are from https://personal.sron.nl/~pault _except_ that I'm +// using "darkblue" for 4477AA, and "blue" for 66CCEE, because overall 4477AA +// turns out to cause more trouble for colorblind folks than 66CCEE. +// +// Specific problematic pairs: +// +// Protanopia/deuteranopia: darkblue/purple, green/red, grey/red, and maybe +// blue/grey (the math says it's a problem, looking at davidmathlogic seems +// like probably not?) +// +// Deuteranopia: yellow/red might be a problem, according to the math +// +// Tritanopia: this is more rare than the others, but darkblue and green are +// almost identical to this crowd, and the yellow/grey pair is troubling too. +// +// So, by default, Faces uses: +// +// blue (66CCEE) for color workload success +// grey (BBBBBB) for color workload error +// purple (AA3377) for when the face workload can't talk to the color +// workload at all +// red (EE6677) for a color timeout and +// yellow (CCBB44) for a latched error state +// +// and, hopefully, that's a decent compromise. + +var Colors = Palette{ + colors: map[string]string{ + // Include grey/black/white because they're sometimes convenient. + "grey": "#BBBBBB", + "black": "#000000", + "white": "#FFFFFF", + + // See lots of notes above. + "darkblue": "#4477AA", + "blue": "#66CCEE", + "green": "#228833", + "yellow": "#CCBB44", + "red": "#EE6677", + "purple": "#AA3377", + }, +} + +func (p *Palette) Lookup(name string) string { + if color, ok := p.colors[name]; ok { + return color + } + + // If the color starts with '#', assume it's a hex color code and + // return it as-is. + + if name[0] == '#' { + return name + } + + // It doesn't look like a hex code and it's not a color code we know, + // so just return yellow as a fallback. + return p.colors["yellow"] } var Defaults = map[string]string{ // Default to grey background, cursing face. - "color": Colors["grey"], - "smiley": Smileys["Cursing"], + "color": "grey", + "smiley": "Cursing", // 504 errors (GatewayTimeout) from the face workload will get handled in // the GUI, but from the color & smiley workloads, they should get - // translated to a pink color and a sleeping face. - "color-504": Colors["pink"], - "smiley-504": Smileys["Sleeping"], + // translated to a red color and a sleeping face. + "color-504": "red", + "smiley-504": "Sleeping", - // Ratelimits are pink with an exploding head. - "color-ratelimit": Colors["pink"], - "smiley-ratelimit": Smileys["Kaboom"], + // Ratelimits are yellow with an exploding head. + "color-ratelimit": "yellow", + "smiley-ratelimit": "Kaboom", } diff --git a/pkg/faces/faceserver.go b/pkg/faces/faceserver.go index 01f833e..8980e5f 100644 --- a/pkg/faces/faceserver.go +++ b/pkg/faces/faceserver.go @@ -181,13 +181,14 @@ func (srv *FaceServer) faceGetHandler(r *http.Request, rstat *BaseRequestStatus) errors := []string{} var smiley string var color string + var smileyOK bool rateStr := fmt.Sprintf("%.1f RPS", srv.CurrentRate()) if rstat.IsRateLimited() { errors = append(errors, rstat.Message()) - smiley = Defaults["smiley-ratelimit"] - color = Defaults["color-ratelimit"] + smiley, smileyOK = Smileys.Lookup(Defaults["smiley-ratelimit"]) + color = Colors.Lookup(Defaults["color-ratelimit"]) } else { user := r.Header.Get("X-Faces-User") @@ -218,21 +219,33 @@ func (srv *FaceServer) faceGetHandler(r *http.Request, rstat *BaseRequestStatus) if smileyResp.statusCode != http.StatusOK { errors = append(errors, fmt.Sprintf("smiley: %s", smileyResp.data)) - smiley = mapStatus("smiley", smileyResp.statusCode) + mapped := mapStatus("smiley", smileyResp.statusCode) + smiley, smileyOK = Smileys.Lookup(mapped) + + if srv.debugEnabled { + fmt.Printf("%s %s: mapped smiley %d to %s (%s, %v)\n", + time.Now().Format(time.RFC3339), srv.Name, smileyResp.statusCode, mapped, smiley, smileyOK) + } } else { smiley = smileyResp.data + smileyOK = true } colorResp := <-colorCh if colorResp.statusCode != http.StatusOK { errors = append(errors, fmt.Sprintf("color: %s", colorResp.data)) - color = mapStatus("color", colorResp.statusCode) + color = Colors.Lookup(mapStatus("color", colorResp.statusCode)) } else { color = colorResp.data } } + if !smileyOK { + // Something bizarre happened with the smiley lookup? + smiley, _ = Smileys.Lookup("Vomiting") + } + end := time.Now() latency := end.Sub(start) diff --git a/pkg/faces/guiserver.go b/pkg/faces/guiserver.go index 6c927cb..e1dfa03 100644 --- a/pkg/faces/guiserver.go +++ b/pkg/faces/guiserver.go @@ -118,7 +118,7 @@ func (srv *GUIServer) guiGetHandler(w http.ResponseWriter, r *http.Request) { rtext = strings.ReplaceAll(rtext, "%%{hide_key}", fmt.Sprintf("%v", srv.hideKey)) rtext = strings.ReplaceAll(rtext, "%%{show_pods}", fmt.Sprintf("%v", srv.showPods)) rtext = strings.ReplaceAll(rtext, "%%{user}", user) - rtext = strings.ReplaceAll(rtext, "%%{user_Agent}", userAgent) + rtext = strings.ReplaceAll(rtext, "%%{user_agent}", userAgent) } } else if strings.HasPrefix(r.URL.Path, "/face/") { // /face/ is a special case: we forward it to the face workload. This is diff --git a/pkg/faces/smileyserver.go b/pkg/faces/smileyserver.go index d8b0aac..3993660 100644 --- a/pkg/faces/smileyserver.go +++ b/pkg/faces/smileyserver.go @@ -48,28 +48,35 @@ func NewSmileyServer(serverName string) *SmileyServer { func (srv *SmileyServer) SetupFromEnvironment() { srv.BaseServer.SetupFromEnvironment() - smileyKey := utils.StringFromEnv("SMILEY", "Smiling") + smileyKey := utils.StringFromEnv("SMILEY", "Grinning") - smiley, ok := Smileys[smileyKey] + smiley, ok := Smileys.Lookup(smileyKey) if !ok { smileyKey = "Neutral" - smiley = Smileys[smileyKey] + smiley, _ = Smileys.Lookup(smileyKey) } srv.smiley = smiley - fmt.Printf("%s %s: smiley %s\n", time.Now().Format(time.RFC3339), srv.Name, smileyKey) + fmt.Printf("%s %s: smiley %s (%s)\n", time.Now().Format(time.RFC3339), srv.Name, smileyKey, srv.smiley) } func (srv *SmileyServer) smileyGetHandler(r *http.Request, rstat *BaseRequestStatus) *BaseServerResponse { // The only error we need to handle here is the internal rate limiter. if rstat.ratelimited { + smiley, ok := Smileys.Lookup(Defaults["smiley-ratelimit"]) + + if !ok { + // This isn't good. + smiley, _ = Smileys.Lookup("Vomiting") + } + errstr := fmt.Sprintf("Rate limited (%.1f RPS > max %.1f RPS)", srv.CurrentRate(), srv.maxRate) return &BaseServerResponse{ StatusCode: http.StatusTooManyRequests, Data: map[string]interface{}{ - "smiley": Defaults["smiley-ratelimit"], + "smiley": smiley, "rate": fmt.Sprintf("%.1f RPS", srv.CurrentRate()), "errors": []string{errstr}, },