k9s/internal/view/svc.go

191 lines
4.4 KiB
Go

package view
import (
"errors"
"fmt"
"strings"
"time"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/perf"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/ui"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
)
// Service represents a service viewer.
type Service struct {
ResourceViewer
bench *perf.Benchmark
}
// NewService returns a new viewer.
func NewService(title, gvr string, list resource.List) ResourceViewer {
s := Service{
ResourceViewer: NewLogsExtender(
NewResource(title, gvr, list),
func() string { return "" },
),
}
s.BindKeys()
s.GetTable().SetEnterFn(s.showPods)
return &s
}
// Protocol...
func (s *Service) BindKeys() {
s.Actions().Add(ui.KeyActions{
tcell.KeyCtrlB: ui.NewKeyAction("Bench", s.benchCmd, true),
tcell.KeyCtrlK: ui.NewKeyAction("Bench Stop", s.benchStopCmd, true),
ui.KeyShiftT: ui.NewKeyAction("Sort Type", s.GetTable().SortColCmd(1, true), false),
})
}
func (s *Service) showPods(app *App, ns, res, sel string) {
log.Debug().Msgf("SVC SHOW PODS %q -- %q -- %q", ns, res, sel)
ns, n := namespaced(sel)
svc, err := k8s.NewService(app.Conn()).Get(ns, n)
if err != nil {
app.Flash().Err(err)
return
}
sv, ok := svc.(*v1.Service)
if !ok {
log.Fatal().Msg("Expecting a valid service")
}
showPodsFromLabels(s.App(), sel, sv.Spec.Selector)
}
func (s *Service) benchStopCmd(evt *tcell.EventKey) *tcell.EventKey {
if s.bench != nil {
log.Debug().Msg(">>> Benchmark canceled!!")
s.App().status(ui.FlashErr, "Benchmark Canceled!")
s.bench.Cancel()
}
s.App().StatusReset()
return nil
}
func (s *Service) checkSvc(row int) error {
svcType := trimCellRelative(s.GetTable(), row, 1)
if svcType != "NodePort" && svcType != "LoadBalancer" {
return errors.New("You must select a reachable service")
}
return nil
}
func (s *Service) getExternalPort(row int) (string, error) {
ports := trimCellRelative(s.GetTable(), row, 5)
pp := strings.Split(ports, " ")
if len(pp) == 0 {
return "", errors.New("No ports found")
}
// Grap the first port pair for now...
tokens := strings.Split(pp[0], "►")
if len(tokens) < 2 {
return "", errors.New("No ports pair found")
}
return tokens[1], nil
}
func (s *Service) reloadBenchCfg() error {
path := ui.BenchConfig(s.App().Config.K9s.CurrentCluster)
return s.App().Bench.Reload(path)
}
func (s *Service) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
sel := s.GetTable().GetSelectedItem()
if sel == "" || s.bench != nil {
return evt
}
if err := s.reloadBenchCfg(); err != nil {
s.App().Flash().Err(err)
return nil
}
cfg, ok := s.App().Bench.Benchmarks.Services[sel]
if !ok {
s.App().Flash().Errf("No bench config found for service %s", sel)
return nil
}
cfg.Name = sel
log.Debug().Msgf("Benchmark config %#v", cfg)
row := s.GetTable().GetSelectedRowIndex()
if err := s.checkSvc(row); err != nil {
s.App().Flash().Err(err)
return nil
}
port, err := s.getExternalPort(row)
if err != nil {
s.App().Flash().Err(err)
return nil
}
if err := s.runBenchmark(port, cfg); err != nil {
s.App().Flash().Errf("Benchmark failed %v", err)
s.App().StatusReset()
s.bench = nil
}
return nil
}
func (s *Service) runBenchmark(port string, cfg config.BenchConfig) error {
if cfg.HTTP.Host == "" {
return fmt.Errorf("Invalid benchmark host %q", cfg.HTTP.Host)
}
var err error
base := "http://" + cfg.HTTP.Host + ":" + port + cfg.HTTP.Path
if s.bench, err = perf.NewBenchmark(base, cfg); err != nil {
return err
}
s.App().status(ui.FlashWarn, "Benchmark in progress...")
log.Debug().Msg("Bench starting...")
go s.bench.Run(s.App().Config.K9s.CurrentCluster, s.benchDone)
return nil
}
func (s *Service) benchDone() {
log.Debug().Msg("Bench Completed!")
s.App().QueueUpdate(func() {
if s.bench.Canceled() {
s.App().status(ui.FlashInfo, "Benchmark canceled")
} else {
s.App().status(ui.FlashInfo, "Benchmark Completed!")
s.bench.Cancel()
}
s.bench = nil
go benchTimedOut(s.App())
})
}
func benchTimedOut(app *App) {
<-time.After(2 * time.Second)
app.QueueUpdate(func() {
app.StatusReset()
})
}
func showPodsFromLabels(app *App, path string, sel map[string]string) {
var labels []string
for k, v := range sel {
labels = append(labels, fmt.Sprintf("%s=%s", k, v))
}
showPods(app, path, strings.Join(labels, ","), "")
}