Repository: vmware-tanzu/octant
Pull request #663 (2020-02-11 22:07:25)

fix dynamic backoff test
Signed-off-by: Wayne Witzel III <wayne@riotousliving.com>


**What this PR does / why we need it**:
The dynamic backoff test is too close to the real amount of time the backoff will wait. This means it sometimes fails on slower systems. Extend it by 300 to ensure that it passes on GitHub Actions CI runs.


wwitzel3: Agreed. I guess we don't actually need to test the After call, we can make an assumption that respects what we pass it.

I'll extract the timing function and actually check the time.Duration for incrementing values.
wwitzel3: I admit the PR was made in frustration. I'll fix it up.

Patch:
diff --git a/changelogs/unreleased/643-GuessWhoSamFoo b/changelogs/unreleased/643-GuessWhoSamFoo
deleted file mode 100644
index 9a865c7a..00000000
--- a/changelogs/unreleased/643-GuessWhoSamFoo
+++ /dev/null
@@ -1,1 +0,0 @@
-Remove boolean value from object store Get
diff --git a/cmd/octant-sample-plugin/main.go b/cmd/octant-sample-plugin/main.go
index a269ca3e..383526a8 100644
--- a/cmd/octant-sample-plugin/main.go
+++ b/cmd/octant-sample-plugin/main.go
@@ -101,13 +101,13 @@ func handlePrint(request *service.PrintRequest) (plugin.PrintResponse, error) {
 	if err != nil {
 		return plugin.PrintResponse{}, err
 	}
-	u, err := request.DashboardClient.Get(request.Context(), key)
+	u, found, err := request.DashboardClient.Get(request.Context(), key)
 	if err != nil {
 		return plugin.PrintResponse{}, err
 	}
 
 	// The plugin can check if the object it requested exists.
-	if u == nil {
+	if !found {
 		return plugin.PrintResponse{}, errors.New("object doesn't exist")
 	}
 
diff --git a/internal/api/content_manager.go b/internal/api/content_manager.go
index dcc667b8..4b99fc59 100644
--- a/internal/api/content_manager.go
+++ b/internal/api/content_manager.go
@@ -119,9 +119,6 @@ func (cm *ContentManager) runUpdate(state octant.State, s OctantClient) PollerFu
 					return false
 				}
 			}
-			if ctx.Err() == context.Canceled {
-				return false
-			}
 			cm.logger.
 				WithErr(err).
 				With("content-path", contentPath).
diff --git a/internal/describer/crd.go b/internal/describer/crd.go
index 54c80a5f..26317e40 100644
--- a/internal/describer/crd.go
+++ b/internal/describer/crd.go
@@ -96,12 +96,12 @@ func (c *crd) Describe(ctx context.Context, namespace string, options Options) (
 		Name:       options.Fields["name"],
 	}
 
-	object, err := objectStore.Get(ctx, key)
+	object, found, err := objectStore.Get(ctx, key)
 	if err != nil {
 		return component.EmptyContentResponse, err
 	}
 
