Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(211)

Side by Side Diff: milo/common/config.go

Issue 2982183002: Milo: Store console defs as their own entities (Closed)
Patch Set: Rebase fixes and Working Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « milo/common/acl_test.go ('k') | milo/common/config_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2016 The LUCI Authors. 1 // Copyright 2016 The LUCI Authors.
2 // 2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License. 4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at 5 // You may obtain a copy of the License at
6 // 6 //
7 // http://www.apache.org/licenses/LICENSE-2.0 7 // http://www.apache.org/licenses/LICENSE-2.0
8 // 8 //
9 // Unless required by applicable law or agreed to in writing, software 9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, 10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and 12 // See the License for the specific language governing permissions and
13 // limitations under the License. 13 // limitations under the License.
14 14
15 package common 15 package common
16 16
17 import ( 17 import (
18 "fmt" 18 "fmt"
19 "strings"
19 "time" 20 "time"
20 21
21 "github.com/golang/protobuf/proto" 22 "github.com/golang/protobuf/proto"
22 "golang.org/x/net/context" 23 "golang.org/x/net/context"
23 24
24 "github.com/luci/gae/service/datastore" 25 "github.com/luci/gae/service/datastore"
25 "github.com/luci/gae/service/info" 26 "github.com/luci/gae/service/info"
27 configInterface "github.com/luci/luci-go/common/config"
26 "github.com/luci/luci-go/common/data/caching/proccache" 28 "github.com/luci/luci-go/common/data/caching/proccache"
29 "github.com/luci/luci-go/common/data/stringset"
27 "github.com/luci/luci-go/common/errors" 30 "github.com/luci/luci-go/common/errors"
28 "github.com/luci/luci-go/common/logging" 31 "github.com/luci/luci-go/common/logging"
29 "github.com/luci/luci-go/luci_config/server/cfgclient" 32 "github.com/luci/luci-go/luci_config/server/cfgclient"
30 "github.com/luci/luci-go/luci_config/server/cfgclient/backend" 33 "github.com/luci/luci-go/luci_config/server/cfgclient/backend"
31 "github.com/luci/luci-go/luci_config/server/cfgclient/textproto"
32 34
33 "github.com/luci/luci-go/milo/api/config" 35 "github.com/luci/luci-go/milo/api/config"
34 ) 36 )
35 37
36 // Project is a LUCI project. 38 // Console is af datastore entity representing a single console.
37 type Project struct { 39 type Console struct {
38 » // ID of the project, as per self defined. This is the luci-config name . 40 » // Parent is a key to the parent Project entity where this console was
41 » // defined in.
42 » Parent *datastore.Key `gae:"$parent"`
43 » // ID is the ID of the console.
39 ID string `gae:"$id"` 44 ID string `gae:"$id"`
40 » // Data is the Project data in protobuf binary format. 45 » // RepoURL and Ref combined defines the commits the show up on the left
41 » Data []byte `gae:",noindex"` 46 » // hand side of a Console.
42 » // Revision is the latest revision we have of this project's config. 47 » RepoURL string
48 » // RepoURL and Ref combined defines the commits the show up on the left
49 » // hand side of a Console.
50 » Ref string
51 » // ManifestName is the name of the manifest to look for when querying fo r
52 » // builds under this console.
53 » ManifestName string
54 » // URL is the URL to the luci-config definition of this console.
55 » URL string
56 » // Revision is the luci-config reivision from when this Console was retr ieved.
43 Revision string 57 Revision string
58 // Builders is a list of universal builder IDs.
59 Builders []string
60 }
61
62 // GetProjectName retrieves the project name of the console out of the Console's
63 // parent key.
64 func (con *Console) GetProjectName() string {
65 return con.Parent.StringID()
66 }
67
68 // NewConsole creates a fully populated console out of the luci-config proto
69 // definition of a console.
70 func NewConsole(project *datastore.Key, URL, revision string, con *config.Consol e) *Console {
71 return &Console{
72 Parent: project,
73 ID: con.ID,
74 RepoURL: con.RepoURL,
75 Ref: con.Ref,
76 ManifestName: con.ManifestName,
77 Revision: revision,
78 URL: URL,
79 Builders: BuilderFromProto(con.Builders),
80 }
81 }
82
83 // BuilderFromProto tranforms a luci-config proto builder format into the datast ore
84 // format.
85 func BuilderFromProto(cb []*config.Builder) []string {
86 builders := make([]string, len(cb))
87 for i, b := range cb {
88 builders[i] = b.Name
89 }
90 return builders
91 }
92
93 // LuciConfigURL returns a user friendly URL that specifies where to view
94 // this console definition.
95 func LuciConfigURL(c context.Context, configSet, path, revision string) string {
96 // TODO(hinoka): This shouldn't be hardcoded, instead we should get the
97 // luci-config instance from the context. But we only use this instance at
98 // the moment so it is okay for now.
99 // TODO(hinoka): The UI doesn't allow specifying paths and revision yet. Add
100 // that in when it is supported.
101 return fmt.Sprintf("https://luci-config.appspot.com/newui#/%s", configSe t)
44 } 102 }
45 103
46 // The key for the service config entity in datastore. 104 // The key for the service config entity in datastore.
47 const ServiceConfigID = "service_config" 105 const ServiceConfigID = "service_config"
48 106
49 // ServiceConfig is a container for the instance's service config. 107 // ServiceConfig is a container for the instance's service config.
50 type ServiceConfig struct { 108 type ServiceConfig struct {
51 // ID is the datastore key. This should be static, as there should only be 109 // ID is the datastore key. This should be static, as there should only be
52 // one service config. 110 // one service config.
53 ID string `gae:"$id"` 111 ID string `gae:"$id"`
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after
171 }, nil) 229 }, nil)
172 230
173 if err != nil { 231 if err != nil {
174 return nil, fmt.Errorf("failed to update config entry in transac tion", err) 232 return nil, fmt.Errorf("failed to update config entry in transac tion", err)
175 } 233 }
176 logging.Infof(c, "successfully updated to new config") 234 logging.Infof(c, "successfully updated to new config")
177 235
178 return settings, nil 236 return settings, nil
179 } 237 }
180 238
181 // UpdateProjectConfigs internal project configuration based off luci-config. 239 // updateProjectConsoles updates all of the consoles for a given project,
182 // update updates Milo's configuration based off luci config. This includes 240 // and then returns a set of known console names.
183 // scanning through all project and extract all console configs. 241 func updateProjectConsoles(c context.Context, projectName string, cfg *configInt erface.Config) (stringset.Set, error) {
184 func UpdateProjectConfigs(c context.Context) error { 242 » proj := config.Project{}
243 » if err := proto.UnmarshalText(cfg.Content, &proj); err != nil {
244 » » return nil, errors.Annotate(err, "unmarshalling proto").Err()
245 » }
246
247 » // Keep a list of known consoles so we can prune deleted ones later.
248 » knownConsoles := stringset.New(len(proj.Consoles))
249 » // Iterate through all the proto consoles, adding and replacing the
250 » // known ones if needed.
251 » parentKey := datastore.MakeKey(c, "Project", projectName)
252 » for _, pc := range proj.Consoles {
253 » » knownConsoles.Add(pc.ID)
254 » » con, err := GetConsole(c, projectName, pc.ID)
255 » » switch err {
256 » » case datastore.ErrNoSuchEntity:
257 » » » // continue
258 » » case nil:
259 » » » // Check if revisions match, if so just skip it.
260 » » » if con.Revision == cfg.Revision {
261 » » » » continue
262 » » » }
263 » » default:
264 » » » return nil, errors.Annotate(err, "checking %s", pc.ID).E rr()
265 » » }
266 » » URL := LuciConfigURL(c, cfg.ConfigSet, cfg.Path, cfg.Revision)
267 » » con = NewConsole(parentKey, URL, cfg.Revision, pc)
268 » » if err = datastore.Put(c, con); err != nil {
269 » » » return nil, errors.Annotate(err, "saving %s", pc.ID).Err ()
270 » » } else {
271 » » » logging.Infof(c, "saved a new %s / %s (revision %s)", pr ojectName, con.ID, cfg.Revision)
272 » » }
273 » }
274 » return knownConsoles, nil
275 }
276
277 // UpdateConsoles updates internal console definitions entities based off luci-c onfig.
278 func UpdateConsoles(c context.Context) error {
185 cfgName := info.AppID(c) + ".cfg" 279 cfgName := info.AppID(c) + ".cfg"
186 280
187 » var ( 281 » logging.Debugf(c, "fetching configs for %s", cfgName)
188 » » configs []*config.Project 282 » // Acquire the raw config client.
189 » » metas []*cfgclient.Meta 283 » lucicfg := backend.Get(c).GetConfigInterface(c, backend.AsService)
190 » » merr errors.MultiError 284 » // Project configs for Milo contains console definitions.
191 » ) 285 » configs, err := lucicfg.GetProjectConfigs(c, cfgName, false)
286 » if err != nil {
287 » » return errors.Annotate(err, "while fetching project configs").Er r()
288 » }
289 » logging.Infof(c, "got %d project configs", len(configs))
192 290
193 » logging.Debugf(c, "fetching configs for %s", cfgName) 291 » merr := errors.MultiError{}
194 » if err := cfgclient.Projects(c, cfgclient.AsService, cfgName, textproto. Slice(&configs), &metas); err != nil { 292 » knownProjects := map[string]stringset.Set{}
195 » » merr = err.(errors.MultiError) 293 » // Iterate through each project config, extracting the console definitio n.
196 » » // Some configs errored out, but we can continue with the ones t hat work still. 294 » for _, cfg := range configs {
295 » » // This looks like "projects/<project name>"
296 » » splitPath := strings.SplitN(cfg.ConfigSet, "/", 2)
297 » » if len(splitPath) != 2 {
298 » » » return fmt.Errorf("Invalid config set path %s", cfg.Conf igSet)
299 » » }
300 » » projectName := splitPath[1]
301 » » knownProjects[projectName] = nil
302 » » if kp, err := updateProjectConsoles(c, projectName, &cfg); err ! = nil {
303 » » » err = errors.Annotate(err, "processing project %s", cfg. ConfigSet).Err()
304 » » » merr = append(merr, err)
305 » » } else {
306 » » » knownProjects[projectName] = kp
307 » » }
197 } 308 }
198 309
199 » // A map of project ID to project. 310 » // Delete all the consoles that no longer exists or are part of deleted projects.
200 » projects := map[string]*Project{} 311 » toDelete := []*datastore.Key{}
201 » for i, proj := range configs { 312 » err = datastore.Run(c, datastore.NewQuery("Console"), func(key *datastor e.Key) error {
202 » » meta := metas[i] 313 » » proj := key.Parent().StringID()
203 » » projectName, _, _ := meta.ConfigSet.SplitProject() 314 » » id := key.StringID()
204 » » name := string(projectName) 315 » » // If this console is either:
205 » » projects[name] = nil 316 » » // 1. In a project that no longer exists, or
206 » » if merr != nil && merr[i] != nil { 317 » » // 2. Not in the project, then delete it.
207 » » » logging.WithError(merr[i]).Warningf(c, "skipping %s due to error", name) 318 » » knownConsoles, ok := knownProjects[proj]
208 » » » continue 319 » » if !ok {
320 » » » logging.Infof(
321 » » » » c, "deleting %s/%s because the project no longer exists", proj, id)
322 » » » toDelete = append(toDelete, key)
323 » » » return nil
209 } 324 }
210 325 » » if knownConsoles == nil {
211 » » p := &Project{ 326 » » » // The project exists but we couldn't check it this time . Skip it and
212 » » » ID: name, 327 » » » // try again the next cron cycle.
213 » » » Revision: meta.Revision, 328 » » » return nil
214 } 329 }
215 330 » » if !knownConsoles.Has(id) {
216 » » logging.Infof(c, "prossing %s", name) 331 » » » logging.Infof(
217 332 » » » » c, "deleting %s/%s because the console no longer exists", proj, id)
218 » » var err error 333 » » » toDelete = append(toDelete, key)
219 » » p.Data, err = proto.Marshal(proj)
220 » » if err != nil {
221 » » » logging.WithError(err).Errorf(c, "Encountered error whil e processing project %s", name)
222 » » » // Set this to nil to signal this project exists but we don't want to update it.
223 » » » projects[name] = nil
224 » » » continue
225 } 334 }
226 » » projects[name] = p 335 » » return nil
336 » })
337 » if err != nil {
338 » » merr = append(merr, err)
339 » } else if err := datastore.Delete(c, toDelete); err != nil {
340 » » merr = append(merr, err)
227 } 341 }
228 342
229 » // Now load all the data into the datastore. 343 » // Print some stats.
230 » projs := make([]*Project, 0, len(projects)) 344 » processedConsoles := 0
231 » for _, proj := range projects { 345 » for _, cons := range knownProjects {
232 » » if proj != nil { 346 » » processedConsoles += cons.Len()
233 » » » projs = append(projs, proj)
234 » » }
235 } 347 }
236 » if err := datastore.Put(c, projs); err != nil { 348 » logging.Infof(
237 » » return err 349 » » c, "processed %d consoles over %d projects", len(knownProjects), processedConsoles)
350
351 » if len(merr) == 0 {
352 » » return nil
238 } 353 }
239 354 » return merr
240 » // Delete entries that no longer exist.
241 » q := datastore.NewQuery("Project").KeysOnly(true)
242 » allProjs := []Project{}
243 » datastore.GetAll(c, q, &allProjs)
244 » toDelete := []Project{}
245 » for _, proj := range allProjs {
246 » » if _, ok := projects[proj.ID]; !ok {
247 » » » toDelete = append(toDelete, proj)
248 » » }
249 » }
250 » return datastore.Delete(c, toDelete)
251 } 355 }
252 356
253 // GetAllProjects returns all registered projects. 357 // GetAllConsoles returns all registered projects with the builder name.
254 func GetAllProjects(c context.Context) ([]*config.Project, error) { 358 // If builderName is empty, then this retrieves all Consoles.
255 » q := datastore.NewQuery("Project") 359 func GetAllConsoles(c context.Context, builderName string) ([]*Console, error) {
360 » q := datastore.NewQuery("Console")
361 » if builderName != "" {
362 » » q = q.Eq("Builders", builderName)
363 » }
256 q.Order("ID") 364 q.Order("ID")
257 365 » con := []*Console{}
258 » ps := []*Project{} 366 » err := datastore.GetAll(c, q, &con)
259 » err := datastore.GetAll(c, q, &ps) 367 » return con, err
260 » if err != nil {
261 » » return nil, err
262 » }
263 » results := make([]*config.Project, len(ps))
264 » for i, p := range ps {
265 » » results[i] = &config.Project{}
266 » » if err := proto.Unmarshal(p.Data, results[i]); err != nil {
267 » » » return nil, err
268 » » }
269 » }
270 » return results, nil
271 } 368 }
272 369
273 // GetProject returns the requested project. 370 // GetConsole returns the requested console.
274 func GetProject(c context.Context, projName string) (*config.Project, error) { 371 func GetConsole(c context.Context, proj, id string) (*Console, error) {
275 » // Next, Try datastore 372 » // TODO(hinoka): Memcache this.
276 » p := Project{ID: projName} 373 » con := Console{
277 » if err := datastore.Get(c, &p); err != nil { 374 » » Parent: datastore.MakeKey(c, "Project", proj),
278 » » return nil, err 375 » » ID: id,
279 } 376 }
280 » mp := config.Project{} 377 » err := datastore.Get(c, &con)
281 » if err := proto.Unmarshal(p.Data, &mp); err != nil { 378 » return &con, err
282 » » return nil, err
283 » }
284
285 » return &mp, nil
286 } 379 }
287
288 // GetConsole returns the requested console instance.
289 func GetConsole(c context.Context, projName, consoleName string) (*config.Consol e, error) {
290 p, err := GetProject(c, projName)
291 if err != nil {
292 return nil, err
293 }
294 for _, cs := range p.Consoles {
295 if cs.Name == consoleName {
296 return cs, nil
297 }
298 }
299 return nil, fmt.Errorf("Console %s not found in project %s", consoleName , projName)
300 }
301
302 // ProjectConsole is a simple tuple type for GetConsolesForBuilder.
303 type ProjectConsole struct {
304 ProjectID string
305 Console *config.Console
306 }
307
308 // GetConsolesForBuilder retrieves all the console definitions that this builder
309 // belongs to.
310 func GetConsolesForBuilder(c context.Context, builderName string) ([]*ProjectCon sole, error) {
311 projs, err := GetAllProjects(c)
312 if err != nil {
313 return nil, err
314 }
315 ret := []*ProjectConsole{}
316 for _, p := range projs {
317 for _, con := range p.Consoles {
318 for _, b := range con.Builders {
319 if b.Name == builderName {
320 ret = append(ret, &ProjectConsole{p.ID, con})
321 }
322 }
323 }
324 }
325 return ret, nil
326 }
OLDNEW
« no previous file with comments | « milo/common/acl_test.go ('k') | milo/common/config_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698