diff --git a/CHANGELOG.md b/CHANGELOG.md
index 992ba8d9..124d3495 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,17 @@
# Changelog
This is the changelog for NeoFS Proto
+## [0.3.2] - 2020-02-10
+
+### Added
+- gRPC method DumpVars to State service
+- add method `EncodeVariables` to encode debug variables to JSON (slice of bytes)
+- increase test coverage for state package
+
+### Updated
+- state proto file
+- documentation for state service and messages
+
## [0.3.1] - 2020-02-07
### Fixed
- bug with `tz.Concat`
@@ -177,3 +188,4 @@ Initial public release
[0.2.14]: https://github.com/nspcc-dev/neofs-api/compare/v0.2.13...v0.2.14
[0.3.0]: https://github.com/nspcc-dev/neofs-api/compare/v0.2.14...v0.3.0
[0.3.1]: https://github.com/nspcc-dev/neofs-api/compare/v0.3.0...v0.3.1
+[0.3.2]: https://github.com/nspcc-dev/neofs-api/compare/v0.3.1...v0.3.2
diff --git a/Makefile b/Makefile
index 43f46426..a20243e6 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,6 @@ deps:
@go mod vendor
@echo "${B}${G}=> Cleanup old files ${R}"
- @find . -type f -name '*.pb.go' -not -path './vendor/*' -exec rm {} \;
@find . -type f -name '*.proto' -not -path './vendor/*' -not -name '*_test.proto' -exec rm {} \;
@echo "${B}${G}=> NeoFS Proto files ${R}"
@@ -49,6 +48,9 @@ docgen: deps
# Regenerate proto files:
protoc: deps
+ @echo "${B}${G}=> Cleanup old files ${R}"
+ @find . -type f -name '*.pb.go' -not -path './vendor/*' -exec rm {} \;
+
@echo "${B}${G}=> Install specific version for gogo-proto ${R}"
@go list -f '{{.Path}}/...@{{.Version}}' -m github.com/gogo/protobuf | xargs go get -v
@echo "${B}${G}=> Install specific version for protobuf lib ${R}"
@@ -60,3 +62,5 @@ protoc: deps
--proto_path=.:./vendor:/usr/local/include \
--gofast_out=plugins=grpc,paths=source_relative:. $$f; \
done
+
+update: docgen protoc
diff --git a/docs/state.md b/docs/state.md
index 79504554..2b3de4a0 100644
--- a/docs/state.md
+++ b/docs/state.md
@@ -10,6 +10,8 @@
- Messages
- [DumpRequest](#state.DumpRequest)
- [DumpResponse](#state.DumpResponse)
+ - [DumpVarsRequest](#state.DumpVarsRequest)
+ - [DumpVarsResponse](#state.DumpVarsResponse)
- [HealthRequest](#state.HealthRequest)
- [HealthResponse](#state.HealthResponse)
- [MetricsRequest](#state.MetricsRequest)
@@ -39,6 +41,7 @@ rpc Netmap(NetmapRequest) returns (.bootstrap.SpreadMap);
rpc Metrics(MetricsRequest) returns (MetricsResponse);
rpc HealthCheck(HealthRequest) returns (HealthResponse);
rpc DumpConfig(DumpRequest) returns (DumpResponse);
+rpc DumpVars(DumpVarsRequest) returns (DumpVarsResponse);
```
@@ -73,6 +76,15 @@ The request should be signed.
| Name | Input | Output |
| ---- | ----- | ------ |
| DumpConfig | [DumpRequest](#state.DumpRequest) | [DumpResponse](#state.DumpResponse) |
+#### Method DumpVars
+
+DumpVars returns debug variables for the current node.
+To permit access, used server config options.
+The request should be signed.
+
+| Name | Input | Output |
+| ---- | ----- | ------ |
+| DumpVars | [DumpVarsRequest](#state.DumpVarsRequest) | [DumpVarsResponse](#state.DumpVarsResponse) |
@@ -100,6 +112,30 @@ Config stored in JSON encoded into slice of bytes.
| Config | [bytes](#bytes) | | |
+
+
+### Message DumpVarsRequest
+DumpVarsRequest message to fetch current server debug variables.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| Meta | [service.RequestMetaHeader](#service.RequestMetaHeader) | | RequestMetaHeader contains information about request meta headers (should be embedded into message) |
+| Verify | [service.RequestVerificationHeader](#service.RequestVerificationHeader) | | RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message) |
+
+
+
+
+### Message DumpVarsResponse
+DumpVarsResponse message contains current server debug variables.
+Variables stored in JSON encoded into slice of bytes.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| Variables | [bytes](#bytes) | | |
+
+
### Message HealthRequest
diff --git a/state/service.go b/state/service.go
index 343a8222..577046cc 100644
--- a/state/service.go
+++ b/state/service.go
@@ -1,7 +1,10 @@
package state
import (
+ "bytes"
"encoding/json"
+ "expvar"
+ "fmt"
"github.com/golang/protobuf/proto"
"github.com/prometheus/client_golang/prometheus"
@@ -61,3 +64,25 @@ func EncodeConfig(v *viper.Viper) (*DumpResponse, error) {
return &DumpResponse{Config: data}, nil
}
+
+// EncodeVariables encodes debug variables into DumpVarsResponse message.
+// Variables encoded into JSON and stored as slice of bytes.
+func EncodeVariables() *DumpVarsResponse {
+ buf := new(bytes.Buffer)
+ buf.WriteString("{\n")
+ first := true
+
+ expvar.Do(func(kv expvar.KeyValue) {
+ if !first {
+ buf.WriteString(",\n")
+ }
+
+ first = false
+
+ _, _ = fmt.Fprintf(buf, "%q: %s", kv.Key, kv.Value)
+ })
+
+ buf.WriteString("\n}\n")
+
+ return &DumpVarsResponse{Variables: buf.Bytes()}
+}
diff --git a/state/service.pb.go b/state/service.pb.go
index f1e08dee..b55f3c12 100644
Binary files a/state/service.pb.go and b/state/service.pb.go differ
diff --git a/state/service.proto b/state/service.proto
index 312d465c..dc5efe40 100644
--- a/state/service.proto
+++ b/state/service.proto
@@ -23,6 +23,10 @@ service Status {
// To permit access, used server config options.
// The request should be signed.
rpc DumpConfig(DumpRequest) returns (DumpResponse);
+ // DumpVars returns debug variables for the current node.
+ // To permit access, used server config options.
+ // The request should be signed.
+ rpc DumpVars(DumpVarsRequest) returns (DumpVarsResponse);
}
// NetmapRequest message to request current node netmap
@@ -77,3 +81,17 @@ message DumpRequest {
message DumpResponse {
bytes Config = 1;
}
+
+// DumpVarsRequest message to fetch current server debug variables.
+message DumpVarsRequest {
+ // RequestMetaHeader contains information about request meta headers (should be embedded into message)
+ service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
+ // RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
+ service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
+}
+
+// DumpVarsResponse message contains current server debug variables.
+// Variables stored in JSON encoded into slice of bytes.
+message DumpVarsResponse {
+ bytes Variables = 1;
+}
diff --git a/state/service_test.go b/state/service_test.go
new file mode 100644
index 00000000..d545cb06
--- /dev/null
+++ b/state/service_test.go
@@ -0,0 +1,118 @@
+package state
+
+import (
+ "bytes"
+ "encoding/json"
+ "expvar"
+ "testing"
+
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/spf13/viper"
+ "github.com/stretchr/testify/require"
+)
+
+type testCollector struct {
+ testA *prometheus.Desc
+ testB *prometheus.Desc
+}
+
+func (c *testCollector) Describe(ch chan<- *prometheus.Desc) {
+ ch <- c.testA
+ ch <- c.testB
+}
+
+func (c *testCollector) Collect(ch chan<- prometheus.Metric) {
+ ch <- prometheus.MustNewConstMetric(c.testA, prometheus.GaugeValue, 1, "label_1")
+ ch <- prometheus.MustNewConstMetric(c.testB, prometheus.GaugeValue, 2, "label_2")
+}
+
+func TestEncodeVariables(t *testing.T) {
+ dump := make(map[string]interface{})
+
+ expvar.NewString("test1").Set("test1")
+ expvar.NewString("test2").Set("test2")
+
+ res := EncodeVariables()
+
+ require.NoError(t, json.Unmarshal(res.Variables, &dump))
+ require.NotEmpty(t, dump)
+
+ // dump should contains keys `test1` and `test2`
+ require.Contains(t, dump, "test1")
+ require.Equal(t, "test1", dump["test1"])
+
+ require.Contains(t, dump, "test2")
+ require.Equal(t, "test2", dump["test2"])
+}
+
+func TestEncodeConfig(t *testing.T) {
+ v := viper.New()
+ v.Set("test1", "test1")
+ v.Set("test2", "test2")
+
+ res, err := EncodeConfig(v)
+ require.NoError(t, err)
+
+ dump := make(map[string]interface{})
+ require.NoError(t, json.Unmarshal(res.Config, &dump))
+
+ require.NotEmpty(t, dump)
+
+ require.Contains(t, dump, "test1")
+ require.Equal(t, dump["test1"], "test1")
+
+ require.Contains(t, dump, "test2")
+ require.Equal(t, dump["test2"], "test2")
+}
+
+func TestEncodeAndDecodeMetrics(t *testing.T) {
+ registry := prometheus.NewRegistry()
+
+ collector := &testCollector{
+ testA: prometheus.NewDesc("test1", "test1", []string{"test1"}, prometheus.Labels{"label_1": "test1"}),
+ testB: prometheus.NewDesc("test2", "test2", []string{"test2"}, prometheus.Labels{"label_2": "test2"}),
+ }
+
+ require.NoError(t, registry.Register(collector))
+
+ gather, err := registry.Gather()
+ require.NoError(t, err)
+
+ res, err := EncodeMetrics(registry)
+ require.NoError(t, err)
+
+ metrics, err := DecodeMetrics(res)
+ require.NoError(t, err)
+
+ require.Len(t, metrics, len(gather))
+
+ { // Check that JSON encoded metrics are equal:
+ expect := new(bytes.Buffer)
+ actual := new(bytes.Buffer)
+
+ require.NoError(t, json.NewEncoder(expect).Encode(gather))
+ require.NoError(t, json.NewEncoder(actual).Encode(metrics))
+
+ require.Equal(t, expect.Bytes(), actual.Bytes())
+ }
+
+ { // Deep comparison of metrics:
+ for i := range metrics {
+ require.Equal(t, gather[i].Help, metrics[i].Help)
+ require.Equal(t, gather[i].Name, metrics[i].Name)
+ require.Equal(t, gather[i].Type, metrics[i].Type)
+
+ require.Len(t, metrics[i].Metric, len(gather[i].Metric))
+
+ for j := range metrics[i].Metric {
+ require.Equal(t, gather[i].Metric[j].Gauge, metrics[i].Metric[j].Gauge)
+ require.Len(t, metrics[i].Metric[j].Label, len(gather[i].Metric[j].Label))
+
+ for k := range metrics[i].Metric[j].Label {
+ require.Equal(t, gather[i].Metric[j].Label[k].Name, metrics[i].Metric[j].Label[k].Name)
+ require.Equal(t, gather[i].Metric[j].Label[k].Value, metrics[i].Metric[j].Label[k].Value)
+ }
+ }
+ }
+ }
+}