cleanup and bug fixes

mine
derailed 2020-10-29 09:32:59 -06:00
parent 4045cb56a4
commit 372b4d8e09
17 changed files with 132 additions and 89 deletions

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.15
require (
github.com/atotto/clipboard v0.1.2
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/cenkalti/backoff/v4 v4.1.0
github.com/derailed/popeye v0.8.10
github.com/derailed/tview v0.4.6

1
go.sum
View File

@ -111,6 +111,7 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXe
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/cenkalti/backoff v1.1.0 h1:QnvVp8ikKCDWOsFheytRCoYWYPO/ObCTBGxT19Hc+yE=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

View File

@ -264,29 +264,29 @@ func (a *APIClient) HasMetrics() bool {
if !ok || err != nil {
return false
}
v, ok := a.cache.Get(cacheMXKey)
if ok {
if v, ok := a.cache.Get(cacheMXKey); ok {
flag, k := v.(bool)
return k && flag
}
var flag bool
var metricsOK bool
defer func() {
a.cache.Add(cacheMXKey, metricsOK, cacheExpiry)
}()
dial, err := a.MXDial()
if err != nil {
a.cache.Add(cacheMXKey, flag, cacheExpiry)
return flag
return metricsOK
}
ctx, cancel := context.WithTimeout(context.Background(), a.config.CallTimeout())
defer cancel()
if _, err := dial.MetricsV1beta1().NodeMetricses().List(ctx, metav1.ListOptions{Limit: 1}); err == nil {
flag = true
metricsOK = true
} else {
log.Error().Err(err).Msgf("List metrics failed")
}
a.cache.Add(cacheMXKey, flag, cacheExpiry)
return flag
return metricsOK
}
// Dial returns a handle to api server or die.
@ -412,10 +412,6 @@ func (a *APIClient) reset() {
}
func (a *APIClient) supportsMetricsResources() (supported bool, err error) {
defer func() {
a.cache.Add(cacheMXAPIKey, supported, cacheExpiry)
}()
if v, ok := a.cache.Get(cacheMXAPIKey); ok {
flag, k := v.(bool)
supported = k && flag
@ -424,6 +420,9 @@ func (a *APIClient) supportsMetricsResources() (supported bool, err error) {
if a.config == nil || a.config.flags == nil {
return
}
defer func() {
a.cache.Add(cacheMXAPIKey, supported, cacheExpiry)
}()
dial, err := a.CachedDiscovery()
if err != nil {

View File

@ -229,7 +229,6 @@ func (c *Config) Load(path string) error {
// Save configuration to disk.
func (c *Config) Save() error {
log.Debug().Msg("[Config] Saving configuration...")
c.Validate()
return c.SaveFile(K9sConfigFile)

View File

@ -261,7 +261,7 @@ func TestSetup(t *testing.T) {
var expectedConfig = `k9s:
refreshRate: 100
maxConnRetry: 15
maxConnRetry: 5
enableMouse: false
headless: false
crumbsless: false
@ -344,7 +344,7 @@ var expectedConfig = `k9s:
var resetConfig = `k9s:
refreshRate: 2
maxConnRetry: 15
maxConnRetry: 5
enableMouse: false
headless: false
crumbsless: false

View File

@ -4,7 +4,7 @@ import "github.com/derailed/k9s/internal/client"
const (
defaultRefreshRate = 2
defaultMaxConnRetry = 15
defaultMaxConnRetry = 5
)
// K9s tracks K9s configuration options.

View File

@ -116,7 +116,7 @@ func (n *Node) Get(ctx context.Context, path string) (runtime.Object, error) {
)
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
if nmx, err = client.DialMetrics(n.Client()).FetchNodesMetrics(ctx); err != nil {
log.Warn().Err(err).Msgf("No node metrics")
log.Debug().Err(err).Msgf("No node metrics")
}
}

View File

@ -16,24 +16,6 @@ import (
"github.com/sahilm/fuzzy"
)
type ResourceViewerListener interface {
ResourceChanged(lines []string, matches fuzzy.Matches)
ResourceFailed(error)
}
type ToggleOpts map[string]bool
type ResourceViewer interface {
GetPath() string
Filter(string)
ClearFilter()
Peek() []string
SetOptions(context.Context, ToggleOpts)
Watch(context.Context) error
AddListener(ResourceViewerListener)
RemoveListener(ResourceViewerListener)
}
// Describe tracks describeable resources.
type Describe struct {
gvr client.GVR
@ -50,7 +32,7 @@ func NewDescribe(gvr client.GVR, path string) *Describe {
return &Describe{
gvr: gvr,
path: path,
refreshRate: 2 * time.Second,
refreshRate: defaultReaderRefreshRate,
}
}
@ -60,7 +42,7 @@ func (d *Describe) GetPath() string {
}
// SetOptions toggle model options.
func (d *Describe) SetOptions(context.Context, ToggleOpts) {}
func (d *Describe) SetOptions(context.Context, ViewerToggleOpts) {}
// Filter filters the model.
func (d *Describe) Filter(q string) {
@ -134,27 +116,30 @@ func (d *Describe) Watch(ctx context.Context) error {
func (d *Describe) updater(ctx context.Context) {
defer log.Debug().Msgf("Describe canceled -- %q", d.gvr)
bf := backoff.NewExponentialBackOff()
bf.InitialInterval, bf.MaxElapsedTime = initRefreshRate, maxRetryInterval
rate := initRefreshRate
backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval)
delay := defaultReaderRefreshRate
for {
select {
case <-ctx.Done():
return
case <-time.After(rate):
rate = d.refreshRate
err := backoff.Retry(func() error {
return d.refresh(ctx)
}, backoff.WithContext(bf, ctx))
if err != nil {
log.Error().Err(err).Msgf("Retry failed")
case <-time.After(delay):
if err := d.refresh(ctx); err != nil {
log.Error().Err(err).Msgf("Describe Failed")
d.fireResourceFailed(err)
return
delay = backOff.NextBackOff()
if delay == backoff.Stop {
log.Error().Err(err).Msgf("Describe done Retrying bailing out!")
return
}
} else {
backOff.Reset()
delay = defaultReaderRefreshRate
}
}
}
}
func (d *Describe) refresh(ctx context.Context) error {
log.Debug().Msgf("DESCRefresh %v", time.Now())
if !atomic.CompareAndSwapInt32(&d.inUpdate, 0, 1) {
log.Debug().Msgf("Dropping update...")
return nil

View File

@ -1,6 +1,10 @@
package model
import (
"context"
"time"
"github.com/cenkalti/backoff"
"github.com/derailed/tview"
runewidth "github.com/mattn/go-runewidth"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -23,3 +27,10 @@ func FQN(ns, n string) string {
func Truncate(str string, width int) string {
return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis))
}
// NewExpBackOff returns a new exponential backoff timer.
func NewExpBackOff(ctx context.Context, start, max time.Duration) backoff.BackOffContext {
bf := backoff.NewExponentialBackOff()
bf.InitialInterval, bf.MaxElapsedTime = start, max
return backoff.WithContext(bf, ctx)
}

View File

@ -183,6 +183,7 @@ func (l *Log) Filter(q string) {
}
l.filter = q
// BOZO!! No needed since cmdbuff is now throttled!!
if l.filtering {
return
}
@ -352,7 +353,7 @@ func (l *Log) applyFilter(q string) ([][]byte, error) {
func (l *Log) fireLogBuffChanged(lines dao.LogItems) {
ll := make([][]byte, len(lines))
if l.filter == "" {
l.lines.Render(l.logOptions.ShowTimestamp, ll)
lines.Render(l.logOptions.ShowTimestamp, ll)
} else {
ff, err := l.applyFilter(l.filter)
if err != nil {

View File

@ -167,7 +167,7 @@ func (t *Table) updater(ctx context.Context) {
defer log.Debug().Msgf("TABLE-MODEL canceled -- %q", t.gvr)
bf := backoff.NewExponentialBackOff()
bf.InitialInterval, bf.MaxElapsedTime = initRefreshRate, maxRetryInterval
bf.InitialInterval, bf.MaxElapsedTime = initRefreshRate, maxReaderRetryInterval
rate := initRefreshRate
for {
select {

View File

@ -2,14 +2,42 @@ package model
import (
"context"
"time"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/tview"
"github.com/sahilm/fuzzy"
"k8s.io/apimachinery/pkg/runtime"
)
const (
maxReaderRetryInterval = 2 * time.Minute
defaultReaderRefreshRate = 5 * time.Second
)
// ResourceViewerListener listens to viewing resource events.
type ResourceViewerListener interface {
ResourceChanged(lines []string, matches fuzzy.Matches)
ResourceFailed(error)
}
// ToggleOpts represents a collection of viewing options.
type ViewerToggleOpts map[string]bool
// ResourceViewer represents a viewed resource.
type ResourceViewer interface {
GetPath() string
Filter(string)
ClearFilter()
Peek() []string
SetOptions(context.Context, ViewerToggleOpts)
Watch(context.Context) error
AddListener(ResourceViewerListener)
RemoveListener(ResourceViewerListener)
}
// Igniter represents a runnable view.
type Igniter interface {
// Start starts a component.

View File

@ -18,31 +18,25 @@ import (
"github.com/sahilm/fuzzy"
)
const (
maxRetryInterval = 1 * time.Minute
// ManageFieldOpts tracks managed fields.
ManagedFieldsOpts = "ManagedFields"
)
// ManageFieldOpts tracks managed fields.
const ManagedFieldsOpts = "ManagedFields"
// YAML tracks yaml resource representations.
type YAML struct {
gvr client.GVR
inUpdate int32
path string
query string
lines []string
refreshRate time.Duration
listeners []ResourceViewerListener
options ToggleOpts
gvr client.GVR
inUpdate int32
path string
query string
lines []string
listeners []ResourceViewerListener
options ViewerToggleOpts
}
// NewYAML return a new yaml resource model.
func NewYAML(gvr client.GVR, path string) *YAML {
return &YAML{
gvr: gvr,
path: path,
refreshRate: 2 * time.Second,
gvr: gvr,
path: path,
}
}
@ -52,7 +46,7 @@ func (y *YAML) GetPath() string {
}
// SetOptions toggle model options.
func (y *YAML) SetOptions(ctx context.Context, opts ToggleOpts) {
func (y *YAML) SetOptions(ctx context.Context, opts ViewerToggleOpts) {
y.options = opts
if err := y.refresh(ctx); err != nil {
y.fireResourceFailed(err)
@ -133,28 +127,31 @@ func (y *YAML) Watch(ctx context.Context) error {
func (y *YAML) updater(ctx context.Context) {
defer log.Debug().Msgf("YAML canceled -- %q", y.gvr)
bf := backoff.NewExponentialBackOff()
bf.InitialInterval, bf.MaxElapsedTime = initRefreshRate, maxRetryInterval
rate := initRefreshRate
backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval)
delay := defaultReaderRefreshRate
for {
select {
case <-ctx.Done():
return
case <-time.After(rate):
rate = y.refreshRate
err := backoff.Retry(func() error {
return y.refresh(ctx)
}, backoff.WithContext(bf, ctx))
if err != nil {
log.Error().Err(err).Msgf("Retry failed")
case <-time.After(delay):
if err := y.refresh(ctx); err != nil {
log.Error().Err(err).Msgf("YAML Failed")
y.fireResourceFailed(err)
return
delay = backOff.NextBackOff()
if delay == backoff.Stop {
log.Error().Err(err).Msgf("YAML done Retrying bailing out!")
return
}
} else {
backOff.Reset()
delay = defaultReaderRefreshRate
}
}
}
}
func (y *YAML) refresh(ctx context.Context) error {
log.Debug().Msgf("YAMLRefresh %v", time.Now())
if !atomic.CompareAndSwapInt32(&y.inUpdate, 0, 1) {
log.Debug().Msgf("Dropping update...")
return nil

View File

@ -12,6 +12,7 @@ import (
"syscall"
"time"
"github.com/cenkalti/backoff"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
@ -273,19 +274,35 @@ func (a *App) Resume() {
}
func (a *App) clusterUpdater(ctx context.Context) {
a.refreshCluster()
if err := a.refreshCluster(); err != nil {
log.Error().Err(err).Msgf("Cluster updater failed!")
return
}
bf := model.NewExpBackOff(ctx, clusterRefresh, 2*time.Minute)
delay := clusterRefresh
for {
select {
case <-ctx.Done():
log.Debug().Msg("ClusterInfo updater canceled!")
return
case <-time.After(clusterRefresh):
a.refreshCluster()
case <-time.After(delay):
if err := a.refreshCluster(); err != nil {
log.Error().Err(err).Msgf("ClusterUpdater failed")
if delay = bf.NextBackOff(); delay == backoff.Stop {
a.BailOut()
return
}
} else {
bf.Reset()
delay = clusterRefresh
}
}
}
}
func (a *App) refreshCluster() {
func (a *App) refreshCluster() error {
log.Debug().Msgf("Cluster Refresh %v", time.Now())
c := a.Content.Top()
if ok := a.Conn().CheckConnectivity(); ok {
if atomic.LoadInt32(&a.conRetry) > 0 {
@ -305,13 +322,13 @@ func (a *App) refreshCluster() {
count, maxConnRetry := atomic.LoadInt32(&a.conRetry), int32(a.Config.K9s.MaxConnRetry)
if count >= maxConnRetry {
log.Error().Msgf("Conn check failed (%d/%d). Bailing out!", count, maxConnRetry)
ExitStatus = fmt.Sprintf("Lost K8s connection (%d). Bailing out!", count)
a.BailOut()
}
if count > 0 {
log.Warn().Msgf("Conn check failed (%d/%d)", count, maxConnRetry)
a.Status(model.FlashWarn, fmt.Sprintf("Dial K8s failed (%d)", count))
return
a.Status(model.FlashWarn, fmt.Sprintf("Dial K8s Toast [%d/%d]", count, maxConnRetry))
return fmt.Errorf("Conn check failed (%d/%d)", count, maxConnRetry)
}
// Reload alias
@ -323,6 +340,8 @@ func (a *App) refreshCluster() {
// Update cluster info
a.clusterModel.Refresh()
return nil
}
func (a *App) switchNS(ns string) error {

View File

@ -64,7 +64,7 @@ func (c *Cow) talk() {
func cowTalk(says string) string {
buff := make([]string, 0, len(cow)+3)
buff = append(buff, " "+strings.Repeat("─", len(says)+8))
buff = append(buff, fmt.Sprintf("< [red::b]Ruroh? %s [-::-] >", says))
buff = append(buff, fmt.Sprintf("< [red::b]Ruroh? %s[-::-] >", says))
buff = append(buff, " "+strings.Repeat("─", len(says)+8))
spacer := strings.Repeat(" ", len(says)/2-8)
for _, s := range cow {

View File

@ -88,6 +88,7 @@ func (v *LiveView) ResourceFailed(err error) {
// ResourceChanged notifies when the filter changes.
func (v *LiveView) ResourceChanged(lines []string, matches fuzzy.Matches) {
v.app.QueueUpdateDraw(func() {
v.text.SetTextAlign(tview.AlignLeft)
v.maxRegions = len(matches)
ll := make([]string, len(lines))
copy(ll, lines)
@ -96,9 +97,10 @@ func (v *LiveView) ResourceChanged(lines []string, matches fuzzy.Matches) {
ll[m.Index] = line[:loc[0]] + `<<<"search_` + strconv.Itoa(i) + `">>>` + line[loc[0]:loc[1]] + `<<<"">>>` + line[loc[1]:]
}
if v.maxRegions == 0 {
if v.text.GetText(true) == "" {
v.text.ScrollToBeginning()
}
v.text.SetText(colorizeYAML(v.app.Styles.Views().Yaml, strings.Join(ll, "\n")))
v.text.Highlight()
if v.currentRegion < v.maxRegions {

View File

@ -191,7 +191,7 @@ func (l *Log) bindKeys() {
ui.Key5: ui.NewKeyAction("1h", l.sinceCmd(60*60), true),
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", l.filterCmd, false),
tcell.KeyEscape: ui.NewKeyAction("Back", l.resetCmd, false),
tcell.KeyCtrlK: ui.NewKeyAction("Clear", l.clearCmd, true),
ui.KeyShiftC: ui.NewKeyAction("Clear", l.clearCmd, true),
ui.KeyM: ui.NewKeyAction("Mark", l.markCmd, true),
ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.toggleAutoScrollCmd, true),
ui.KeyF: ui.NewKeyAction("Toggle FullScreen", l.toggleFullScreenCmd, true),