Validates the reference to metrics from the report config.
Also, validates that metric parts are not nil.
Change-Id: Ic172e4ebfa2189382fe78c8def7090a39d938d66
diff --git a/config/config_parser/src/config_parser/project_config.go b/config/config_parser/src/config_parser/project_config.go
index bc4a088..c5f803f 100644
--- a/config/config_parser/src/config_parser/project_config.go
+++ b/config/config_parser/src/config_parser/project_config.go
@@ -21,6 +21,7 @@
import (
"config"
"fmt"
+ "github.com/golang/glog"
"yamlpb"
)
@@ -41,9 +42,13 @@
return fmt.Errorf("Error while parsing yaml: %v", err)
}
- // TODO(azani): Validate that metrics and encodings referred to exist.
- metricIds := map[uint32]bool{}
+ // Maps metric ids to the metric's index in c.projectConfig.MetricConfigs.
+ metrics := map[uint32]uint32{}
+
+ // Set of encoding ids. Used to detect duplicates.
encodingIds := map[uint32]bool{}
+
+ // Set of report ids. Used to detect duplicates.
reportIds := map[uint32]bool{}
for i, e := range c.projectConfig.EncodingConfigs {
if encodingIds[e.Id] {
@@ -55,10 +60,14 @@
}
for i, e := range c.projectConfig.MetricConfigs {
- if metricIds[e.Id] {
+ if _, ok := metrics[e.Id]; ok {
return fmt.Errorf("Metric id '%v' is repeated in metric config entry number %v. Metric ids must be unique.", e.Id, i)
}
- metricIds[e.Id] = true
+ metrics[e.Id] = uint32(i)
+
+ if err := validateMetric(*e); err != nil {
+ return fmt.Errorf("Error validating metric %v (%v): %v", e.Name, e.Id, err)
+ }
e.CustomerId = c.customerId
e.ProjectId = c.projectId
}
@@ -70,6 +79,63 @@
reportIds[e.Id] = true
e.CustomerId = c.customerId
e.ProjectId = c.projectId
+
+ if _, ok := metrics[e.MetricId]; !ok {
+ return fmt.Errorf("Error validating report %v (%v): There is no metric id %v.", e.Name, e.Id, e.MetricId)
+ }
+
+ m := c.projectConfig.MetricConfigs[metrics[e.MetricId]]
+ if err := validateReportVariables(*e, *m); err != nil {
+ return fmt.Errorf("Error validating report %v (%v): %v", e.Name, e.Id, err)
+ }
+ }
+
+ return nil
+}
+
+func validateMetric(m config.Metric) (err error) {
+ for k, v := range m.Parts {
+ if v == nil {
+ return fmt.Errorf("Metric part '%v' is null. This is not allowed.", k)
+ }
+ }
+
+ return nil
+}
+
+// Checks that the report variables are compatible with the specific metric.
+func validateReportVariables(c config.ReportConfig, m config.Metric) (err error) {
+ if len(c.Variable) == 0 {
+ glog.Warningf("Report '%v' (Customer %v, Project %v Id %v) does not have any report variables.", c.Name, c.CustomerId, c.ProjectId, c.Id)
+ return nil
+ }
+
+ for i, v := range c.Variable {
+ if v == nil {
+ return fmt.Errorf("Report Variable in position %v is null. This is not allowed.", i)
+ }
+
+ // Check that the metric part being referenced can be found.
+ p, ok := m.Parts[v.MetricPart]
+ if !ok {
+ return fmt.Errorf("Metric part '%v' is not present in metric '%v'.", v.MetricPart, m.Name)
+ }
+
+ // Checks that if index labels are found, the metric part referred to is an index.
+ if v.IndexLabels != nil && len(v.IndexLabels.Labels) > 0 && p.DataType != config.MetricPart_INDEX {
+ return fmt.Errorf("Report variable %v has index labels specified "+
+ "which implies referring to an index metric part. But metric part '%v'"+
+ "of metric '%v' (%v) is of type %v.",
+ i, v.MetricPart, m.Name, m.Id, config.MetricPart_DataType_name[int32(p.DataType)])
+ }
+
+ // Checks that if RAPPOR candidates are found, the metric part referred to is a string.
+ if v.RapporCandidates != nil && len(v.RapporCandidates.Candidates) > 0 && p.DataType != config.MetricPart_STRING {
+ return fmt.Errorf("Report variable %v has RAPPOR candidates specified "+
+ "which implies referring to a string metric part. But metric part '%v'"+
+ "of metric '%v' (%v) is of type %v.",
+ i, v.MetricPart, m.Name, m.Id, config.MetricPart_DataType_name[int32(p.DataType)])
+ }
}
return nil
diff --git a/config/config_parser/src/config_parser/project_config_test.go b/config/config_parser/src/config_parser/project_config_test.go
index d79e133..6da6b0c 100644
--- a/config/config_parser/src/config_parser/project_config_test.go
+++ b/config/config_parser/src/config_parser/project_config_test.go
@@ -39,8 +39,9 @@
- id: 2
report_configs:
- id: 1
- metric_id: 5
+ metric_id: 1
- id: 2
+ metric_id: 1
`
c := projectConfig{
customerId: 1,
@@ -91,12 +92,13 @@
CustomerId: 1,
ProjectId: 10,
Id: 1,
- MetricId: 5,
+ MetricId: 1,
},
&config.ReportConfig{
CustomerId: 1,
ProjectId: 10,
Id: 2,
+ MetricId: 1,
},
},
}
@@ -201,3 +203,122 @@
t.Error("Accepted non-unique report id.")
}
}
+
+// Test that a report config with an unknown metric id gets rejected.
+func TestParseProjectConfigUnknownMetricIdInReportConfig(t *testing.T) {
+ y := `
+report_configs:
+- id: 1
+ metric_id: 10
+`
+ c := projectConfig{}
+
+ if err := parseProjectConfig(y, &c); err == nil {
+ t.Error("Accepted report config with unknown metric id.")
+ }
+}
+
+// Check that valid report variables are accepted.
+func TestValidateReportVariables(t *testing.T) {
+ m := config.Metric{
+ Parts: map[string]*config.MetricPart{
+ "int_part": &config.MetricPart{DataType: config.MetricPart_INT},
+ "string_part": &config.MetricPart{DataType: config.MetricPart_STRING},
+ "blob_part": &config.MetricPart{DataType: config.MetricPart_BLOB},
+ "index_part": &config.MetricPart{DataType: config.MetricPart_INDEX},
+ },
+ }
+
+ c := config.ReportConfig{
+ Variable: []*config.ReportVariable{
+ &config.ReportVariable{
+ MetricPart: "int_part",
+ },
+ &config.ReportVariable{
+ MetricPart: "index_part",
+ IndexLabels: &config.IndexLabels{Labels: map[uint32]string{0: "zero"}},
+ },
+ &config.ReportVariable{
+ MetricPart: "string_part",
+ RapporCandidates: &config.RapporCandidateList{Candidates: []string{"hello"}},
+ },
+ },
+ }
+
+ if err := validateReportVariables(c, m); err != nil {
+ t.Error(err)
+ }
+}
+
+// Test that a report variable referring to an unknown metric part will be rejected.
+func TestValidateReportVariablesUnknownMetricPart(t *testing.T) {
+ m := config.Metric{}
+
+ c := config.ReportConfig{
+ Variable: []*config.ReportVariable{
+ &config.ReportVariable{
+ MetricPart: "int_part",
+ },
+ },
+ }
+
+ if err := validateReportVariables(c, m); err == nil {
+ t.Error("Report with unknown metric part was accepted.")
+ }
+}
+
+// Test that if a report variable specifies index labels, the metric part it
+// refers to must be of type index.
+func TestValidateReportVariablesIndexLablesNonIndexMetric(t *testing.T) {
+ m := config.Metric{
+ Parts: map[string]*config.MetricPart{
+ "int_part": &config.MetricPart{DataType: config.MetricPart_INT},
+ },
+ }
+
+ c := config.ReportConfig{
+ Variable: []*config.ReportVariable{
+ &config.ReportVariable{
+ MetricPart: "int_part",
+ IndexLabels: &config.IndexLabels{Labels: map[uint32]string{0: "zero"}},
+ },
+ },
+ }
+
+ if err := validateReportVariables(c, m); err == nil {
+ t.Error("Report with with index labels specified for a non-index metric part accepted.")
+ }
+}
+
+// Test that if a report variable specifies rappor candidates, the metric part
+// it refers to must be of type string.
+func TestValidateReportVarialesRapporCandidatesNonStringMetric(t *testing.T) {
+ m := config.Metric{
+ Parts: map[string]*config.MetricPart{
+ "int_part": &config.MetricPart{DataType: config.MetricPart_INT},
+ },
+ }
+
+ c := config.ReportConfig{
+ Variable: []*config.ReportVariable{
+ &config.ReportVariable{
+ MetricPart: "int_part",
+ RapporCandidates: &config.RapporCandidateList{Candidates: []string{"alpha"}},
+ },
+ },
+ }
+
+ if err := validateReportVariables(c, m); err == nil {
+ t.Error("Report with with rappor candidates specified for a non-string metric part accepted.")
+ }
+}
+
+func TestValidateMetricNoNilMetricPart(t *testing.T) {
+ m := config.Metric{
+ Parts: map[string]*config.MetricPart{"int_part": nil},
+ }
+
+ if err := validateMetric(m); err == nil {
+ t.Error("Metric with nil metric part was accepted.")
+ }
+}