@@ -17,15 +17,24 @@ package validation
1717import (
1818 "fmt"
1919 "reflect"
20+ "time"
21+
22+ "github.com/golang/protobuf/ptypes"
2023
2124 "google.golang.org/genproto/googleapis/api/metric"
2225 "google.golang.org/genproto/googleapis/monitoring/v3"
2326 "google.golang.org/genproto/googleapis/rpc/errdetails"
2427)
2528
26- // IsValidRequest verifies that the given request is valid.
27- // This means required fields are present and all fields semantically make sense.
28- func IsValidRequest (req interface {}) error {
29+ const (
30+ // Time series constraints can be found at https://cloud.google.com/monitoring/quotas#custom_metrics_quotas.
31+ maxTimeSeriesPerRequest = 200
32+ maxTimeSeriesLabelKeyBytes = 100
33+ maxTimeSeriesLabelValueBytes = 1024
34+ )
35+
36+ // ValidRequiredFields verifies that the given request contains the required fields.
37+ func ValidRequiredFields (req interface {}) error {
2938 reqReflect := reflect .ValueOf (req )
3039 requiredFields := []string {"Name" }
3140 requestName := ""
@@ -105,3 +114,116 @@ func RemoveMetricDescriptor(uploadedMetricDescriptors map[string]*metric.MetricD
105114
106115 return nil
107116}
117+
118+ // ValidateCreateTimeSeries checks that the given TimeSeries conform to the API requirements.
119+ func ValidateCreateTimeSeries (timeSeries []* monitoring.TimeSeries , descriptors map [string ]* metric.MetricDescriptor ) error {
120+ if len (timeSeries ) > maxTimeSeriesPerRequest {
121+ return statusTooManyTimeSeries
122+ }
123+
124+ for _ , ts := range timeSeries {
125+ // Check that required fields for time series are present.
126+ if ts .Metric == nil || len (ts .Points ) != 1 || ts .Resource == nil {
127+ return statusInvalidTimeSeries
128+ }
129+
130+ // Check that the metric labels follow the constraints.
131+ for k , v := range ts .Metric .Labels {
132+ if len (k ) > maxTimeSeriesLabelKeyBytes {
133+ return statusInvalidTimeSeriesLabelKey
134+ }
135+
136+ if len (v ) > maxTimeSeriesLabelValueBytes {
137+ return statusInvalidTimeSeriesLabelValue
138+ }
139+ }
140+
141+ if err := validateMetricKind (ts , descriptors ); err != nil {
142+ return err
143+ }
144+
145+ if err := validateValueType (ts .ValueType , ts .Points [0 ]); err != nil {
146+ return err
147+ }
148+
149+ if err := validatePoint (ts .MetricKind , ts .Points [0 ]); err != nil {
150+ return err
151+ }
152+ }
153+
154+ return nil
155+ }
156+
157+ // validateMetricKind check that if metric_kind is present,
158+ // it is the same as the metricKind of the associated metric.
159+ func validateMetricKind (timeSeries * monitoring.TimeSeries , descriptors map [string ]* metric.MetricDescriptor ) error {
160+ descriptor := descriptors [timeSeries .Metric .Type ]
161+ if descriptor == nil {
162+ return statusMissingMetricDescriptor
163+ }
164+ if descriptor .MetricKind != timeSeries .MetricKind {
165+ return statusInvalidTimeSeriesMetricKind
166+ }
167+
168+ return nil
169+ }
170+
171+ // validateValueType checks that if TimeSeries' "value_type" is present,
172+ // it is the same as the type of the data in the "points" field.
173+ func validateValueType (valueType metric.MetricDescriptor_ValueType , point * monitoring.Point ) error {
174+ if valueType == metric .MetricDescriptor_BOOL {
175+ if _ , ok := point .Value .Value .(* monitoring.TypedValue_BoolValue ); ! ok {
176+ return statusInvalidTimeSeriesValueType
177+ }
178+ }
179+ if valueType == metric .MetricDescriptor_INT64 {
180+ if _ , ok := point .Value .Value .(* monitoring.TypedValue_Int64Value ); ! ok {
181+ return statusInvalidTimeSeriesValueType
182+ }
183+ }
184+ if valueType == metric .MetricDescriptor_DOUBLE {
185+ if _ , ok := point .Value .Value .(* monitoring.TypedValue_DoubleValue ); ! ok {
186+ return statusInvalidTimeSeriesValueType
187+ }
188+ }
189+ if valueType == metric .MetricDescriptor_STRING {
190+ if _ , ok := point .Value .Value .(* monitoring.TypedValue_StringValue ); ! ok {
191+ return statusInvalidTimeSeriesValueType
192+ }
193+ }
194+ if valueType == metric .MetricDescriptor_DISTRIBUTION {
195+ if _ , ok := point .Value .Value .(* monitoring.TypedValue_DistributionValue ); ! ok {
196+ return statusInvalidTimeSeriesValueType
197+ }
198+ }
199+
200+ return nil
201+ }
202+
203+ // validatePoint checks that the data for the point is valid.
204+ // The specifics checks depend on the metric_kind.
205+ func validatePoint (metricKind metric.MetricDescriptor_MetricKind , point * monitoring.Point ) error {
206+ startTime , err := ptypes .Timestamp (point .Interval .StartTime )
207+ // If metric_kind is GAUGE, the startTime is optional.
208+ if err != nil && metricKind != metric .MetricDescriptor_GAUGE {
209+ return statusMalformedTimestamp
210+ }
211+ endTime , err := ptypes .Timestamp (point .Interval .EndTime )
212+ if err != nil {
213+ return statusMalformedTimestamp
214+ }
215+
216+ // If metric_kind is GAUGE, if start time is supplied, it must equal the end time.
217+ if metricKind == metric .MetricDescriptor_GAUGE {
218+ // If start time is nil, ptypes.Timestamp will return time.Unix(0, 0).UTC().
219+ if startTime != time .Unix (0 , 0 ).UTC () && ! startTime .Equal (endTime ) {
220+ return statusInvalidTimeSeriesPointGauge
221+ }
222+ }
223+
224+ // TODO (ejwang): also need checks for metric.MetricDescriptor_DELTA and metric.MetricDescriptor_CUMULATIVE,
225+ // however they involve comparing against previous time series, so we'll need to store time series in memory.
226+ // Leaving this for another PR since this PR is already quite large.
227+
228+ return nil
229+ }
0 commit comments