222 lines
5.1 KiB
Go
222 lines
5.1 KiB
Go
package view
|
|
|
|
import (
|
|
"context"
|
|
"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 {
|
|
*Resource
|
|
|
|
bench *perf.Benchmark
|
|
logs *Logs
|
|
}
|
|
|
|
// NewService returns a new viewer.
|
|
func NewService(title, gvr string, list resource.List) ResourceViewer {
|
|
return &Service{
|
|
Resource: NewResource(title, gvr, list),
|
|
}
|
|
}
|
|
|
|
// Init initializes the viewer.
|
|
func (s *Service) Init(ctx context.Context) {
|
|
s.extraActionsFn = s.extraActions
|
|
s.enterFn = s.showPods
|
|
s.Resource.Init(ctx)
|
|
|
|
s.logs = NewLogs(s.list.GetName(), s)
|
|
s.logs.Init(ctx)
|
|
}
|
|
|
|
// Protocol...
|
|
|
|
func (s *Service) getList() resource.List {
|
|
return s.list
|
|
}
|
|
|
|
func (s *Service) getSelection() string {
|
|
return s.masterPage().GetSelectedItem()
|
|
}
|
|
|
|
func (s *Service) extraActions(aa ui.KeyActions) {
|
|
aa[ui.KeyL] = ui.NewKeyAction("Logs", s.logsCmd, true)
|
|
aa[tcell.KeyCtrlB] = ui.NewKeyAction("Bench", s.benchCmd, true)
|
|
aa[tcell.KeyCtrlK] = ui.NewKeyAction("Bench Stop", s.benchStopCmd, true)
|
|
aa[ui.KeyShiftT] = ui.NewKeyAction("Sort Type", s.sortColCmd(1, false), false)
|
|
}
|
|
|
|
func (s *Service) showPods(app *App, _, res, sel string) {
|
|
ns, n := namespaced(sel)
|
|
svc, err := k8s.NewService(app.Conn()).Get(ns, n)
|
|
if err != nil {
|
|
app.Flash().Err(err)
|
|
return
|
|
}
|
|
|
|
if sv, ok := svc.(*v1.Service); ok {
|
|
s.showSvcPods(ns, sv.Spec.Selector, s.backCmd)
|
|
}
|
|
}
|
|
|
|
func (s *Service) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if !s.masterPage().RowSelected() {
|
|
return evt
|
|
}
|
|
|
|
s.logs.reload("", s, false)
|
|
s.Push(s.logs)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
// Reset namespace to what it was
|
|
if err := s.app.Config.SetActiveNamespace(s.list.GetNamespace()); err != nil {
|
|
log.Error().Err(err).Msg("Unable to set active namespace")
|
|
}
|
|
s.app.inject(s)
|
|
|
|
return nil
|
|
}
|
|
|
|
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.masterPage(), 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.masterPage(), 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 {
|
|
// BOZO!! Poorman Reload bench to make sure we pick up updates if any.
|
|
path := ui.BenchConfig(s.app.Config.K9s.CurrentCluster)
|
|
return s.app.Bench.Reload(path)
|
|
}
|
|
|
|
func (s *Service) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if !s.masterPage().RowSelected() || s.bench != nil {
|
|
return evt
|
|
}
|
|
|
|
if err := s.reloadBenchCfg(); err != nil {
|
|
s.app.Flash().Err(err)
|
|
return nil
|
|
}
|
|
|
|
sel := s.getSelection()
|
|
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.masterPage().GetSelection()
|
|
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 (s *Service) showSvcPods(ns string, sel map[string]string, a ui.ActionHandler) {
|
|
var labels []string
|
|
for k, v := range sel {
|
|
labels = append(labels, fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
showPods(s.app, ns, strings.Join(labels, ","), "", a)
|
|
}
|