diff --git a/cmd/list.go b/cmd/list.go index 834ad09aea..41845890b7 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -167,9 +167,9 @@ func (items listItems) Plain(w io.Writer) error { tabWriter := tabwriter.NewWriter(w, 0, 8, 2, ' ', 0) defer tabWriter.Flush() - fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\t%s\t%s\n", "NAME", "NAMESPACE", "RUNTIME", "DEPLOYER", "URL", "READY") + fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "NAME", "NAMESPACE", "RUNTIME", "DEPLOYER", "URL", "READY", "REPLICAS") for _, item := range items { - fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\t%s\t%s\n", item.Name, item.Namespace, item.Runtime, item.Deployer, item.URL, item.Ready) + fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\t%s\t%s\t%d\n", item.Name, item.Namespace, item.Runtime, item.Deployer, item.URL, item.Ready, item.Replicas) } return nil } diff --git a/pkg/functions/client.go b/pkg/functions/client.go index a4b2902ff9..6aba27f951 100644 --- a/pkg/functions/client.go +++ b/pkg/functions/client.go @@ -165,6 +165,7 @@ type ListItem struct { URL string `json:"url" yaml:"url"` Ready string `json:"ready" yaml:"ready"` Deployer string `json:"deployer" yaml:"deployer"` + Replicas int `json:"replicas" yaml:"replicas"` } // Describer of function instances diff --git a/pkg/k8s/lister.go b/pkg/k8s/lister.go index fdb120c479..05b2022cb9 100644 --- a/pkg/k8s/lister.go +++ b/pkg/k8s/lister.go @@ -90,6 +90,7 @@ func (l *Lister) get(ctx context.Context, clientset *kubernetes.Clientset, name, URL: fmt.Sprintf("http://%s.%s.svc", service.Name, service.Namespace), // TODO: use correct scheme Ready: string(ready), Deployer: KubernetesDeployerName, + Replicas: int(deployment.Status.ReadyReplicas), } return listItem, nil diff --git a/pkg/keda/lister.go b/pkg/keda/lister.go index 72593ee900..58eb9587fe 100644 --- a/pkg/keda/lister.go +++ b/pkg/keda/lister.go @@ -9,6 +9,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" fn "knative.dev/func/pkg/functions" "knative.dev/func/pkg/k8s" "knative.dev/func/pkg/k8s/labels" @@ -61,7 +62,7 @@ func (l *Lister) List(ctx context.Context, namespace string) ([]fn.ListItem, err } runtime := service.Labels[labels.FunctionRuntimeKey] - item, err := l.get(ctx, httpScaledObjectClientset, service.Name, service.Namespace, runtime) + item, err := l.get(ctx, clientset, httpScaledObjectClientset, service.Name, service.Namespace, runtime) if err != nil { return nil, fmt.Errorf("unable to get details about function: %v", err) } @@ -73,12 +74,18 @@ func (l *Lister) List(ctx context.Context, namespace string) ([]fn.ListItem, err } // Get a function, optionally specifying a namespace. -func (l *Lister) get(ctx context.Context, httpScaledObjectClientset *versioned.Clientset, name, namespace, runtime string) (fn.ListItem, error) { +func (l *Lister) get(ctx context.Context, clientset *kubernetes.Clientset, httpScaledObjectClientset *versioned.Clientset, name, namespace, runtime string) (fn.ListItem, error) { httpScaledObject, err := httpScaledObjectClientset.HttpV1alpha1().HTTPScaledObjects(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return fn.ListItem{}, fmt.Errorf("unable to get HTTPScaledObject: %v", err) } + deployment, err := clientset.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return fn.ListItem{}, fmt.Errorf("unable to get deployment: %v", err) + } + replicas := int(deployment.Status.ReadyReplicas) + ready := v1.ConditionUnknown if meta.IsStatusConditionTrue(httpScaledObject.Status.Conditions, v1alpha1.ConditionTypeReady) { ready = v1.ConditionTrue @@ -98,6 +105,7 @@ func (l *Lister) get(ctx context.Context, httpScaledObjectClientset *versioned.C URL: url, Ready: string(ready), Deployer: KedaDeployerName, + Replicas: replicas, } return listItem, nil diff --git a/pkg/knative/lister.go b/pkg/knative/lister.go index a1157e42a4..feb76ba6bc 100644 --- a/pkg/knative/lister.go +++ b/pkg/knative/lister.go @@ -7,6 +7,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientservingv1 "knative.dev/client/pkg/serving/v1" "knative.dev/func/pkg/k8s" "knative.dev/func/pkg/k8s/labels" @@ -33,12 +34,12 @@ func (l *Lister) List(ctx context.Context, namespace string) ([]fn.ListItem, err restConfig, err := l.kc.ClientConfig() if err != nil { - return nil, fmt.Errorf("failed to get kubernetes client config: %v", err) + return nil, fmt.Errorf("unable to get kubernetes client config: %w", err) } servingClient, err := servingv1.NewForConfig(restConfig) if err != nil { - return nil, fmt.Errorf("failed to create serving client: %v", err) + return nil, fmt.Errorf("unable to create serving client: %w", err) } client := clientservingv1.NewKnServingClient(servingClient, namespace) @@ -80,6 +81,11 @@ func (l *Lister) List(ctx context.Context, namespace string) ([]fn.ListItem, err } } + replicas, err := readyReplicas(ctx, servingClient, service.Namespace, service.Status.LatestReadyRevisionName) + if err != nil { + return nil, fmt.Errorf("unable to get replicas for %s: %w", service.Name, err) + } + runtimeLabel := service.Labels[labels.FunctionRuntimeKey] listItem := fn.ListItem{ @@ -89,6 +95,7 @@ func (l *Lister) List(ctx context.Context, namespace string) ([]fn.ListItem, err URL: service.Status.URL.String(), Ready: string(ready), Deployer: KnativeDeployerName, + Replicas: replicas, } items = append(items, listItem) @@ -96,3 +103,20 @@ func (l *Lister) List(ctx context.Context, namespace string) ([]fn.ListItem, err return items, nil } + +func readyReplicas(ctx context.Context, client servingv1.ServingV1Interface, namespace, revision string) (int, error) { + if revision == "" { + return 0, nil + } + rev, err := client.Revisions(namespace).Get(ctx, revision, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return 0, nil + } + return 0, err + } + if rev.Status.ActualReplicas == nil { + return 0, nil + } + return int(*rev.Status.ActualReplicas), nil +} diff --git a/pkg/lister/testing/integration_test_helper.go b/pkg/lister/testing/integration_test_helper.go index d063fb175f..a8f1ed7b41 100644 --- a/pkg/lister/testing/integration_test_helper.go +++ b/pkg/lister/testing/integration_test_helper.go @@ -87,15 +87,18 @@ func TestInt_List(t *testing.T, lister fn.Lister, deployer fn.Deployer, describe } // Should find at least our function (may have others in namespace) - found := false - for _, item := range list { - if item.Name == f.Name { - found = true + var found *fn.ListItem + for i := range list { + if list[i].Name == f.Name { + found = &list[i] break } } - if !found { - t.Errorf("function %s not found in list", f.Name) + if found == nil { + t.Fatalf("function %s not found in list", f.Name) } + if found.Replicas < 1 { + t.Errorf("expected function %s to report >= 1 replica, got %d", f.Name, found.Replicas) + } }