Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify the Stat API to handle requests for resource type "all" #928

Merged
merged 14 commits into from
May 11, 2018

Conversation

rmars
Copy link

@rmars rmars commented May 10, 2018

Allow the Stat endpoint in the public-api to accept requests for resourceType "all".
Currently, this queries Pods, Deployments, RCs and Services, but can be modified to query other things as well.

Both the CLI and web endpoints now work if you set resourceType to all.

Examples:

http://localhost:8084/api/stat?resource_type=all&namespace=emojivoto
$ go run cli/main.go stat all -n emojivoto --api-addr localhost:8085
NAME                           MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
po/emoji-5f6854bc96-kmwqv         1/1   100.00%   0.3rps           5ms          10ms          10ms
po/emoji-5f6854bc96-r842n         1/1   100.00%   0.5rps           5ms          10ms          10ms
po/vote-bot-6659cc85cb-dldds      1/1         -        -             -             -             -
po/voting-844b495b9d-jj2vl        1/1         -        -             -             -             -
po/voting-844b495b9d-tplzn        1/1         -        -             -             -             -
po/web-77fdc7bc7d-k5lbt           1/1   100.00%   0.1rps           7ms          28ms          30ms
po/web-77fdc7bc7d-t9d7f           1/1         -        -             -             -             -

NAME                           MESHED   SUCCESS   RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
svc/emoji-svc                     2/2         -     -             -             -             -
svc/voting-svc                    2/2         -     -             -             -             -
svc/web-svc                       2/2         -     -             -             -             -

NAME                           MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
deploy/emoji                      2/2   100.00%   0.9rps           5ms          10ms          10ms
deploy/vote-bot                   1/1         -        -             -             -             -
deploy/voting                     2/2         -        -             -             -             -
deploy/web                        2/2   100.00%   0.1rps           7ms          28ms          30ms

Copy link
Member

@siggy siggy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so cool!

can you update the stat command help output near Valid resource types include:?

@@ -56,9 +64,60 @@ func (s *grpcServer) StatSummary(ctx context.Context, req *pb.StatSummaryRequest
return nil, status.Errorf(codes.InvalidArgument, "service only supported as a target on 'from' queries, or as a destination on 'to' queries.")
}

statTables := make([]*pb.StatTable, 0)

if req.Selector.Resource.Type == k8s.All {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to add a test for this case in stat_summary_test.go?

cli/cmd/stat.go Outdated
nameHeader = "NAME"
maxNameLength = len(nameHeader)
namespaceHeader = "NAMESPACE"
maxNamespaceLength = len(namespaceHeader)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i realize this makes for more code changes, but consider defining maxNameLength and maxNamespaceLength in writeStatsToBuffer, and then passing them into printStatTable, rather than sharing global vars between the functions.

cli/cmd/stat.go Outdated
@@ -185,6 +194,14 @@ func writeStatsToBuffer(resp *pb.StatSummaryResponse, w *tabwriter.Writer) {
}
}