-	if object == nil {
+	if !found {
 		return component.EmptyContentResponse, err
 	}
 
diff --git a/internal/describer/crd_section.go b/internal/describer/crd_section.go
index 8453260c..ea99ef26 100644
--- a/internal/describer/crd_section.go
+++ b/internal/describer/crd_section.go
@@ -69,7 +69,7 @@ func (csd *CRDSection) Describe(ctx context.Context, namespace string, options O
 		case *crdList:
 			key := store.KeyFromGroupVersionKind(gvk.CustomResourceDefinition)
 			key.Name = d.name
-			crd, err := options.ObjectStore().Get(ctx, key)
+			crd, _, err := options.ObjectStore().Get(ctx, key)
 			if err != nil {
 				return component.EmptyContentResponse, err
 			}
diff --git a/internal/describer/customresource.go b/internal/describer/customresource.go
index 724fb935..1b51c30e 100644
--- a/internal/describer/customresource.go
+++ b/internal/describer/customresource.go
@@ -22,7 +22,7 @@ func CustomResourceDefinition(ctx context.Context, name string, o store.Store) (
 	key := store.KeyFromGroupVersionKind(gvk.CustomResourceDefinition)
 	key.Name = name
 
-	crd, err := o.Get(ctx, key)
+	crd, _, err := o.Get(ctx, key)
 	if err != nil {
 		return nil, fmt.Errorf("get %s: %w", key, err)
 	}
diff --git a/internal/describer/describer.go b/internal/describer/describer.go
index bdb74b23..e21e2ab4 100644
--- a/internal/describer/describer.go
+++ b/internal/describer/describer.go
@@ -53,7 +53,7 @@ func LoadObject(ctx context.Context, objectStore store.Store, errorStore oerrors
 		objectStoreKey.Name = name
 	}
 
-	object, err := objectStore.Get(ctx, objectStoreKey)
+	object, found, err := objectStore.Get(ctx, objectStoreKey)
 	if err != nil {
 		var ae *oerrors.AccessError
 		if errors.As(err, &ae) {
@@ -68,7 +68,7 @@ func LoadObject(ctx context.Context, objectStore store.Store, errorStore oerrors
 		}
 		return nil, err
 	}
-	if object == nil {
+	if !found {
 		return nil, errors.New("object was not found")
 	}
 
diff --git a/internal/objectstatus/ingress.go b/internal/objectstatus/ingress.go
index e035cd0b..58316d2d 100644
--- a/internal/objectstatus/ingress.go
+++ b/internal/objectstatus/ingress.go
@@ -135,14 +135,14 @@ func (is *ingressStatus) run(ctx context.Context) (ObjectStatus, error) {
 			Name:       tls.SecretName,
 		}
 
-		u, err := is.objectstore.Get(ctx, key)
+		_, found, err := is.objectstore.Get(ctx, key)
 		if err != nil {
 			status.SetError()
 			status.AddDetailf("Unable to load Secret %q: %s", tls.SecretName, err)
 			continue
 		}
 
-		if u == nil {
+		if !found {
 			status.SetError()
 			status.AddDetailf("Secret %q does not exist", tls.SecretName)
 		}
diff --git a/internal/objectstore/dynamic_cache.go b/internal/objectstore/dynamic_cache.go
index 06797c99..a26e752f 100644
--- a/internal/objectstore/dynamic_cache.go
+++ b/internal/objectstore/dynamic_cache.go
@@ -178,16 +178,19 @@ func (dc *DynamicCache) checkKeySynced(ctx context.Context, informer informers.G
 	go dc.syncTimeoutFunc(ctx, key, done)
 }
 
-func (dc *DynamicCache) backoff(ctx context.Context, key store.Key) {
+// backoff returns the Duration of the next backoff. A duration of 0 is returned if
+// backoff is already waiting.
+func (dc *DynamicCache) backoff(ctx context.Context, key store.Key) time.Duration {
 	newEntry := newBackoffEntry(key, defaultBackoff)
 	actual, _ := dc.backoffMap.LoadOrStore(key.GroupVersionKind(), newEntry)
 	entry := actual.(backoffer)
 	if entry.isWaiting() {
-		return
+		return 0
 	}
 
 	entry.setWaiting(true)
 	dc.backoffMap.Store(key.GroupVersionKind(), entry)
+	t := entry.wait()
 
 	go func() {
 		logger := log.From(ctx)
@@ -199,10 +202,11 @@ func (dc *DynamicCache) backoff(ctx context.Context, key store.Key) {
 		if unwatchErr != nil {
 			logger.Errorf("unwatch: %w", unwatchErr)
 		}
-		t := entry.wait()
 		logger.Infof("backing off for %s (%s)", key.GroupVersionKind(), t)
 		<-time.After(t)
 	}()
+
+	return t
 }
 
 func (dc *DynamicCache) isBackingOff(ctx context.Context, key store.Key) bool {
@@ -315,22 +319,22 @@ type getter interface {
 }
 
 // Get retrieves a single object.
-func (dc *DynamicCache) Get(ctx context.Context, key store.Key) (*unstructured.Unstructured, error) {
+func (dc *DynamicCache) Get(ctx context.Context, key store.Key) (*unstructured.Unstructured, bool, error) {
 	ctx, span := trace.StartSpan(ctx, "dynamicCacheGet")
 	defer span.End()
 
 	if dc.isBackingOff(ctx, key) {
-		return &unstructured.Unstructured{}, nil
+		return &unstructured.Unstructured{}, false, nil
 	}
 
 	if err := dc.access.HasAccess(ctx, key, "get"); err != nil {
 		if meta.IsNoMatchError(err) {
-			return &unstructured.Unstructured{}, nil
+			return &unstructured.Unstructured{}, false, nil
 		}
 		if !dc.isBackingOff(ctx, key) {
 			dc.backoff(ctx, key)
 		}
-		return nil, fmt.Errorf("check access for get to %s: %w", key, err)
+		return nil, false, fmt.Errorf("check access for get to %s: %w", key, err)
 	}
 
 	span.Annotate([]trace.Attribute{
@@ -341,15 +345,16 @@ func (dc *DynamicCache) Get(ctx context.Context, key store.Key) (*unstructured.U
 	}, "get key")
 
 	object, err := dc.getFromInformer(ctx, key)
+
 	if err != nil {
 		if kerrors.IsNotFound(err) {
-			return nil, nil
+			return nil, false, nil
 		}
 
-		return nil, err
+		return nil, false, err
 	}
 
-	return object, nil
+	return object, true, nil
 
 }
 
@@ -528,12 +533,12 @@ func (dc *DynamicCache) Update(ctx context.Context, key store.Key, updater func(
 	}
 
 	err := kretry.RetryOnConflict(kretry.DefaultRetry, func() error {
-		object, err := dc.Get(ctx, key)
+		object, found, err := dc.Get(ctx, key)
 		if err != nil {
 			return err
 		}
 
-		if object == nil {
+		if !found {
 			return errors.New("object not found")
 		}
 
diff --git a/internal/octant/workload.go b/internal/octant/workload.go
index 4ea628a9..53487abd 100644
--- a/internal/octant/workload.go
+++ b/internal/octant/workload.go
@@ -179,12 +179,12 @@ func objectOwner(ctx context.Context, objectStore store.Store, object *unstructu
 		Name:       ownerReference.Name,
 	}
 
-	owner, err := objectStore.Get(ctx, ownerRefKey)
+	owner, found, err := objectStore.Get(ctx, ownerRefKey)
 	if err != nil {
 		return nil, fmt.Errorf("find object %s: %w", ownerRefKey, err)
 	}
 
-	if owner == nil {
+	if !found {
 		return object, nil
 	}
 
diff --git a/internal/printer/container.go b/internal/printer/container.go
index d8a1d42f..f4944406 100644
--- a/internal/printer/container.go
+++ b/internal/printer/container.go
@@ -378,12 +378,12 @@ func describeEnvRows(ctx context.Context, namespace string, vars []corev1.EnvVar
 				Kind:       "ConfigMap",
 			}
 
-			u, err := objectStore.Get(ctx, key)
+			u, found, err := objectStore.Get(ctx, key)
 			if err != nil {
 				return nil, err
 			}
 
-			if u != nil {
+			if found {
 				configMap := &corev1.ConfigMap{}
 				if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, configMap); err != nil {
 					return nil, err
diff --git a/internal/printer/horizontalpodautoscaler.go b/internal/printer/horizontalpodautoscaler.go
index fa0ceee6..9f4098ab 100644
--- a/internal/printer/horizontalpodautoscaler.go
+++ b/internal/printer/horizontalpodautoscaler.go
@@ -443,8 +443,8 @@ func forScaleTarget(ctx context.Context, object runtime.Object, scaleTarget *aut
 	}
 
 	objectStore := options.DashConfig.ObjectStore()
-	u, err := objectStore.Get(ctx, key)
-	if err != nil || u == nil {
+	_, found, err := objectStore.Get(ctx, key)
+	if err != nil || !found {
 		return component.NewLink("", "none", ""), nil
 	}
 
diff --git a/internal/printer/namespace.go b/internal/printer/namespace.go
index 6d47d10e..6b99476f 100644
--- a/internal/printer/namespace.go
+++ b/internal/printer/namespace.go
@@ -328,6 +328,7 @@ func printNamespaceResourceLimits(limits *corev1.LimitRangeList) (*component.Tab
 
 	for i := range limits.Items {
 		limit := limits.Items[i]
+		fmt.Print(fmt.Sprintf("%+v", limit))
 		for _, item := range limit.Spec.Limits {
 			sortKey, row, created := createResourceLimitCPURow(item)
 			if created {
diff --git a/internal/printer/service.go b/internal/printer/service.go
index 42bf28bc..ae43f44f 100644
--- a/internal/printer/service.go
+++ b/internal/printer/service.go
@@ -311,11 +311,11 @@ func createServiceEndpointsView(ctx context.Context, service *corev1.Service, op
 	cols := component.NewTableCols("Target", "IP", "Node Name")
 	table := component.NewTable("Endpoints", "There are no endpoints!", cols)
 
-	object, err := o.Get(ctx, key)
+	object, found, err := o.Get(ctx, key)
 	if err != nil {
 		return nil, errors.Wrapf(err, "get endpoints for service %s", service.Name)
 	}
-	if object == nil {
+	if !found {
 		return table, nil
 	}
 
diff --git a/internal/printer/serviceaccount.go b/internal/printer/serviceaccount.go
index 344f75dc..20f29844 100644
--- a/internal/printer/serviceaccount.go
+++ b/internal/printer/serviceaccount.go
@@ -280,12 +280,12 @@ func (s *ServiceAccountPolicyRules) Create() (*component.Table, error) {
 		}
 		switch kind := roleRef.Kind; kind {
 		case "ClusterRole":
-			object, err := s.objectStore.Get(s.context, key)
+			object, found, err := s.objectStore.Get(s.context, key)
 			if err != nil {
 				return nil, err
 			}
 
-			if object == nil {
+			if !found {
 				return nil, errors.Errorf("unable to find %s", key)
 			}
 
@@ -299,12 +299,12 @@ func (s *ServiceAccountPolicyRules) Create() (*component.Table, error) {
 		case "Role":
 			key.Namespace = s.namespace
 
-			object, err := s.objectStore.Get(s.context, key)
+			object, found, err := s.objectStore.Get(s.context, key)
 			if err != nil {
 				return nil, err
 			}
 
-			if object == nil {
+			if !found {
 				return nil, errors.Errorf("unable to find %s", key)
 			}
 
diff --git a/internal/queryer/queryer.go b/internal/queryer/queryer.go
index c415b74b..541079ea 100644
--- a/internal/queryer/queryer.go
+++ b/internal/queryer/queryer.go
@@ -465,12 +465,12 @@ func (osq *ObjectStoreQueryer) OwnerReference(ctx context.Context, object *unstr
 			return true, object, nil
 		}
 
-		owner, err := osq.objectStore.Get(ctx, key)
+		owner, found, err := osq.objectStore.Get(ctx, key)
 		if err != nil {
 			return false, nil, errors.Wrap(err, "get owner from store")
 		}
 
-		if owner == nil {
+		if !found {
 			return false, nil, errors.Errorf("owner %s not found", key)
 		}
 
@@ -494,12 +494,12 @@ func (osq *ObjectStoreQueryer) ScaleTarget(ctx context.Context, hpa *autoscaling
 		Name:       hpa.Spec.ScaleTargetRef.Name,
 	}
 
-	u, err := osq.objectStore.Get(ctx, key)
+	u, found, err := osq.objectStore.Get(ctx, key)
 	if err != nil {
 		return nil, errors.WithMessagef(err, "retrieve scale target %q from namespace %q", key.Name, key.Namespace)
 	}
 
-	if u != nil {
+	if found {
 		switch key.Kind {
 		case "Deployment":
 			deployment := &appsv1.Deployment{}
@@ -619,12 +619,12 @@ func (osq *ObjectStoreQueryer) ServicesForIngress(ctx context.Context, ingress *
 			Kind:       "Service",
 			Name:       backend.ServiceName,
 		}
-		u, err := osq.objectStore.Get(ctx, key)
+		u, found, err := osq.objectStore.Get(ctx, key)
 		if err != nil {
 			return nil, errors.Wrapf(err, "retrieving service backend: %v", backend)
 		}
 
-		if u == nil {
+		if !found {
 			continue
 		}
 
@@ -690,13 +690,13 @@ func (osq *ObjectStoreQueryer) ServiceAccountForPod(ctx context.Context, pod *co
 		Name:       pod.Spec.ServiceAccountName,
 	}
 
-	u, err := osq.objectStore.Get(ctx, key)
+	u, found, err := osq.objectStore.Get(ctx, key)
 	if err != nil {
 		return nil, errors.WithMessagef(err, "retrieve service account %q from namespace %q",
 			key.Name, key.Namespace)
 	}
 
-	if u == nil {
+	if !found {
 		return nil, errors.Errorf("service account %q from namespace %q does not exist",
 			key.Name, key.Namespace)
 	}
diff --git a/internal/terminal/manager.go b/internal/terminal/manager.go
index 82cd4a7f..6bd7b592 100644
--- a/internal/terminal/manager.go
+++ b/internal/terminal/manager.go
@@ -96,11 +96,11 @@ func (tm *manager) Create(ctx context.Context, logger log.Logger, key store.Key,
 	t := NewTerminalInstance(ctx, logger, key, container, command, tm.chanInstance)
 	tm.instances.Store(t.ID(), t)
 
-	pod, err := tm.objectStore.Get(ctx, key)
+	pod, ok, err := tm.objectStore.Get(ctx, key)
 	if err != nil {
 		return nil, err
 	}
-	if pod == nil {
+	if !ok {
 		return nil, errors.New("pod not found")
 	}
 
diff --git a/pkg/plugin/api/client.go b/pkg/plugin/api/client.go
index 6d770472..0d3aa22e 100644
--- a/pkg/plugin/api/client.go
+++ b/pkg/plugin/api/client.go
@@ -100,25 +100,25 @@ func (c *Client) List(ctx context.Context, key store.Key) (*unstructured.Unstruc
 }
 
 // Get retrieves an object from the dashboard's objectStore.
-func (c *Client) Get(ctx context.Context, key store.Key) (*unstructured.Unstructured, error) {
+func (c *Client) Get(ctx context.Context, key store.Key) (*unstructured.Unstructured, bool, error) {
 	client := c.DashboardConnection.Client()
 
 	keyRequest, err := convertFromKey(key)
 	if err != nil {
-		return nil, err
+		return nil, false, err
 	}
 
 	resp, err := client.Get(ctx, keyRequest)
 	if err != nil {
-		return nil, err
+		return nil, false, err
 	}
 
-	object, err := convertToObject(resp.Object)
+	object, found, err := convertToObject(resp.Object)
 	if err != nil {
-		return nil, err
+		return nil, false, err
 	}
 
-	return object, nil
+	return object, found, nil
 }
 
 // Update updates an object in the store.
diff --git a/pkg/plugin/api/conversion.go b/pkg/plugin/api/conversion.go
index 60bfc906..ab6cdce1 100644
--- a/pkg/plugin/api/conversion.go
+++ b/pkg/plugin/api/conversion.go
@@ -76,11 +76,11 @@ func convertToObjects(in [][]byte) (*unstructured.UnstructuredList, error) {
 	list := &unstructured.UnstructuredList{}
 
 	for _, data := range in {
-		object, err := convertToObject(data)
+		object, found, err := convertToObject(data)
 		if err != nil {
 			return nil, err
 		}
-		if object == nil {
+		if !found {
 			continue
 		}
 		list.Items = append(list.Items, *object)
@@ -89,22 +89,18 @@ func convertToObjects(in [][]byte) (*unstructured.UnstructuredList, error) {
 	return list, nil
 }
 
-func convertToObject(in []byte) (*unstructured.Unstructured, error) {
+func convertToObject(in []byte) (*unstructured.Unstructured, bool, error) {
 	if in == nil {
-		return nil, nil
+		return nil, false, nil
 	}
 
 	object := unstructured.Unstructured{}
 	err := json.Unmarshal(in, &object)
 	if err != nil {
-		return nil, err
+		return nil, false, err
 	}
 
-	if object.Object == nil {
-		return nil, nil
-	}
-
-	return &object, nil
+	return &object, true, nil
 }
 
 func convertToPortForwardRequest(in *proto.PortForwardRequest) (*PortForwardRequest, error) {
diff --git a/pkg/plugin/api/fake/mock_dash_service.go b/pkg/plugin/api/fake/mock_dash_service.go
index 25f7d121..67b7e51d 100644
--- a/pkg/plugin/api/fake/mock_dash_service.go
+++ b/pkg/plugin/api/fake/mock_dash_service.go
@@ -63,12 +63,13 @@ func (mr *MockServiceMockRecorder) ForceFrontendUpdate(arg0 interface{}) *gomock
 }
 
 // Get mocks base method
-func (m *MockService) Get(arg0 context.Context, arg1 store.Key) (*unstructured.Unstructured, error) {
+func (m *MockService) Get(arg0 context.Context, arg1 store.Key) (*unstructured.Unstructured, bool, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Get", arg0, arg1)
 	ret0, _ := ret[0].(*unstructured.Unstructured)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
+	ret1, _ := ret[1].(bool)
+	ret2, _ := ret[2].(error)
+	return ret0, ret1, ret2
 }
 
 // Get indicates an expected call of Get
diff --git a/pkg/plugin/api/server.go b/pkg/plugin/api/server.go
index c43a0b02..ebfe546a 100644
--- a/pkg/plugin/api/server.go
+++ b/pkg/plugin/api/server.go
@@ -40,7 +40,7 @@ type NamespacesResponse struct {
 // Service is the dashboard service.
 type Service interface {
 	List(ctx context.Context, key store.Key) (*unstructured.UnstructuredList, error)
-	Get(ctx context.Context, key store.Key) (*unstructured.Unstructured, error)
+	Get(ctx context.Context, key store.Key) (*unstructured.Unstructured, bool, error)
 	PortForward(ctx context.Context, req PortForwardRequest) (PortForwardResponse, error)
 	CancelPortForward(ctx context.Context, id string)
 	ListNamespaces(ctx context.Context) (NamespacesResponse, error)
@@ -85,7 +85,7 @@ func (s *GRPCService) List(ctx context.Context, key store.Key) (*unstructured.Un
 }
 
 // Get retrieves an object.
-func (s *GRPCService) Get(ctx context.Context, key store.Key) (*unstructured.Unstructured, error) {
+func (s *GRPCService) Get(ctx context.Context, key store.Key) (*unstructured.Unstructured, bool, error) {
 	return s.ObjectStore.Get(ctx, key)
 }
 
@@ -180,14 +180,14 @@ func (c *grpcServer) Get(ctx context.Context, in *proto.KeyRequest) (*proto.GetR
 		return nil, err
 	}
 
-	object, err := c.service.Get(ctx, key)
+	object, found, err := c.service.Get(ctx, key)
 	if err != nil {
 		return nil, err
 	}
 
 	var out *proto.GetResponse
 
-	if object != nil {
+	if found {
 		encodedObject, err := convertFromObject(object)
 		if err != nil {
 			return nil, err
@@ -196,8 +196,7 @@ func (c *grpcServer) Get(ctx context.Context, in *proto.KeyRequest) (*proto.GetR
 		out = &proto.GetResponse{
 			Object: encodedObject,
 		}
-	} else {
-		return &proto.GetResponse{}, nil
+
 	}
 
 	return out, nil
@@ -205,12 +204,12 @@ func (c *grpcServer) Get(ctx context.Context, in *proto.KeyRequest) (*proto.GetR
 
 // Update updates an object.
 func (c *grpcServer) Update(ctx context.Context, in *proto.UpdateRequest) (*proto.UpdateResponse, error) {
-	object, err := convertToObject(in.Object)
+	object, found, err := convertToObject(in.Object)
 	if err != nil {
 		return nil, err
 	}
 
-	if object == nil {
+	if !found {
 		return &proto.UpdateResponse{}, errors.Errorf("can't update an object that doesn't exist")
 	}
 
diff --git a/pkg/plugin/service/dashboard.go b/pkg/plugin/service/dashboard.go
index bdc27f7f..7286c5fb 100644
--- a/pkg/plugin/service/dashboard.go
+++ b/pkg/plugin/service/dashboard.go
@@ -15,7 +15,7 @@ import (
 type Dashboard interface {
 	Close() error
 	List(ctx context.Context, key store.Key) (*unstructured.UnstructuredList, error)
-	Get(ctx context.Context, key store.Key) (*unstructured.Unstructured, error)
+	Get(ctx context.Context, key store.Key) (*unstructured.Unstructured, bool, error)
 	Update(ctx context.Context, object *unstructured.Unstructured) error
 	PortForward(ctx context.Context, req api.PortForwardRequest) (api.PortForwardResponse, error)
 	CancelPortForward(ctx context.Context, id string)
diff --git a/pkg/plugin/service/fake/mock_dashboard.go b/pkg/plugin/service/fake/mock_dashboard.go
index c0c193b7..3b96aac2 100644
--- a/pkg/plugin/service/fake/mock_dashboard.go
+++ b/pkg/plugin/service/fake/mock_dashboard.go
@@ -77,12 +77,13 @@ func (mr *MockDashboardMockRecorder) ForceFrontendUpdate(arg0 interface{}) *gomo
 }
 
 // Get mocks base method
-func (m *MockDashboard) Get(arg0 context.Context, arg1 store.Key) (*unstructured.Unstructured, error) {
+func (m *MockDashboard) Get(arg0 context.Context, arg1 store.Key) (*unstructured.Unstructured, bool, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Get", arg0, arg1)
 	ret0, _ := ret[0].(*unstructured.Unstructured)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
+	ret1, _ := ret[1].(bool)
+	ret2, _ := ret[2].(error)
+	return ret0, ret1, ret2
 }
 
 // Get indicates an expected call of Get
diff --git a/pkg/store/fake/mock_store.go b/pkg/store/fake/mock_store.go
index 319b56eb..eb4c0ff4 100644
--- a/pkg/store/fake/mock_store.go
+++ b/pkg/store/fake/mock_store.go
@@ -53,12 +53,13 @@ func (mr *MockStoreMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call {
 }
 
 // Get mocks base method
-func (m *MockStore) Get(arg0 context.Context, arg1 store.Key) (*unstructured.Unstructured, error) {
+func (m *MockStore) Get(arg0 context.Context, arg1 store.Key) (*unstructured.Unstructured, bool, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Get", arg0, arg1)
 	ret0, _ := ret[0].(*unstructured.Unstructured)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
+	ret1, _ := ret[1].(bool)
+	ret2, _ := ret[2].(error)
+	return ret0, ret1, ret2
 }
 
 // Get indicates an expected call of Get
diff --git a/pkg/store/get_as.go b/pkg/store/get_as.go
index da65d0bb..657174dc 100644
--- a/pkg/store/get_as.go
+++ b/pkg/store/get_as.go
@@ -19,12 +19,12 @@ import (
 // GetAs gets an object from the object store by key. If the object is not found,
 // return false and a nil error.
 func GetAs(ctx context.Context, o Store, key Key, as interface{}) (bool, error) {
-	u, err := o.Get(ctx, key)
+	u, found, err := o.Get(ctx, key)
 	if err != nil {
 		return false, errors.Wrap(err, "get object from object store")
 	}
 
-	if u == nil {
+	if !found {
 		return false, nil
 	}
 
diff --git a/pkg/store/store.go b/pkg/store/store.go
index 1eb4fa96..0a9de0db 100644
--- a/pkg/store/store.go
+++ b/pkg/store/store.go
@@ -29,7 +29,7 @@ type UpdateFn func(store Store)
 // Store stores Kubernetes objects.
 type Store interface {
 	List(ctx context.Context, key Key) (list *unstructured.UnstructuredList, loading bool, err error)
-	Get(ctx context.Context, key Key) (object *unstructured.Unstructured, err error)
+	Get(ctx context.Context, key Key) (object *unstructured.Unstructured, found bool, err error)
 	Delete(ctx context.Context, key Key) error
 	Watch(ctx context.Context, key Key, handler cache.ResourceEventHandler) error
 	Unwatch(ctx context.Context, groupVersionKinds ...schema.GroupVersionKind) error
diff --git a/web/src/app/modules/shared/components/smart/context-selector/context-selector.component.scss b/web/src/app/modules/shared/components/smart/context-selector/context-selector.component.scss
index f2b50250..49cbd19d 100644
--- a/web/src/app/modules/shared/components/smart/context-selector/context-selector.component.scss
+++ b/web/src/app/modules/shared/components/smart/context-selector/context-selector.component.scss
@@ -4,8 +4,13 @@
 
 .dropdown > .dropdown-toggle {
   line-height: 2.5rem;
-  color: white;
+  color: #fafafa;
+  opacity: 0.65;
   outline: none;
+
+  &:hover {
+    opacity: 1;
+  }
 }
 
 .dropdown {
diff --git a/web/src/app/modules/sugarloaf/components/smart/container/container.component.scss b/web/src/app/modules/sugarloaf/components/smart/container/container.component.scss
index 64fafa39..17ce4fa3 100644
--- a/web/src/app/modules/sugarloaf/components/smart/container/container.component.scss
+++ b/web/src/app/modules/sugarloaf/components/smart/container/container.component.scss
@@ -8,18 +8,13 @@ $navigation-width: 13rem;
     // Navigation's color variables for Light Theme.
     :host-context(body) {
       --navigation-border-color: rgb(215, 215, 215);
-      --navigation-background-color: #0079AD;
     }
     
     // Navigation's color variables for Dark Theme.
     :host-context(body.dark) {
       --navigation-border-color: #0e161b;
-      --navigation-background-color: #0e161b;
     }
-
   .header {
-    background: var(--navigation-background-color);
-
     .branding {
       min-width: $navigation-width;
     }
diff --git a/web/src/app/modules/sugarloaf/components/smart/input-filter/input-filter.component.scss b/web/src/app/modules/sugarloaf/components/smart/input-filter/input-filter.component.scss
index 4eb2bf89..34f53e93 100644
--- a/web/src/app/modules/sugarloaf/components/smart/input-filter/input-filter.component.scss
+++ b/web/src/app/modules/sugarloaf/components/smart/input-filter/input-filter.component.scss
@@ -11,7 +11,6 @@
     --tags-border-color: #ccc;
     --tags-font-color: black;
     --filterTag-link-color: #0079b8;
-    --input-border-color: white;
   }
   
   // Dropdown's color variables for Dark Theme.
@@ -20,7 +19,6 @@
     --tags-border-color: black;
     --tags-font-color: #adbbc4;
     --filterTag-link-color: #49afd9;
-    --input-border-color: #999;
   }
 
   &-control {
@@ -37,16 +35,6 @@
 
       & .clr-control-container {
         width: 100%;
-
-        .clr-input-wrapper .clr-input {
-          border-bottom-color: var(--input-border-color);
-          color: white;
-
-          &::placeholder {
-            color: white;
-            opacity: 1; // Fix for Firefox's placeholder opacity.
-          }
-        }
       }
     }
 
diff --git a/web/src/app/modules/sugarloaf/components/smart/namespace/namespace.component.scss b/web/src/app/modules/sugarloaf/components/smart/namespace/namespace.component.scss
index 257b7cb6..345a7ce4 100644
--- a/web/src/app/modules/sugarloaf/components/smart/namespace/namespace.component.scss
+++ b/web/src/app/modules/sugarloaf/components/smart/namespace/namespace.component.scss
@@ -5,7 +5,7 @@
 .app-namespace-selector {
   // Dropdown's color variables for Light Theme.
   :host-context(body) {
-    --dropdownLink-color:  white;
+    --dropdownLink-color:  #0079b8;
     --dropdownPanel-border-color: #ccc;
     --dropdownOptionSelected-bg-color: #dae4ea;
     --dropdownOption-bg-color: white;


Test patch:
diff --git a/internal/objectstatus/ingress_test.go b/internal/objectstatus/ingress_test.go
index cfc5849e..39e55e0f 100644
--- a/internal/objectstatus/ingress_test.go
+++ b/internal/objectstatus/ingress_test.go
@@ -44,7 +44,7 @@ func Test_runIngressStatus(t *testing.T) {
 			name: "no matching backends",
 			init: func(t *testing.T, o *storefake.MockStore) runtime.Object {
 				key := store.Key{Namespace: "default", APIVersion: "v1", Kind: "Service", Name: "no-such-service"}
-				o.EXPECT().Get(gomock.Any(), gomock.Eq(key)).Return(nil, nil)
+				o.EXPECT().Get(gomock.Any(), gomock.Eq(key)).Return(nil, false, nil)
 
 				objectFile := "ingress_no_matching_backend.yaml"
 				return testutil.LoadObjectFromFile(t, objectFile)
@@ -88,7 +88,7 @@ func Test_runIngressStatus(t *testing.T) {
 				mockServiceInCache(t, o, "default", "my-service", "service_my-service.yaml")
 
 				key := store.Key{Namespace: "default", APIVersion: "v1", Kind: "Secret", Name: "no-such-secret"}
-				o.EXPECT().Get(gomock.Any(), gomock.Eq(key)).Return(nil, nil)
+				o.EXPECT().Get(gomock.Any(), gomock.Eq(key)).Return(nil, false, nil)
 
 				objectFile := "ingress_ingress-bad-tls-host.yaml"
 				return testutil.LoadObjectFromFile(t, objectFile)
@@ -146,7 +146,7 @@ func mockSecretInCache(t *testing.T, o *storefake.MockStore, namespace, name, fi
 		Name:       name,
 	}
 
-	o.EXPECT().Get(gomock.Any(), gomock.Eq(key)).Return(testutil.ToUnstructured(t, secret), nil)
+	o.EXPECT().Get(gomock.Any(), gomock.Eq(key)).Return(testutil.ToUnstructured(t, secret), true, nil)
 
 	return secret
 }
@@ -160,7 +160,7 @@ func mockServiceInCache(t *testing.T, o *storefake.MockStore, namespace, name, f
 		Name:       name,
 	}
 
-	o.EXPECT().Get(gomock.Any(), gomock.Eq(key)).Return(testutil.ToUnstructured(t, secret), nil)
+	o.EXPECT().Get(gomock.Any(), gomock.Eq(key)).Return(testutil.ToUnstructured(t, secret), true, nil)
 
 	return secret
 }
diff --git a/internal/objectstatus/service_test.go b/internal/objectstatus/service_test.go
index 8c0b6fa2..918da48d 100644
--- a/internal/objectstatus/service_test.go
+++ b/internal/objectstatus/service_test.go
@@ -41,7 +41,7 @@ func Test_service(t *testing.T) {
 				endpoints := testutil.LoadObjectFromFile(t, "endpoints_ok.yaml")
 
 				o.EXPECT().Get(gomock.Any(), gomock.Eq(key)).
-					Return(testutil.ToUnstructured(t, endpoints), nil)
+					Return(testutil.ToUnstructured(t, endpoints), true, nil)
 
 				objectFile := "service_ok.yaml"
 				return testutil.LoadObjectFromFile(t, objectFile)
@@ -65,7 +65,7 @@ func Test_service(t *testing.T) {
 				endpoints := testutil.LoadObjectFromFile(t, "endpoints_no_subsets.yaml")
 
 				o.EXPECT().Get(gomock.Any(), gomock.Eq(key)).
-					Return(testutil.ToUnstructured(t, endpoints), nil)
+					Return(testutil.ToUnstructured(t, endpoints), true, nil)
 
 				objectFile := "service_ok.yaml"
 				return testutil.LoadObjectFromFile(t, objectFile)
diff --git a/internal/objectstore/backoff_test.go b/internal/objectstore/backoff_test.go
index ddd40707..efefbf2a 100644
--- a/internal/objectstore/backoff_test.go
+++ b/internal/objectstore/backoff_test.go
@@ -21,6 +21,10 @@ func Test_wait(t *testing.T) {
 	key := store.Key{APIVersion: "apiVersion"}
 	entry := newBackoffEntry(key, defaultBackoff)
 	d := entry.wait()
+
 	assert.True(t, d > time.Second)
-	assert.True(t, d < (d + (time.Millisecond * 500)))
+	d = entry.wait()
+	assert.True(t, d > (time.Second * 2))
+	d = entry.wait()
+	assert.True(t, d > (time.Second * 4))
 }
diff --git a/internal/objectstore/dynamic_cache_test.go b/internal/objectstore/dynamic_cache_test.go
index 0f57e7c6..89e21f83 100644
--- a/internal/objectstore/dynamic_cache_test.go
+++ b/internal/objectstore/dynamic_cache_test.go
@@ -19,9 +19,8 @@ func TestDynamicCache_backoff(t *testing.T) {
 	ctx := context.TODO()
 	key := store.Key{APIVersion: gvk.Pod.Version, Kind: gvk.Pod.Kind}
 
-	d.backoff(ctx, key)
+	tD := d.backoff(ctx, key)
 	require.True(t, d.isBackingOff(ctx, key))
-	// Default back starts at 1 second + some jitter so we wait 1.2s.
-	<-time.After(time.Millisecond * 1200)
+	<-time.After(tD + (time.Millisecond * 250))
 	assert.False(t, d.isBackingOff(ctx, key))
 }
diff --git a/internal/octant/workload_test.go b/internal/octant/workload_test.go
index 1e41dea6..de30273d 100644
--- a/internal/octant/workload_test.go
+++ b/internal/octant/workload_test.go
@@ -95,7 +95,7 @@ func TestClusterWorkloadLoader(t *testing.T) {
 			Kind:       "ReplicaSet",
 			Name:       "rs-1",
 		}).
-		Return(replicaSet, nil).
+		Return(replicaSet, true, nil).
 		AnyTimes()
 
 	noOwnerObjectStore := storeFake.NewMockStore(controller)
@@ -114,7 +114,7 @@ func TestClusterWorkloadLoader(t *testing.T) {
 			Kind:       "ReplicaSet",
 			Name:       "missing",
 		}).
-		Return(nil, nil).
+		Return(nil, false, nil).
 		AnyTimes()
 
 	cases := []struct {
diff --git a/internal/printer/container_test.go b/internal/printer/container_test.go
index 5e058a60..b3f4b6cd 100644
--- a/internal/printer/container_test.go
+++ b/internal/printer/container_test.go
@@ -390,7 +390,7 @@ func Test_ContainerConfiguration(t *testing.T) {
 					Name:       configMap.Name,
 					Namespace:  configMap.Namespace,
 				}
-				tpo.objectStore.EXPECT().Get(ctx, gomock.Eq(key)).Return(testutil.ToUnstructured(t, configMap), nil).AnyTimes()
+				tpo.objectStore.EXPECT().Get(ctx, gomock.Eq(key)).Return(testutil.ToUnstructured(t, configMap), true, nil).AnyTimes()
 			}
 
 			cc := NewContainerConfiguration(ctx, parentPod, tc.container, pf, tc.isInit, printOptions)
diff --git a/internal/printer/horizontalpodautoscaler_test.go b/internal/printer/horizontalpodautoscaler_test.go
index b8bcc5b9..89e1c6bf 100644
--- a/internal/printer/horizontalpodautoscaler_test.go
+++ b/internal/printer/horizontalpodautoscaler_test.go
@@ -156,7 +156,7 @@ func Test_HorizontalPodAutoscalerConfiguration(t *testing.T) {
 					Name:       deployment.Name,
 					Namespace:  deployment.Namespace,
 				}
-				tpo.objectStore.EXPECT().Get(ctx, gomock.Eq(key)).Return(testutil.ToUnstructured(t, deployment), nil)
+				tpo.objectStore.EXPECT().Get(ctx, gomock.Eq(key)).Return(testutil.ToUnstructured(t, deployment), true, nil)
 			}
 
 			summary, err := hc.Create(ctx, printOptions)
diff --git a/internal/printer/service_test.go b/internal/printer/service_test.go
index 58710a44..2a6add69 100644
--- a/internal/printer/service_test.go
+++ b/internal/printer/service_test.go
@@ -321,7 +321,7 @@ func Test_createServiceEndpointsView(t *testing.T) {
 	key := store.Key{Namespace: "default", APIVersion: "v1", Kind: "Endpoints", Name: "service"}
 	tpo.objectStore.EXPECT().
 		Get(gomock.Any(), gomock.Eq(key)).
-		Return(toUnstructured(t, endpoints), nil)
+		Return(toUnstructured(t, endpoints), true, nil)
 
 	podLink := component.NewLink("", "pod", "/pod")
 	tpo.link.EXPECT().
diff --git a/internal/printer/serviceaccount_test.go b/internal/printer/serviceaccount_test.go
index 329abf9e..7d9ee92e 100644
--- a/internal/printer/serviceaccount_test.go
+++ b/internal/printer/serviceaccount_test.go
@@ -213,11 +213,11 @@ func Test_ServiceAccountPolicyRules(t *testing.T) {
 
 	tpo.objectStore.EXPECT().
 		Get(gomock.Any(), role1Key).
-		Return(testutil.ToUnstructured(t, role1), nil)
+		Return(testutil.ToUnstructured(t, role1), true, nil)
 
 	tpo.objectStore.EXPECT().
 		Get(gomock.Any(), role2Key).
-		Return(testutil.ToUnstructured(t, role2), nil)
+		Return(testutil.ToUnstructured(t, role2), true, nil)
 
 	saph := NewServiceAccountPolicyRules(ctx, serviceAccount, printOptions)
 	got, err := saph.Create()
diff --git a/internal/queryer/queryer_test.go b/internal/queryer/queryer_test.go
index 5d9400cc..2263e092 100644
--- a/internal/queryer/queryer_test.go
+++ b/internal/queryer/queryer_test.go
@@ -406,7 +406,7 @@ func TestCacheQueryer_OwnerReference(t *testing.T) {
 				}
 				o.EXPECT().
 					Get(gomock.Any(), gomock.Eq(key)).
-					Return(deployment, nil)
+					Return(deployment, true, nil)
 			},
 			expected: func(t *testing.T) runtime.Object {
 				return testutil.ToUnstructured(t, deployment)
@@ -423,7 +423,7 @@ func TestCacheQueryer_OwnerReference(t *testing.T) {
 				}
 				o.EXPECT().
 					Get(gomock.Any(), gomock.Eq(key)).
-					Return(nil, errors.New("failed"))
+					Return(nil, false, errors.New("failed"))
 			},
 			isErr: true,
 		},
@@ -579,7 +579,7 @@ func TestCacheQueryer_ServicesForIngress_service_not_found(t *testing.T) {
 	o := storeFake.NewMockStore(controller)
 	o.EXPECT().
 		Get(gomock.Any(), gomock.Any()).
-		Return(nil, nil)
+		Return(nil, false, nil)
 
 	discovery := queryerFake.NewMockDiscoveryInterface(controller)
 
@@ -668,7 +668,7 @@ func TestCacheQueryer_ServicesForIngress(t *testing.T) {
 				}
 				o.EXPECT().
 					Get(gomock.Any(), gomock.Eq(key)).
-					Return(testutil.ToUnstructured(t, service1), nil)
+					Return(testutil.ToUnstructured(t, service1), true, nil)
 			},
 			expected: []string{"service1"},
 		},
@@ -684,7 +684,7 @@ func TestCacheQueryer_ServicesForIngress(t *testing.T) {
 				}
 				o.EXPECT().
 					Get(gomock.Any(), gomock.Eq(key1)).
-					Return(testutil.ToUnstructured(t, service1), nil)
+					Return(testutil.ToUnstructured(t, service1), true, nil)
 				key2 := store.Key{
 					Namespace:  "default",
 					APIVersion: "v1",
@@ -693,7 +693,7 @@ func TestCacheQueryer_ServicesForIngress(t *testing.T) {
 				}
 				o.EXPECT().
 					Get(gomock.Any(), gomock.Eq(key2)).
-					Return(testutil.ToUnstructured(t, service2), nil)
+					Return(testutil.ToUnstructured(t, service2), true, nil)
 			},
 			expected: []string{"service1", "service2"},
 		},
@@ -714,7 +714,7 @@ func TestCacheQueryer_ServicesForIngress(t *testing.T) {
 				}
 				c.EXPECT().
 					Get(gomock.Any(), gomock.Eq(key)).
-					Return(nil, errors.New("failed"))
+					Return(nil, false, errors.New("failed"))
 			},
 			isErr: true,
 		},
@@ -880,7 +880,7 @@ func TestObjectStoreQueryer_ServiceAccountForPod(t *testing.T) {
 	require.NoError(t, err)
 	o.EXPECT().
 		Get(gomock.Any(), key).
-		Return(testutil.ToUnstructured(t, serviceAccount), nil)
+		Return(testutil.ToUnstructured(t, serviceAccount), true, nil)
 
 	discovery := queryerFake.NewMockDiscoveryInterface(controller)
 
@@ -1065,7 +1065,7 @@ func TestObjectStoreQueryer_ScaleTarget(t *testing.T) {
 	require.NoError(t, err)
 	o.EXPECT().
 		Get(gomock.Any(), key).
-		Return(testutil.ToUnstructured(t, deployment), nil)
+		Return(testutil.ToUnstructured(t, deployment), true, nil)
 
 	discovery := queryerFake.NewMockDiscoveryInterface(controller)
 
diff --git a/pkg/plugin/api/api_test.go b/pkg/plugin/api/api_test.go
index 1b27b49f..59f5e340 100644
--- a/pkg/plugin/api/api_test.go
+++ b/pkg/plugin/api/api_test.go
@@ -86,14 +86,15 @@ func TestAPI(t *testing.T) {
 			name: "get",
 			initFunc: func(t *testing.T, mocks *apiMocks) {
 				mocks.objectStore.EXPECT().
-					Get(gomock.Any(), gomock.Eq(getKey)).Return(object, nil)
+					Get(gomock.Any(), gomock.Eq(getKey)).Return(object, true, nil)
 			},
 			doFunc: func(t *testing.T, client *api.Client) {
 				clientCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
 				defer cancel()
 
-				got, err := client.Get(clientCtx, getKey)
+				got, found, err := client.Get(clientCtx, getKey)
 				require.NoError(t, err)
+				require.True(t, found)
 
 				expected := object