-
Notifications
You must be signed in to change notification settings - Fork 0
/
builder_summary.go
148 lines (123 loc) · 5.15 KB
/
builder_summary.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright 2017 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 model
import (
"context"
"fmt"
"strings"
"time"
"go.chromium.org/luci/gae/service/datastore"
buildbucketpb "go.chromium.org/luci/buildbucket/proto"
"go.chromium.org/luci/common/logging"
)
// InvalidBuilderIDURL is returned if a BuilderID cannot be parsed and a URL generated.
const InvalidBuilderIDURL = "#invalid-builder-id"
// BuilderSummaryKind is the name of the datastore entity kind of the
// entity represented by the BuilderSummary struct.
const BuilderSummaryKind = "BuilderSummary"
// BuilderSummary holds builder state for the purpose of representing e.g. header consoles.
type BuilderSummary struct {
// BuilderID is the global identifier for the builder that this Build belongs to, i.e.:
// "buildbucket/<bucketname>/<buildername>"
// Matches field in BuildSummary.
BuilderID string `gae:"$id"`
// The LUCI project ID associated with this build. This is used for generating links.
ProjectID string
// LastFinishedCreated is the time the last finished build was created.
LastFinishedCreated time.Time
// LastFinishedStatus is the status of last finished build on builder.
LastFinishedStatus Status
// LastFinishedCritical is the criticality of last finished build on builder.
LastFinishedCritical buildbucketpb.Trinary
// LastFinishedBuildID is the BuildID of the BuildSummary associated with last finished build on
// the builder.
LastFinishedBuildID string
// LastFinishedExperimental indicates if the last build on this builder was
// marked as experimental.
LastFinishedExperimental bool
// Ignore unrecognized fields and strip in future writes.
_ datastore.PropertyMap `gae:"-,extra"`
}
// BuilderPool is a datastore entity that associates a BuilderSummary entity
// with a BotPool entity. BuilderPool is a separate entity from BuilderSummary
// to allow for transaction-less updates.
type BuilderPool struct {
// _entityID is always 1, as each BuildSummary entity only has a single BuilderPool child.
_entityID int `gae:"$id,1"`
// BuilderID is a datastore key pointing to the parent BuilderSummary entity.
BuilderID *datastore.Key `gae:"$parent"`
// PoolKey is a datastore key pointing to the BotPool associated with this Builder.
PoolKey *datastore.Key
}
// LastFinishedBuildIDLink returns a link to the last finished build.
func (b *BuilderSummary) LastFinishedBuildIDLink() string {
return buildIDLink(b.LastFinishedBuildID, b.ProjectID)
}
// SelfLink returns a link to the associated builder.
func (b *BuilderSummary) SelfLink() string {
if b == nil {
return InvalidBuilderIDURL
}
return BuilderIDLink(b.BuilderID, b.ProjectID)
}
// BuilderIDLink gets a builder link from builder ID and project.
// Depends on routes.go.
func BuilderIDLink(b, project string) string {
parts := strings.Split(b, "/")
if len(parts) != 3 {
return InvalidBuilderIDURL
}
if parts[0] == "buildbucket" {
return fmt.Sprintf("/p/%s/builders/%s/%s", project, parts[1], parts[2])
}
return InvalidBuilderIDURL
}
// UpdateBuilderForBuild updates the appropriate BuilderSummary for the
// provided BuildSummary, if needed.
// In particular, a BuilderSummary is updated with a BuildSummary if the latter is marked complete
// and has a more recent creation time than the one stored in the BuilderSummary.
// If there is no existing BuilderSummary for the BuildSummary provided, one is created.
// Idempotent.
// c must have a current datastore transaction.
func UpdateBuilderForBuild(c context.Context, build *BuildSummary) error {
if datastore.CurrentTransaction(c) == nil {
panic("UpdateBuilderForBuild was called outside of a transaction")
}
if !build.Summary.Status.Terminal() {
// this build did not complete yet.
// There is no new info for the builder.
// Do not even bother starting a transaction.
return nil
}
// Get or create the relevant BuilderSummary.
builder := &BuilderSummary{BuilderID: build.BuilderID}
switch err := datastore.Get(c, builder); {
case err == datastore.ErrNoSuchEntity:
logging.Warningf(c, "creating new BuilderSummary for BuilderID: %s", build.BuilderID)
case err != nil:
return err
case build.Created.Before(builder.LastFinishedCreated):
logging.Warningf(c, "message for build %s is out of order", build.BuildID)
return nil
}
// Overwrite previous value of ProjectID in case a builder has moved to
// another project.
builder.ProjectID = build.ProjectID
builder.LastFinishedBuildID = build.BuildID
builder.LastFinishedCreated = build.Created
builder.LastFinishedExperimental = build.Experimental
builder.LastFinishedStatus = build.Summary.Status
builder.LastFinishedCritical = build.Critical
return datastore.Put(c, builder)
}