fmt.Fprint(w, "\n\n")
for resourceType, stats := range statTables {
fmt.Fprintf(w, "\n%s\n\n", resourceType)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd like to keep the conduit cli as similar to kubectl as possible. that pattern appends the resource types onto the names when the all flag is used:

$ kubectl -n emojivoto get all
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/emoji      1         1         1            1           50s
deploy/vote-bot   1         1         1            1           48s
deploy/voting     1         1         1            1           49s
deploy/web        1         1         1            1           49s

NAME                     DESIRED   CURRENT   READY     AGE
rs/emoji-8bfdd4cfb       1         1         1         50s
...

this has the added benefit that those strings can be cut/pasted into subsequent commands.

this all implies a bigger change, as the output of printStatTable needs to vary depending on whether the all flag is set, but i think that's for the best, as a single resource stat output carries some redundant info in this implementation:

$ bin/conduit -n emojivoto stat deploy
deployments

NAME       MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
emoji         1/1   100.00%   1.9rps           5ms          10ms          10ms
vote-bot      1/1         -        -             -             -             -
voting        1/1    79.66%   1.0rps           5ms          10ms          10ms
web           1/1    90.68%   2.0rps           6ms          40ms          48ms

you'll need to leverage the shortest version of our resource strings somehow, currently utilized in CanonicalKubernetesNameFromFriendlyName.

resultChan := make(chan resourceResult)

for _, resource := range resourceTypes {
req := &pb.StatSummaryRequest{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mind naming req something else, to not collide with the param passed in?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yeah, that would be less confusing

statTables := make([]*pb.StatTable, 0)

if req.Selector.Resource.Type == k8s.All {
// request stats for all resourceTypes, in parallel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is awesome! i think it will be a bit more readable if you remove the special casing for the single stat query, and instead do something like:

	resources := []string{}
	if req.Selector.Resource.Type == k8s.All {
		resources = resourceTypes
	} else {
		resources = []string{req.Selector.Resource.Type}
	}

	// request stats for all resourceTypes, in parallel
	resultChan := make(chan resourceResult)

	for _, resource := range resources {
		req := &pb.StatSummaryRequest{
			Selector: &pb.ResourceSelection{
				Resource: &pb.Resource{
					Type:      resource,
					Namespace: req.Selector.Resource.Namespace,
				},
				LabelSelector: req.Selector.LabelSelector,
			},
			TimeWindow: req.TimeWindow,
		}

		go func() {
			resultChan <- s.resourceQuery(ctx, req)
		}()
	}

	statTables := make([]*pb.StatTable, 0)

	for i := 0; i < len(resources); i++ {
		result := <-resultChan
		if result.err != nil {
			return nil, util.GRPCError(result.err)
		}
		statTables = append(statTables, result.res)
	}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah! this is way more readable!

resourceType := p.ResourceType
var err error
if resourceType != k8s.All {
resourceType, err = k8s.CanonicalKubernetesNameFromFriendlyName(p.ResourceType)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about adding support for all into CanonicalKubernetesNameFromFriendlyName, to avoid the special casing here?

this would motivate a further change in (s *grpcServer) StatSummary, where we'd have to check that the FromResource or ToResource are not all, but maybe that's not a big deal, because we're already checking that the FromResource is not a svc, so it's kind of a similar check?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that's probably a good idea! then we can avoid checking everywhere else.

Copy link
Member

@siggy siggy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for all the changes, the output is looking great!

i made a few more comments, i think you're also going to be a little conflict-y with #935 fyi.

cli/cmd/stat.go Outdated
}
}

func printStatTable(stats map[string]*row, resourceType string, w *tabwriter.Writer, maxNameLength int, maxNamespaceLength int) {
if len(stats) == 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i just realized we need to move this len(stats) check up into writeStatsToBuffer, as that's where we should be checking for the presence of any stats, otherwise we panic:

$ bin/conduit stat deploy -n foo
panic: runtime error: slice bounds out of range

goroutine 1 [running]:
github.com/runconduit/conduit/cli/cmd.renderStats(0xc42027db50, 0x1d6ea6d, 0xb, 0xc42023fd70, 0x0)
	/go/src/github.com/runconduit/conduit/cli/cmd/stat.go:129 +0x20a
github.com/runconduit/conduit/cli/cmd.requestStatsFromAPI(0x1e84ec0, 0xc42027db30, 0xc42023fd70, 0xc4202ee630, 0x1, 0x3, 0x7ffeefbfeae6)
	/go/src/github.com/runconduit/conduit/cli/cmd/stat.go:119 +0x124

we can move the check like this:

diff --git a/cli/cmd/stat.go b/cli/cmd/stat.go
index 79c7566..b576d9e 100644
--- a/cli/cmd/stat.go
+++ b/cli/cmd/stat.go
@@ -199,8 +199,12 @@ func writeStatsToBuffer(resp *pb.StatSummaryResponse, reqResourceType string, w
                }
        }

-       for resourceType, stats := range statTables {
+       if len(statTables) == 0 {
+               fmt.Fprintln(os.Stderr, "No traffic found.")
+               os.Exit(0)
+       }

+       for resourceType, stats := range statTables {
                if reqResourceType == k8s.All {
                        printStatTable(stats, resourceType, w, maxNameLength, maxNamespaceLength)
                } else {
@@ -211,11 +215,6 @@ func writeStatsToBuffer(resp *pb.StatSummaryResponse, reqResourceType string, w
 }

 func printStatTable(stats map[string]*row, resourceType string, w *tabwriter.Writer, maxNameLength int, maxNamespaceLength int) {
-       if len(stats) == 0 {
-               fmt.Fprintln(os.Stderr, "No traffic found.")
-               os.Exit(0)
-       }
-
        headers := make([]string, 0)
        if allNamespaces {
                headers = append(headers,

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahhh, good catch

cli/cmd/stat.go Outdated
} else {
printStatTable(stats, "", w, maxNameLength, maxNamespaceLength)
}
fmt.Fprint(w, "\n")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we can avoid the trailing \n with:

	firstStat := true
	for resourceType, stats := range statTables {
		if !firstStat {
			fmt.Fprint(w, "\n")
		}
		firstStat = false

		if reqResourceType == k8s.All {
			printStatTable(stats, resourceType, w, maxNameLength, maxNamespaceLength)
		} else {
			printStatTable(stats, "", w, maxNameLength, maxNamespaceLength)
		}
	}

@rmars rmars force-pushed the rmars/stat-all branch from 082a95a to 2dd7938 Compare May 11, 2018 18:10
Copy link
Member

@siggy siggy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

TIOLI before merging, consider mentioning the new all flag in the Valid resource types include: section of stat --help.

@rmars
Copy link
Author

rmars commented May 11, 2018

OMG I intended to and I completely forgot - thanks!

rmars added a commit that referenced this pull request May 18, 2018
* Fix bug where we were dropping parts of the StatSummaryRequest
* Add tests for prometheus query strings and for failed cases

Problem
In #928 I rewrote the stat api to handle 'all' as a resource type. To query for all resource types, 
we would copy the Resource, LabelSelector and TimeWindow of the original request, and then 
go through all the resource types and set Resource.Type for each resource we wanted to get.
The bug is that while we copy over some fields of the original request, we didn't copy over all 
of them - namely Resource.Name and the Outbound resource. So the Stat endpoint would 
ignore any --to or --from flags, and would ignore requests for a specific named resource.

Solution
Copy over all fields from the request.

I've also added tests for this case. In this process I've refactored the stat_summary_test code 
to make it a bit easier to read/use.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants