Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Turner committed Aug 29, 2021
1 parent e5b7d1a commit a553a02
Show file tree
Hide file tree
Showing 15 changed files with 1,040 additions and 55 deletions.
86 changes: 77 additions & 9 deletions cmd/client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"net/http"
"net/url"
"os"
"time"

Expand All @@ -28,7 +29,7 @@ func main() {
Flags: []cli.Flag{
&cli.StringFlag{
Name: "url",
Value: "http://localhost:8080",
Value: "http://localhost:8081",
Usage: "URL of the envbin daemon",
},
},
Expand Down Expand Up @@ -56,12 +57,18 @@ var (
func render(c *cli.Context) error {
//log := c.App.Metadata["log"].(logr.Logger)

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

client := &http.Client{}

req, err := http.NewRequestWithContext(ctx, "GET", c.String("addr"), nil)
base, err := url.Parse(c.String("url"))
if err != nil {
return err
}
path, _ := url.Parse("/api/v1/env")

req, err := http.NewRequestWithContext(ctx, "GET", base.ResolveReference(path).String(), nil)
if err != nil {
return err
}
Expand Down Expand Up @@ -91,6 +98,33 @@ func render(c *cli.Context) error {
return err
}

renderSummary(root)
norm.Println()

if false {
renderNetIfaces(root)
norm.Println()
}

if false {
renderPCI(root)
norm.Println()
}

if false {
renderUSB(root)
norm.Println()
}

if true {
renderBlock(root)
norm.Println()
}

return nil
}

func renderSummary(root *jsonquery.Node) {
whiteBold.Print(jsonquery.FindOne(root, "Network/Hostname").InnerText())
norm.Print(" " + jsonquery.FindOne(root, "Network/DefaultIP").InnerText())
norm.Print(" / " + jsonquery.FindOne(root, "Network/ExternalIP/Address").InnerText())
Expand All @@ -99,19 +133,26 @@ func render(c *cli.Context) error {
whiteBold.Print(jsonquery.FindOne(root, "OS/Distro/Release").InnerText())
norm.Print(" " + jsonquery.FindOne(root, "OS/Distro/Version").InnerText())
grey.Printf(" (%s %s)", jsonquery.FindOne(root, "OS/Kernel/Type").InnerText(), jsonquery.FindOne(root, "OS/Kernel/Version").InnerText())
norm.Print(" up " + jsonquery.FindOne(root, "OS/Uptime").InnerText())
norm.Println()

whiteBold.Print(jsonquery.FindOne(root, "Hardware/CPU/Model").InnerText())
white.Printf(" %s/%s", jsonquery.FindOne(root, "Hardware/CPU/Cores").InnerText(), jsonquery.FindOne(root, "Hardware/CPU/Threads").InnerText())
white.Printf(" %s", jsonquery.FindOne(root, "Hardware/CPU/Arch").InnerText())
norm.Println()
whiteBold.Print(jsonquery.FindOne(root, "Hardware/Memory/Total").InnerText())
norm.Println()
}

func renderNetIfaces(root *jsonquery.Node) {
for _, iface := range jsonquery.Find(root, "Network/Interfaces/*") {
whiteBold.Print(jsonquery.FindOne(iface, "Name").InnerText())
norm.Print(" " + jsonquery.FindOne(iface, "Address").InnerText())
grey.Print(" " + jsonquery.FindOne(iface, "Flags").InnerText())
norm.Println()
}
}

norm.Println()

func renderPCI(root *jsonquery.Node) {
for _, dev := range jsonquery.Find(root, "Hardware/Bus/PCI/*") {
white.Print(dev.Data)
fns := jsonquery.Find(dev, "Functions/*")
Expand All @@ -128,9 +169,9 @@ func render(c *cli.Context) error {
norm.Println()
}
}
}

norm.Println()

func renderUSB(root *jsonquery.Node) {
for _, dev := range jsonquery.Find(root, "Hardware/Bus/USB/*") {
white.Print(dev.Data)
whiteBold.Printf(" %s %s", jsonquery.FindOne(dev, "Manufacturer").InnerText(), jsonquery.FindOne(dev, "Product").InnerText())
Expand Down Expand Up @@ -173,8 +214,35 @@ func render(c *cli.Context) error {
}
}
}
}

return nil
func renderBlock(root *jsonquery.Node) {
for _, blk := range jsonquery.Find(root, "Hardware/Block/*") {
white.Print(blk.Data)
whiteBold.Printf(" %s %s", jsonquery.FindOne(blk, "Vendor").InnerText(), jsonquery.FindOne(blk, "Model").InnerText())

serial := jsonquery.FindOne(blk, "Serial").InnerText()
if serial != "" {
grey.Printf(" serial %s", serial)
}

norm.Printf(" [%s, %s bytes", jsonquery.FindOne(blk, "ControllerType").InnerText(), jsonquery.FindOne(blk, "SizeBytes").InnerText())
if jsonquery.FindOne(blk, "Removable").InnerText() == "true" {
norm.Printf("Removable")
}
norm.Print("]")

norm.Println()

ps := jsonquery.Find(blk, "Partitions/*")
for _, p := range ps {
norm.Print(" ")
white.Print(p.Data)
whiteBold.Printf(" %s on %s", jsonquery.FindOne(p, "Filesystem").InnerText(), jsonquery.FindOne(p, "MountPoint").InnerText())
grey.Printf(" uuid %s", jsonquery.FindOne(p, "UUID").InnerText())
norm.Println()
}
}
}

// func title(s string) {
Expand Down
167 changes: 152 additions & 15 deletions cmd/daemon/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ package main

import (
"context"
"math/rand"
"net"
"net/http"
"time"

"github.com/gin-gonic/gin"
"github.com/go-logr/logr"
"github.com/mt-inside/envbin/pkg/data"
"github.com/mt-inside/go-usvc"
"github.com/spf13/viper"
"github.com/urfave/cli/v2"

"github.com/mt-inside/envbin/internal/config"
"github.com/mt-inside/envbin/internal/servers"
"github.com/mt-inside/envbin/pkg/middleware"
)

var Serve = &cli.Command{
Expand All @@ -16,35 +24,164 @@ var Serve = &cli.Command{

Flags: []cli.Flag{
&cli.StringFlag{
Name: "addr",
Name: "output-addr",
Value: ":8080",
Usage: "Listen address",
Usage: "Listen address for Lorem Ipsum output",
},
&cli.StringFlag{
Name: "api-addr",
Value: ":8081",
Usage: "Listen address for API",
},
},

Action: serve,
}

func serve(c *cli.Context) error {
var err error

log := c.App.Metadata["log"].(logr.Logger)

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
data := data.GetData(ctx, log) // TODO refresh on GET. TODO push updates to web UI (gin seems to support push)
stopCh := make(chan struct{})
shutdown := false
defer func() {
if !shutdown {
close(stopCh)
}
}()

signalCh := usvc.InstallSignalHandlers(log)

err = config.LoadConfig(log)
if err != nil {
panic(err)
}

// TODO: output does need to be on a different port cause it needs a different http server cause of low-level tcp-rst stuff
muxApi := gin.Default()
servers.GetProbes(log, muxApi.Group("/health"))
servers.GetEnv(log, muxApi.Group("/api/v1/env"))
servers.GetConfig(log, muxApi.Group("/api/v1/config"))

listenAddrApi := c.String("api-addr")
chApi := serveHttpSimple(log, listenAddrApi, muxApi, stopCh)

listenAddr := c.String("addr")
// TODO: kick off ReadyTimer (as per -> ). 100% shouldn't be in config/

r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.JSON(200, data)
})
muxOutput := gin.Default()
servers.GetOutput(log, muxOutput.Group("/"))

err := r.Run(listenAddr)
listenAddrOutput := c.String("output-addr")
chOutput := serveHttpChaos(log, listenAddrOutput, muxOutput, stopCh)

//rootMux.Path("/health").HandlerFunc(healthHandler) TODO merge with badpod; proper probes
//rootMux.Path("/ready").HandlerFunc(healthHandler) TODO recall use a struct and Handler() to get a log to these things
select {
case err = <-chApi:
case err = <-chOutput:
case <-signalCh:
}

// TODO: graceful shutdown (lower readiness - combine with badpod first)
// TODO: keep the probes serving? Closing the http socket will be notice enough, but that's subject to a timeout, and we want /ready to fail instantly.
// TODO: can we unmount the other routes?
log.Info("Finished serving", "error", err)

shutdown = true
close(stopCh)

shutdownDelay, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(viper.GetInt("ShutdownDelayMS")))
defer cancel()

log.Info("Waiting for shutdown")
for _, ch := range []<-chan error{chOutput, chApi} {
if err := <-ch; err != nil {
log.Error(err, "Error during shutdown")
}
}

log.Info("Delaying exit for the remainder of ShutdownDelay")
<-shutdownDelay.Done()

log.Info("Shutdown complete")

return err
}

func serveHttpSimple(log logr.Logger, listenAddr string, router http.Handler, stopCh <-chan struct{}) <-chan error {
localCh := make(chan error)

srv := &http.Server{
Addr: listenAddr,
Handler: router,
}

go serveHttpInner(log, srv, localCh, stopCh)

return localCh
}

func serveHttpChaos(log logr.Logger, listenAddr string, router *gin.Engine, stopCh <-chan struct{}) <-chan error {
localCh := make(chan error)

// Order important; see comments
router.Use(gin.WrapF(middleware.Delay)) // delay should apply to any return value
router.Use(gin.WrapF(middleware.Error)) // error should come after a delay
// Currently throttled at string-read time :( router.Use(gin.WrapH(middleware.Rate)) // don't apply rate limit to eg the "injected error" message
// TODO: rps middleware, distinct from bw throttling above, eg https://github.com/s12i/gin-throttle

deriveContext := func(ctx context.Context, c net.Conn) context.Context {
return context.WithValue(
context.WithValue(ctx,
middleware.CtxKeyConn, c), // So the handler can close the connection at the TCP level. This is not the listen socket; it's called every time there's a new connection
middleware.CtxKeyLog, log,
)
}
connEvent := func(conn net.Conn, event http.ConnState) {
log.V(1).Info("Connection event", "event", event, "remote", conn.RemoteAddr())

if event == http.StateNew &&
viper.GetString("ErrorType") == "tcp-rst" &&
viper.GetFloat64("ErrorRate") > rand.Float64() {

log.V(1).Info("Closing TCP stream with RST")

// TODO: wait for delay period

// close stream with bytes still to be read in the buffer, hence cause data loss, resulting in a RST
conn.Close()
}
}
srv := &http.Server{
Addr: listenAddr,
Handler: router,
ConnContext: deriveContext,
ConnState: connEvent,
}

go serveHttpInner(log, srv, localCh, stopCh)

return localCh
}

