blob: 65f9947e0a9527feb9816e8099f7189dfd2cd1fb [file] [log] [blame]
// Copyright 2016 The LUCI 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
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dm
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"google.golang.org/protobuf/proto"
"go.chromium.org/luci/common/data/text/templateproto"
)
// IsEmpty returns true if this metadata retry message only contains
// zero-values.
func (q *Quest_Desc_Meta_Retry) IsEmpty() bool {
return q.Crashed == 0 && q.Expired == 0 && q.Failed == 0 && q.TimedOut == 0
}
// Normalize ensures that all timeouts are >= 0
func (t *Quest_Desc_Meta_Timeouts) Normalize() error {
if d := t.Start.AsDuration(); d < 0 {
return fmt.Errorf("desc.meta.timeouts.start < 0: %s", d)
}
if d := t.Run.AsDuration(); d < 0 {
return fmt.Errorf("desc.meta.timeouts.run < 0: %s", d)
}
if d := t.Stop.AsDuration(); d < 0 {
return fmt.Errorf("desc.meta.timeouts.stop < 0: %s", d)
}
return nil
}
// IsEmpty returns true if this metadata only contains zero-values.
func (q *Quest_Desc_Meta) IsEmpty() bool {
return q.AsAccount == "" && q.Retry.IsEmpty()
}
var (
// QuestIDLength is the number of encoded bytes to use. It removes the
// single padding character.
QuestIDLength = base64.URLEncoding.EncodedLen(sha256.Size) - 1
)
// QuestDescPayloadMaxLength is the maximum length (in bytes) of an
// un-normalized Quest payload.
const QuestDescPayloadMaxLength = 256 * 1024
// Normalize returns an error iff the Quest_Desc is invalid.
func (q *Quest_Desc) Normalize() error {
if q.Meta == nil {
q.Meta = &Quest_Desc_Meta{
Retry: &Quest_Desc_Meta_Retry{},
Timeouts: &Quest_Desc_Meta_Timeouts{},
}
} else {
if q.Meta.Retry == nil {
q.Meta.Retry = &Quest_Desc_Meta_Retry{}
}
if q.Meta.Timeouts == nil {
q.Meta.Timeouts = &Quest_Desc_Meta_Timeouts{}
}
}
if err := q.Meta.Timeouts.Normalize(); err != nil {
return err
}
length := len(q.Parameters) + len(q.DistributorParameters)
if length > QuestDescPayloadMaxLength {
return fmt.Errorf("quest payload is too large: %d > %d",
length, QuestDescPayloadMaxLength)
}
normed, err := templateproto.NormalizeJSON(q.Parameters, true)
if err != nil {
return fmt.Errorf("failed to normalize parameters: %s", err)
}
q.Parameters = normed
normed, err = templateproto.NormalizeJSON(q.DistributorParameters, true)
if err != nil {
return fmt.Errorf("failed to normalize distributor parameters: %s", err)
}
q.DistributorParameters = normed
return nil
}
// QuestID computes the DM compatible quest ID for this Quest_Desc. The
// Quest_Desc should already be Normalize()'d.
func (q *Quest_Desc) QuestID() string {
data, err := proto.Marshal(q)
if err != nil {
panic(err)
}
h := sha256.Sum256(data)
return base64.URLEncoding.EncodeToString(h[:])[:QuestIDLength]
}