rework portforward + benchmarks

mine
derailed 2020-02-14 00:57:57 -07:00
parent 1ef375e781
commit 2822ac702e
13 changed files with 150 additions and 180 deletions

View File

@ -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
}

View File

@ -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())
})
}
}

View File

@ -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) {

View File

@ -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),

View File

@ -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("<PortForward>", 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
}

View File

@ -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("<PortForward on %s>", 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
}

View File

@ -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))
})
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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)
}

View File

@ -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()