Repository: nats-io/nsc
Pull request #337 (2020-12-16 22:02:17)

[Added] options to enable headers for latency tracking and sharing
Signed-off-by: Matthias Hanel <mh@synadia.com>

aricart: https://github.com/nats-io/nsc/blob/e30bdbc44a6aa56a8923fe38df08ac9341c36aa6/cmd/addexport.go#L2 - update copyright

Patch:
diff --git a/cmd/addexport.go b/cmd/addexport.go
index fd0d847..ab84331 100644
--- a/cmd/addexport.go
+++ b/cmd/addexport.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2019 The NATS Authors
+ * Copyright 2018-2020 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -45,7 +45,7 @@ func createAddExportCmd() *cobra.Command {
 	cmd.Flags().BoolVarP(&params.service, "service", "r", false, "export type service")
 	cmd.Flags().BoolVarP(&params.private, "private", "p", false, "private export - requires an activation to access")
 	cmd.Flags().StringVarP(&params.latSubject, "latency", "", "", "latency metrics subject (services only)")
-	cmd.Flags().IntVarP(&params.latSampling, "sampling", "", 0, "latency sampling percentage [1-100]  (services only)")
+	cmd.Flags().StringVarP(&params.latSampling, "sampling", "", "", "latency sampling percentage [1-100] or `header`  (services only)")
 	hm := fmt.Sprintf("response type for the service [%s | %s | %s] (services only)", jwt.ResponseTypeSingleton, jwt.ResponseTypeStream, jwt.ResponseTypeChunked)
 	cmd.Flags().StringVarP(&params.responseType, "response-type", "", jwt.ResponseTypeSingleton, hm)
 	params.AccountContextParams.BindFlags(cmd)
@@ -67,7 +67,7 @@ type AddExportParams struct {
 	SignerParams
 	subject      string
 	latSubject   string
-	latSampling  int
+	latSampling  string
 	responseType string
 }
 
@@ -152,12 +152,11 @@ func (p *AddExportParams) PreInteractive(ctx ActionCtx) error {
 			return err
 		}
 		if ok {
-			samp, err := cli.Prompt("sampling percentage [1-100]", "", cli.Val(SamplingValidator))
+			samp, err := cli.Prompt("sampling percentage [1-100] or `header`", "", cli.Val(SamplingValidator))
 			if err != nil {
 				return err
 			}
-			// cannot fail
-			p.latSampling, _ = strconv.Atoi(samp)
+			p.latSampling = samp
 
 			p.latSubject, err = cli.Prompt("latency metrics subject", "", cli.Val(LatencyMetricsSubjectValidator))
 			if err != nil {
@@ -181,6 +180,9 @@ func (p *AddExportParams) PreInteractive(ctx ActionCtx) error {
 }
 
 func SamplingValidator(s string) error {
+	if strings.ToLower(s) == "header" {
+		return nil
+	}
 	v, err := strconv.Atoi(s)
 	if err != nil {
 		return err
@@ -191,6 +193,25 @@ func SamplingValidator(s string) error {
 	return nil
 }
 
+func latSamplingRate(latSampling string) jwt.SamplingRate {
+	samp := 0
+	if strings.ToLower(latSampling) == "header" {
+		samp = int(jwt.Headers)
+	} else {
+		// cannot fail
+		samp, _ = strconv.Atoi(latSampling)
+	}
+	return jwt.SamplingRate(samp)
+}
+
+func latSamplingRateToString(rate jwt.SamplingRate) string {
+	if rate == jwt.Headers {
+		return "header"
+	} else {
+		return fmt.Sprintf("%d", rate)
+	}
+}
+
 func LatencyMetricsSubjectValidator(s string) error {
 	var lat jwt.ServiceLatency
 	// bogus freq just to get a value into the validation
@@ -238,7 +259,7 @@ func (p *AddExportParams) Validate(ctx ActionCtx) error {
 
 	// if we have a latency report subject create it
 	if p.latSubject != "" {
-		p.export.Latency = &jwt.ServiceLatency{Results: jwt.Subject(p.latSubject), Sampling: jwt.SamplingRate(p.latSampling)}
+		p.export.Latency = &jwt.ServiceLatency{Results: jwt.Subject(p.latSubject), Sampling: latSamplingRate(p.latSampling)}
 	}
 
 	// add the new export
diff --git a/cmd/addimport.go b/cmd/addimport.go
index ec5f416..5704717 100644
--- a/cmd/addimport.go
+++ b/cmd/addimport.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2019 The NATS Authors
+ * Copyright 2018-2020 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -46,6 +46,7 @@ func createAddImportCmd() *cobra.Command {
 	params.srcAccount.BindFlags("src-account", "", nkeys.PrefixByteAccount, cmd)
 	cmd.Flags().StringVarP(&params.remote, "remote-subject", "", "", "remote subject (only public imports)")
 	cmd.Flags().BoolVarP(&params.service, "service", "", false, "service (only public imports)")
+	cmd.Flags().BoolVarP(&params.share, "share", "", false, "share data when tracking latency (service only)")
 	params.AccountContextParams.BindFlags(cmd)
 
 	return cmd
@@ -67,6 +68,7 @@ type AddImportParams struct {
 	service    bool
 	name       string
 	public     bool
+	share      bool
 }
 
 func (p *AddImportParams) longHelp() string {
@@ -305,7 +307,11 @@ func (p *AddImportParams) PreInteractive(ctx ActionCtx) error {
 	if !ok {
 		return p.addManualExport(ctx)
 	}
-
+	if p.service {
+		if p.share, err = cli.Confirm("share information when tracking latency?", false); err != nil {
+			return err
+		}
+	}
 	return nil
 }
 
@@ -464,6 +470,8 @@ func (p *AddImportParams) Validate(ctx ActionCtx) error {
 	kind := jwt.Stream
 	if p.service {
 		kind = jwt.Service
+	} else if p.share {
+		return fmt.Errorf("only services can set the share property")
 	}
 
 	// local becomes Subject for services, or prefix for streams
@@ -553,6 +561,7 @@ func (p *AddImportParams) createImport() *jwt.Import {
 	if p.service {
 		im.Type = jwt.Service
 		im.Subject, im.To = im.To, im.Subject
+		im.Share = p.share
 	}
 	if p.tokenSrc != "" {
 		if IsURL(p.tokenSrc) {
diff --git a/cmd/editexport.go b/cmd/editexport.go
index c0cb7e9..b2cf35e 100644
--- a/cmd/editexport.go
+++ b/cmd/editexport.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2019 The NATS Authors
+ * Copyright 2018-2020 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -18,7 +18,6 @@ package cmd
 import (
 	"errors"
 	"fmt"
-	"strconv"
 
 	cli "github.com/nats-io/cliprompts/v2"
 	"github.com/nats-io/jwt/v2"
@@ -44,7 +43,7 @@ func createEditExportCmd() *cobra.Command {
 	cmd.Flags().BoolVarP(&params.service, "service", "r", false, "export type service")
 	cmd.Flags().BoolVarP(&params.private, "private", "p", false, "private export - requires an activation to access")
 	cmd.Flags().StringVarP(&params.latSubject, "latency", "", "", "latency metrics subject (services only)")
-	cmd.Flags().IntVarP(&params.latSampling, "sampling", "", 0, "latency sampling percentage [0-100] - 0 disables it (services only)")
+	cmd.Flags().StringVarP(&params.latSampling, "sampling", "", "", "latency sampling percentage [1-100] or `header` - 0 disables it (services only)")
 	cmd.Flags().BoolVarP(&params.rmLatencySampling, "rm-latency-sampling", "", false, "remove latency sampling")
 	cmd.Flags().StringVarP(&params.description, "description", "", "", "Description for this export")
 	cmd.Flags().StringVarP(&params.infoUrl, "info-url", "", "", "Link for more info on this export")
@@ -68,7 +67,7 @@ type EditExportParams struct {
 	subject string
 
 	name              string
-	latSampling       int
+	latSampling       string
 	latSubject        string
 	service           bool
 	private           bool
@@ -206,18 +205,17 @@ func (p *EditExportParams) PostInteractive(ctx ActionCtx) error {
 			return err
 		}
 		if ok {
-			cls := jwt.SamplingRate(0)
+			cls := ""
 			results := jwt.Subject("")
 			if sel.Latency != nil {
-				cls = sel.Latency.Sampling
+				cls = latSamplingRateToString(sel.Latency.Sampling)
 				results = sel.Latency.Results
 			}
-			samp, err := cli.Prompt("sampling percentage [1-100]", fmt.Sprintf("%d", cls), cli.Val(SamplingValidator))
+			samp, err := cli.Prompt("sampling percentage [1-100] or `header`", cls, cli.Val(SamplingValidator))
 			if err != nil {
 				return err
 			}
-			// cannot fail
-			p.latSampling, _ = strconv.Atoi(samp)
+			p.latSampling = samp
 
 			p.latSubject, err = cli.Prompt("latency metrics subject", string(results), cli.Val(LatencyMetricsSubjectValidator))
 			if err != nil {
@@ -298,17 +296,17 @@ func (p *EditExportParams) syncOptions(ctx ActionCtx) {
 	if !(cmd.Flag("private").Changed) {
 		p.private = old.TokenReq
 	}
-	sampling := 0
+	sampling := jwt.SamplingRate(0)
 	latency := ""
 	if old.Latency != nil {
-		sampling = int(old.Latency.Sampling)
+		sampling = old.Latency.Sampling
 		latency = string(old.Latency.Results)
 	}
 	if !(cmd.Flag("latency").Changed) {
 		p.latSubject = latency
 	}
 	if !(cmd.Flag("sampling").Changed) {
-		p.latSampling = sampling
+		p.latSampling = latSamplingRateToString(sampling)
 	}
 
 	if !(cmd.Flag("response-type").Changed) {
@@ -376,7 +374,7 @@ func (p *EditExportParams) Run(ctx ActionCtx) (store.Status, error) {
 				oldReport = old.Latency.Results
 			}
 			if p.latSubject != "" {
-				export.Latency = &jwt.ServiceLatency{Results: jwt.Subject(p.latSubject), Sampling: jwt.SamplingRate(p.latSampling)}
+				export.Latency = &jwt.ServiceLatency{Results: jwt.Subject(p.latSubject), Sampling: latSamplingRate(p.latSampling)}
 				if oldSampling != export.Latency.Sampling {
 					r.AddOK("changed service latency to %d%%", export.Latency.Sampling)
 				}


Test patch:
diff --git a/cmd/addexport_test.go b/cmd/addexport_test.go
index d1aa200..0b7f03c 100644
--- a/cmd/addexport_test.go
+++ b/cmd/addexport_test.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2019 The NATS Authors
+ * Copyright 2018-2020 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
diff --git a/cmd/addimport_test.go b/cmd/addimport_test.go
index 85da9b6..f228eb7 100644
--- a/cmd/addimport_test.go
+++ b/cmd/addimport_test.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2019 The NATS Authors
+ * Copyright 2018-2020 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -175,13 +175,14 @@ func Test_AddServiceImportGeneratingTokenInteractive(t *testing.T) {
 
 	cmd := createAddImportCmd()
 	HoistRootFlags(cmd)
-	input := []interface{}{1, true, 1, "barfoo.xx", "my import", "foobar.yy"}
+	input := []interface{}{1, true, 1, "barfoo.xx", true, "my import", "foobar.yy"}
 	_, _, err = ExecuteInteractiveCmd(cmd, input)
 	require.NoError(t, err)
 
 	ac, err := ts.Store.ReadAccountClaim("B")
 	require.NoError(t, err)
 	require.Len(t, ac.Imports, 1)
+	require.Equal(t, true, ac.Imports[0].Share)
 	require.Equal(t, "my import", ac.Imports[0].Name)
 	require.Equal(t, "barfoo.xx", string(ac.Imports[0].To))
 	require.Equal(t, "foobar.yy", string(ac.Imports[0].Subject))
@@ -392,6 +393,7 @@ func Test_AddImport_LocalImportsInteractive(t *testing.T) {
 	ac, err := ts.Store.ReadAccountClaim("B")
 	require.NoError(t, err)
 	require.Len(t, ac.Imports, 1)
+	require.Equal(t, false, ac.Imports[0].Share)
 	require.Equal(t, "test", ac.Imports[0].Name)
 	require.Equal(t, "test.foobar", string(ac.Imports[0].To))
 	require.Equal(t, "foobar.>", string(ac.Imports[0].Subject))
@@ -399,13 +401,14 @@ func Test_AddImport_LocalImportsInteractive(t *testing.T) {
 	require.Equal(t, apub, ac.Imports[0].Account)
 
 	// B, pick, service q, name q service, local subj qq
-	input = []interface{}{1, true, 2, "q service", "qq", 0}
+	input = []interface{}{1, true, 2, true, "q service", "qq", 0}
 	_, _, err = ExecuteInteractiveCmd(cmd, input)
 	require.NoError(t, err)
 
 	ac, err = ts.Store.ReadAccountClaim("B")
 	require.NoError(t, err)
 	require.Len(t, ac.Imports, 2)
+	require.Equal(t, true, ac.Imports[1].Share)
 	require.Equal(t, "q service", ac.Imports[1].Name)
 	require.Equal(t, "q", string(ac.Imports[1].To))
 	require.Equal(t, "qq", string(ac.Imports[1].Subject))
diff --git a/cmd/editexport_test.go b/cmd/editexport_test.go
index 2e5b9cd..d1c0014 100644
--- a/cmd/editexport_test.go
+++ b/cmd/editexport_test.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2019 The NATS Authors
+ * Copyright 2018-2020 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -86,7 +86,7 @@ func Test_EditExportInteractiveLatency(t *testing.T) {
 	ts.AddAccount(t, "A")
 	ts.AddExport(t, "A", jwt.Service, "a", true)
 
-	_, _, err := ExecuteInteractiveCmd(createEditExportCmd(), []interface{}{0, 1, "c", "c", false, true, "100", "lat", 2, "", ""})
+	_, _, err := ExecuteInteractiveCmd(createEditExportCmd(), []interface{}{0, 1, "c", "c", false, true, "header", "lat", 2, "", ""})
 	require.NoError(t, err)
 
 	ac, err := ts.Store.ReadAccountClaim("A")
@@ -94,7 +94,7 @@ func Test_EditExportInteractiveLatency(t *testing.T) {
 	require.Equal(t, jwt.Subject("c"), ac.Exports[0].Subject)
 	require.Equal(t, "c", ac.Exports[0].Name)
 	require.NotNil(t, ac.Exports[0].Latency)
-	require.Equal(t, jwt.SamplingRate(100), ac.Exports[0].Latency.Sampling)
+	require.Equal(t, jwt.Headers, ac.Exports[0].Latency.Sampling)
 	require.Equal(t, jwt.Subject("lat"), ac.Exports[0].Latency.Results)
 	require.EqualValues(t, jwt.ResponseTypeChunked, ac.Exports[0].ResponseType)
 }
@@ -106,12 +106,13 @@ func Test_EditExportRmLatencySampling(t *testing.T) {
 	ts.AddAccount(t, "A")
 	ts.AddExport(t, "A", jwt.Service, "a", true)
 
-	_, _, err := ExecuteCmd(createEditExportCmd(), "--subject", "a", "--sampling", "100", "--latency", "metrics.a")
+	_, _, err := ExecuteCmd(createEditExportCmd(), "--subject", "a", "--sampling", "header", "--latency", "metrics.a")
 	require.NoError(t, err)
 
 	ac, err := ts.Store.ReadAccountClaim("A")
 	require.NoError(t, err)
 	require.NotNil(t, ac.Exports[0].Latency)
+	require.Equal(t, jwt.Headers, ac.Exports[0].Latency.Sampling)
 
 	_, _, err = ExecuteCmd(createEditExportCmd(), "--subject", "a", "--rm-latency-sampling")
 	require.NoError(t, err)