func serveHttpInner(log logr.Logger, srv *http.Server, localCh chan<- error, stopCh <-chan struct{}) {
defer close(localCh)

log.Info("Listening", "addr", srv.Addr)
serverCh := usvc.ChannelWrapper(func() error { return srv.ListenAndServe() })

select {
case err := <-serverCh:
localCh <- err
case <-stopCh:
// TODO: ideally reach out to all in-flight requests (like long-lived ones) and cancel them

log.Info("Attempting to gracefully shut down http server")
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(viper.GetInt64("ShutdownDelayMS"))) // We use this value as the maximum and minimum time to wait
defer cancel()

if err := srv.Shutdown(ctx); err != nil {
log.Error(err, "Failed to gracefully shut down http server")
localCh <- err
}
log.Info("Http server shut down")
}
}
14 changes: 8 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,34 @@ module github.com/mt-inside/envbin
go 1.13

require (
github.com/antchfx/jsonquery v1.1.4 // indirect
github.com/antchfx/jsonquery v1.1.4
github.com/aws/aws-sdk-go-v2/config v1.1.1
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2
github.com/cloudfoundry/gosigar v1.1.0
github.com/davecgh/go-spew v1.1.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/docker/go-units v0.4.0
github.com/dselans/dmidecode v0.0.0-20180814053009-65c3f9d81910
github.com/fatih/color v1.10.0
github.com/fsnotify/fsnotify v1.4.9
github.com/gin-gonic/gin v1.7.3
github.com/go-logr/logr v0.4.0
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/google/gousb v1.1.1
github.com/gorilla/handlers v1.4.2
github.com/jaypipes/ghw v0.8.0
github.com/jessevdk/go-flags v1.4.0
github.com/joho/godotenv v1.3.0
github.com/klauspost/cpuid/v2 v2.0.4
github.com/mattn/go-isatty v0.0.12
github.com/mt-inside/go-usvc v0.0.2
github.com/mt-inside/go-usvc v0.0.3
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
github.com/onsi/ginkgo v1.12.0 // indirect
github.com/onsi/gomega v1.9.0 // indirect
github.com/shirou/gopsutil/v3 v3.21.2
github.com/stretchr/testify v1.6.1
github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.7.0
github.com/urfave/cli/v2 v2.3.0
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005
gopkg.in/yaml.v2 v2.3.0 // indirect
golang.org/x/sys v0.0.0-20210510120138-977fb7262007
k8s.io/apimachinery v0.20.4
k8s.io/client-go v0.20.4
)
Loading

0 comments on commit a553a02

Please sign in to comment.