diff --git a/internal/config/bench.go b/internal/config/bench.go index a224f17c..86502c96 100644 --- a/internal/config/bench.go +++ b/internal/config/bench.go @@ -49,11 +49,11 @@ type ( // BenchConfig represents a service benchmark. BenchConfig struct { + Name string C int `yaml:"concurrency"` N int `yaml:"requests"` Auth Auth `yaml:"auth"` HTTP HTTP `yaml:"http"` - Name string } ) @@ -73,7 +73,8 @@ func newBenchmark() Benchmark { } } -func (b Benchmark) empty() bool { +// Empty checks if the benchmark is set +func (b Benchmark) Empty() bool { return b.C == 0 && b.N == 0 } diff --git a/internal/config/bench_test.go b/internal/config/bench_test.go index 77df32fd..36157277 100644 --- a/internal/config/bench_test.go +++ b/internal/config/bench_test.go @@ -19,7 +19,7 @@ func TestBenchEmpty(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.b.empty()) + assert.Equal(t, u.e, u.b.Empty()) }) } } diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index 0b31cf19..38d1d8f5 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -23,6 +23,11 @@ import ( const localhost = "localhost" +// Tunnel represents a host tunnel port mapper. +type Tunnel struct { + Address, LocalPort, ContainerPort string +} + // PortForwarder tracks a port forward stream. type PortForwarder struct { client.Connection @@ -88,8 +93,9 @@ func (p *PortForwarder) FQN() string { } // Start initiates a port forward session for a given pod and ports. -func (p *PortForwarder) Start(path, co, address string, ports []string) (*portforward.PortForwarder, error) { - p.path, p.container, p.ports, p.age = path, co, ports, time.Now() +func (p *PortForwarder) Start(path, co string, t Tunnel) (*portforward.PortForwarder, error) { + fwds := []string{t.LocalPort + ":" + t.ContainerPort} + p.path, p.container, p.ports, p.age = path, co, fwds, time.Now() ns, n := client.Namespaced(path) auth, err := p.CanI(ns, "v1/pods", []string{client.GetVerb}) @@ -99,6 +105,8 @@ func (p *PortForwarder) Start(path, co, address string, ports []string) (*portfo if !auth { return nil, fmt.Errorf("user is not authorized to get pods") } + + // BOZO!! Use the factory! pod, err := p.DialOrDie().CoreV1().Pods(ns).Get(n, metav1.GetOptions{}) if err != nil { return nil, err @@ -131,7 +139,7 @@ func (p *PortForwarder) Start(path, co, address string, ports []string) (*portfo Name(n). SubResource("portforward") - return p.forwardPorts("POST", req.URL(), address, ports) + return p.forwardPorts("POST", req.URL(), t.Address, fwds) } func (p *PortForwarder) forwardPorts(method string, url *url.URL, address string, ports []string) (*portforward.PortForwarder, error) { diff --git a/internal/perf/benchmark.go b/internal/perf/benchmark.go index 53dfff8c..cbb669b8 100644 --- a/internal/perf/benchmark.go +++ b/internal/perf/benchmark.go @@ -64,6 +64,8 @@ func (b *Benchmark) init(base, version string) error { } req.Header.Set("User-Agent", ua) + log.Debug().Msgf("Benching %d:%d", b.config.N, b.config.C) + b.worker = &requester.Work{ Request: req, RequestBody: []byte(b.config.HTTP.Body), diff --git a/internal/ui/dialog/port_forward.go b/internal/ui/dialog/port_forward.go deleted file mode 100644 index 52a6cb7d..00000000 --- a/internal/ui/dialog/port_forward.go +++ /dev/null @@ -1,65 +0,0 @@ -package dialog - -import ( - "strings" - - "github.com/derailed/k9s/internal/ui" - "github.com/derailed/tview" - "github.com/gdamore/tcell" -) - -const portForwardKey = "portforward" - -// ShowPortForward pops a port forwarding configuration dialog. -func ShowPortForward(p *ui.Pages, port string, okFn func(address, lport, cport string)) { - f := tview.NewForm() - f.SetItemPadding(0) - f.SetButtonsAlign(tview.AlignCenter). - SetButtonBackgroundColor(tview.Styles.PrimitiveBackgroundColor). - SetButtonTextColor(tview.Styles.PrimaryTextColor). - SetLabelColor(tcell.ColorAqua). - SetFieldTextColor(tcell.ColorOrange) - - p1, p2, address := port, port, "localhost" - f.AddInputField("Pod Port:", p1, 20, nil, func(p string) { - p1 = p - }) - f.AddInputField("Local Port:", p2, 20, nil, func(p string) { - p2 = p - }) - f.AddInputField("Address:", address, 20, nil, func(h string) { - address = h - }) - - f.AddButton("OK", func() { - okFn(address, stripPort(p2), stripPort(p1)) - }) - f.AddButton("Cancel", func() { - DismissPortForward(p) - }) - - modal := tview.NewModalForm("", f) - modal.SetDoneFunc(func(_ int, b string) { - DismissPortForward(p) - }) - p.AddPage(portForwardKey, modal, false, false) - p.ShowPage(portForwardKey) -} - -// DismissPortForward dismiss the port forward dialog. -func DismissPortForward(p *ui.Pages) { - p.RemovePage(portForwardKey) -} - -// ---------------------------------------------------------------------------- -// Helpers... - -// StripPort removes the named port id if present. -func stripPort(p string) string { - tokens := strings.Split(p, ":") - if len(tokens) == 2 { - return strings.Replace(tokens[1], "╱UDP", "", 1) - } - - return p -} diff --git a/internal/ui/dialog/port_forwards.go b/internal/ui/dialog/port_forwards.go index 45871e3b..fbfc6ded 100644 --- a/internal/ui/dialog/port_forwards.go +++ b/internal/ui/dialog/port_forwards.go @@ -2,14 +2,19 @@ package dialog import ( "fmt" + "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tview" ) +const portForwardKey = "portforward" + // PortForwardFunc represents a port-forward callback function. -type PortForwardFunc func(path, address, lport, cport string) +type PortForwardFunc func(path, co string, mapper dao.Tunnel) // ShowPortForwards pops a port forwarding configuration dialog. func ShowPortForwards(p *ui.Pages, s *config.Styles, path string, ports []string, okFn PortForwardFunc) { @@ -23,7 +28,7 @@ func ShowPortForwards(p *ui.Pages, s *config.Styles, path string, ports []string p1, p2, address := ports[0], ports[0], "localhost" f.AddDropDown("Container Ports", ports, 0, func(sel string, _ int) { - p1, p2 = sel, stripPort(sel) + p1, p2 = sel, extractPort(sel) }) dropD, ok := f.GetFormItem(0).(*tview.DropDown) @@ -43,15 +48,20 @@ func ShowPortForwards(p *ui.Pages, s *config.Styles, path string, ports []string }) f.AddButton("OK", func() { - okFn(path, address, stripPort(p2), stripPort(p1)) + tunnel := dao.Tunnel{ + Address: address, + LocalPort: p2, + ContainerPort: extractPort(p1), + } + okFn(path, extractContainer(p1), tunnel) }) f.AddButton("Cancel", func() { - DismissPortForward(p) + DismissPortForwards(p) }) modal := tview.NewModalForm(fmt.Sprintf("", path), f) modal.SetDoneFunc(func(_ int, b string) { - DismissPortForward(p) + DismissPortForwards(p) }) p.AddPage(portForwardKey, modal, false, false) p.ShowPage(portForwardKey) @@ -61,3 +71,28 @@ func ShowPortForwards(p *ui.Pages, s *config.Styles, path string, ports []string func DismissPortForwards(p *ui.Pages) { p.RemovePage(portForwardKey) } + +// ---------------------------------------------------------------------------- +// Helpers... + +func extractPort(p string) string { + tokens := strings.Split(p, ":") + switch { + case len(tokens) < 2: + return tokens[0] + case len(tokens) == 2: + return strings.Replace(tokens[1], "╱UDP", "", 1) + default: + return tokens[1] + } +} + +func extractContainer(p string) string { + tokens := strings.Split(p, ":") + if len(tokens) != 2 { + return "n/a" + } + + co, _ := client.Namespaced(tokens[0]) + return co +} diff --git a/internal/ui/dialog/port_forward_test.go b/internal/ui/dialog/port_forwards_test.go similarity index 60% rename from internal/ui/dialog/port_forward_test.go rename to internal/ui/dialog/port_forwards_test.go index 1c2461e1..6ecb876f 100644 --- a/internal/ui/dialog/port_forward_test.go +++ b/internal/ui/dialog/port_forwards_test.go @@ -3,26 +3,27 @@ package dialog import ( "testing" + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tview" "github.com/stretchr/testify/assert" ) -func TestPortForwardDialog(t *testing.T) { +func TestPortForwards(t *testing.T) { p := ui.NewPages() - okFunc := func(address, lport, cport string) { - } - ShowPortForward(p, "8080", okFunc) + cbFunc := func(path, co string, t dao.Tunnel) {} + ShowPortForwards(p, config.NewStyles(), "fred", []string{"8080"}, cbFunc) d := p.GetPrimitive(portForwardKey).(*tview.ModalForm) assert.NotNil(t, d) - DismissPortForward(p) + DismissPortForwards(p) assert.Nil(t, p.GetPrimitive(portForwardKey)) } -func TestStripPort(t *testing.T) { +func TestExtractPort(t *testing.T) { uu := map[string]struct { port, e string }{ @@ -40,7 +41,7 @@ func TestStripPort(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, stripPort(u.port)) + assert.Equal(t, u.e, extractPort(u.port)) }) } } diff --git a/internal/view/benchmark.go b/internal/view/benchmark.go index 7fdc78f2..059c3703 100644 --- a/internal/view/benchmark.go +++ b/internal/view/benchmark.go @@ -46,7 +46,7 @@ func (b *Benchmark) viewBench(app *App, model ui.Tabular, gvr, path string) { return } - details := NewDetails(b.App(), "Benchmark", fileToSubject(path), false).Update(data) + details := NewDetails(b.App(), "Results", fileToSubject(path), false).Update(data) if err := app.inject(details); err != nil { app.Flash().Err(err) } diff --git a/internal/view/container.go b/internal/view/container.go index 724801eb..941b2889 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -103,6 +103,7 @@ func (c *Container) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } + log.Debug().Msgf("CONTAINER-SEL %q", path) if _, ok := c.App().factory.ForwarderFor(fwFQN(c.GetTable().Path, path)); ok { c.App().Flash().Err(fmt.Errorf("A PortForward already exist on container %s", c.GetTable().Path)) return nil @@ -112,12 +113,24 @@ func (c *Container) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { if !ok { return nil } - - dialog.ShowPortForward(c.App().Content.Pages, c.preparePort(ports), c.portForward) + log.Debug().Msgf("CONTAINER-PORTS %#v", ports) + dialog.ShowPortForwards(c.App().Content.Pages, c.App().Styles, c.GetTable().Path, ports, c.portForward) return nil } +func (c *Container) portForward(path, co string, t dao.Tunnel) { + pf := dao.NewPortForwarder(c.App().Conn()) + fw, err := pf.Start(path, co, t) + if err != nil { + c.App().Flash().Err(err) + return + } + + log.Debug().Msgf(">>> Starting port forward %q %#v", path, t) + go runForward(c.App(), pf, fw) +} + func (c *Container) isForwardable(path string) ([]string, bool) { state := c.GetTable().GetSelectedCell(3) if state != "Running" { @@ -132,43 +145,15 @@ func (c *Container) isForwardable(path string) ([]string, bool) { return nil, false } - return ports, true -} - -func (c *Container) preparePort(pp []string) string { - var port string - for _, p := range pp { + pp := make([]string, 0, len(ports)) + for _, p := range ports { if !isTCPPort(p) { continue } - port = strings.TrimSpace(p) - tokens := strings.Split(port, ":") - if len(tokens) == 2 { - port = tokens[1] - } - break - } - if port == "" { - c.App().Flash().Warn("No valid TCP port found on this container. User will specify...") - return "MY_TCP_PORT!" + pp = append(pp, path+"/"+p) } - return port -} - -func (c *Container) portForward(address, lport, cport string) { - co := c.GetTable().GetSelectedCell(0) - pf := dao.NewPortForwarder(c.App().Conn()) - path := c.GetTable().GetSelectedItem() - ports := []string{lport + ":" + cport} - fw, err := pf.Start(path, co, address, ports) - if err != nil { - c.App().Flash().Err(err) - return - } - - log.Debug().Msgf(">>> Starting port forward %q %v", path, ports) - go runForward(c.App(), pf, fw) + return pp, true } // ---------------------------------------------------------------------------- @@ -178,7 +163,7 @@ func runForward(a *App, pf *dao.PortForwarder, f *portforward.PortForwarder) { a.QueueUpdateDraw(func() { a.factory.AddForwarder(pf) a.Flash().Infof("PortForward activated %s:%s", pf.Path(), pf.Ports()[0]) - dialog.DismissPortForward(a.Content.Pages) + dialog.DismissPortForwards(a.Content.Pages) }) pf.SetActive(true) diff --git a/internal/view/pod.go b/internal/view/pod.go index 0a79b7c7..413c18d5 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strconv" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" @@ -135,48 +136,51 @@ func (p *Pod) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - pp, err := fetchPodPorts(p.App().factory, path) - if err != nil { + if err := showFwdDialog(p.App(), path, p.portForward); err != nil { p.App().Flash().Err(err) - return nil } - ports := make([]string, 0, len(pp)) - for _, p := range pp { - if p.Protocol == v1.ProtocolTCP { - port := fmt.Sprintf("%s:%d", p.Name, p.ContainerPort) - if p.Name == "" { - port = fmt.Sprintf("%d", p.ContainerPort) - } - ports = append(ports, port) - } - } - - if len(ports) == 0 { - p.App().Flash().Err(fmt.Errorf("no tcp ports found on %s", path)) - return nil - } - - dialog.ShowPortForwards(p.App().Content.Pages, p.App().Styles, path, ports, p.portForward) return nil } -func (p *Pod) portForward(path, address, lport, cport string) { +func (p *Pod) portForward(path, co string, t dao.Tunnel) { pf := dao.NewPortForwarder(p.App().Conn()) - ports := []string{lport + ":" + cport} - fw, err := pf.Start(path, "", address, ports) + fw, err := pf.Start(path, co, t) if err != nil { p.App().Flash().Err(err) return } - log.Debug().Msgf(">>> Starting port forward %q %v", path, ports) + log.Debug().Msgf(">>> Starting port forward %q:%s %v", path, co, t) go runForward(p.App(), pf, fw) } // ---------------------------------------------------------------------------- // Helpers... +func showFwdDialog(a *App, path string, cb dialog.PortForwardFunc) error { + mm, err := fetchPodPorts(a.factory, path) + if err != nil { + return nil + } + ports := make([]string, 0, len(mm)) + for co, pp := range mm { + for _, p := range pp { + if p.Protocol != v1.ProtocolTCP { + continue + } + ports = append(ports, client.FQN(co, p.Name)+":"+strconv.Itoa(int(p.ContainerPort))) + } + } + + if len(ports) == 0 { + return fmt.Errorf("no tcp ports found on %s", path) + } + dialog.ShowPortForwards(a.Content.Pages, a.Styles, path, ports, cb) + + return nil +} + func containerShellin(a *App, comp model.Component, path, co string) error { if co != "" { resumeShellIn(a, comp, path, co) @@ -262,7 +266,7 @@ func fetchContainers(f *watch.Factory, path string, includeInit bool) ([]string, return nn, nil } -func fetchPodPorts(f *watch.Factory, path string) ([]v1.ContainerPort, error) { +func fetchPodPorts(f *watch.Factory, path string) (map[string][]v1.ContainerPort, error) { log.Debug().Msgf("Fetching ports on pod %q", path) o, err := f.Get("v1/pods", path, false, labels.Everything()) if err != nil { @@ -275,9 +279,9 @@ func fetchPodPorts(f *watch.Factory, path string) ([]v1.ContainerPort, error) { return nil, err } - pp := make([]v1.ContainerPort, 0, len(pod.Spec.Containers)) - for _, c := range pod.Spec.Containers { - pp = append(pp, c.Ports...) + pp := make(map[string][]v1.ContainerPort) + for _, co := range pod.Spec.Containers { + pp[co.Name] = co.Ports } return pp, nil diff --git a/internal/view/port_forward.go b/internal/view/port_forward.go index 551e2558..b31e7c84 100644 --- a/internal/view/port_forward.go +++ b/internal/view/port_forward.go @@ -3,6 +3,8 @@ package view import ( "context" "fmt" + "regexp" + "strings" "time" "github.com/derailed/k9s/internal" @@ -63,6 +65,8 @@ func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } +var podNameRX = regexp.MustCompile(`\A(.+)\-(\w{10})\-(\w{5})\z`) + func (p *PortForward) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey { if p.bench != nil { p.App().Status(ui.FlashErr, "Benchmark Canceled!") @@ -71,18 +75,34 @@ func (p *PortForward) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - sel := p.GetTable().GetSelectedItem() - if sel == "" { + path := p.GetTable().GetSelectedItem() + if path == "" { return nil } + tokens := strings.Split(path, ":") + ns, po := client.Namespaced(tokens[0]) + sections := podNameRX.FindStringSubmatch(po) + log.Debug().Msgf("SECTIONS %q::%q--%#v", ns, po, sections) + if len(sections) >= 1 { + po = sections[1] + } + key := client.FQN(ns, po) + ":" + tokens[1] - r, _ := p.GetTable().GetSelection() cfg := defaultConfig() - if b, ok := p.App().Bench.Benchmarks.Containers[sel]; ok { + if defaults := p.App().Bench.Benchmarks.Defaults; !defaults.Empty() { + cfg.C, cfg.N = defaults.C, defaults.N + } + + log.Debug().Msgf("CUST-CFG %q -- %#v", path, key) + if b, ok := p.App().Bench.Benchmarks.Containers[key]; ok { + log.Debug().Msgf("FOUND CUST BENCH_CFG!") cfg = b } - cfg.Name = sel + cfg.Name = path + log.Debug().Msgf("BenchCFG %q::%#v", path, cfg) + + r, _ := p.GetTable().GetSelection() base := ui.TrimCell(p.GetTable().SelectTable, r, 4) var err error if p.bench, err = perf.NewBenchmark(base, p.App().version, cfg); err != nil { diff --git a/internal/view/svc.go b/internal/view/svc.go index 0a5aff0b..b8270991 100644 --- a/internal/view/svc.go +++ b/internal/view/svc.go @@ -11,7 +11,6 @@ import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/perf" "github.com/derailed/k9s/internal/ui" - "github.com/derailed/k9s/internal/ui/dialog" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" @@ -87,42 +86,22 @@ func (s *Service) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - pp, err := fetchPodPorts(s.App().factory, pod) - if err != nil { + if err := showFwdDialog(s.App(), pod, s.portForward); err != nil { s.App().Flash().Err(err) - return nil } - ports := make([]string, 0, len(pp)) - for _, p := range pp { - if p.Protocol == v1.ProtocolTCP { - port := fmt.Sprintf("%s:%d", p.Name, p.ContainerPort) - if p.Name == "" { - port = fmt.Sprintf("%d", p.ContainerPort) - } - ports = append(ports, port) - } - } - - if len(ports) == 0 { - s.App().Flash().Err(fmt.Errorf("no tcp ports found on %s", path)) - return nil - } - - dialog.ShowPortForwards(s.App().Content.Pages, s.App().Styles, pod, ports, s.portForward) return nil } -func (s *Service) portForward(path, address, lport, cport string) { +func (s *Service) portForward(path, co string, t dao.Tunnel) { pf := dao.NewPortForwarder(s.App().Conn()) - ports := []string{lport + ":" + cport} - fw, err := pf.Start(path, "", address, ports) + fw, err := pf.Start(path, co, t) if err != nil { s.App().Flash().Err(err) return } - log.Debug().Msgf(">>> Starting port forward %q %v", path, ports) + log.Debug().Msgf(">>> Starting port forward %q %#v", path, t) go runForward(s.App(), pf, fw) } diff --git a/internal/watch/forwarders.go b/internal/watch/forwarders.go index aa53cc1b..60464f32 100644 --- a/internal/watch/forwarders.go +++ b/internal/watch/forwarders.go @@ -4,13 +4,13 @@ import ( "strings" "github.com/rs/zerolog/log" - "k8s.io/client-go/tools/portforward" ) // Forwarder represents a port forwarder. type Forwarder interface { - // Start initializes a port forward. - Start(path, co, address string, ports []string) (*portforward.PortForwarder, error) + // BOZO!! + // // Start initializes a port forward. + // Start(path, co, string, t dao.Tunnel) (*portforward.PortForwarder, error) // Stop terminates a port forward. Stop()