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

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

Issue 2982183002: Milo: Store console defs as their own entities (Closed)
Patch Set: Remove Validate 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"
27 "github.com/luci/luci-go/common/errors" 29 "github.com/luci/luci-go/common/errors"
28 "github.com/luci/luci-go/common/logging" 30 "github.com/luci/luci-go/common/logging"
29 "github.com/luci/luci-go/luci_config/server/cfgclient" 31 "github.com/luci/luci-go/luci_config/server/cfgclient"
30 "github.com/luci/luci-go/luci_config/server/cfgclient/backend" 32 "github.com/luci/luci-go/luci_config/server/cfgclient/backend"
31 "github.com/luci/luci-go/luci_config/server/cfgclient/textproto"
32 33
33 "github.com/luci/luci-go/milo/api/config" 34 "github.com/luci/luci-go/milo/api/config"
34 ) 35 )
35 36
36 // Project is a LUCI project. 37 // Project is a single luci-config project name. This is only used as a way
38 // to index Console definitions, as it is the parent entity.
37 type Project struct { 39 type Project struct {
38 » // ID of the project, as per self defined. This is the luci-config name . 40 » // ID is the name of the project
39 ID string `gae:"$id"` 41 ID string `gae:"$id"`
40 » // Data is the Project data in protobuf binary format. 42 }
41 » Data []byte `gae:",noindex"` 43
42 » // Revision is the latest revision we have of this project's config. 44 // maybePutProject makes the project if it doesn't exist.
45 func maybePutProject(c context.Context, id string) error {
iannucci 2017/07/19 22:54:10 The Project only has the ID... why bother putting
Ryan Tseng 2017/07/20 00:15:15 Removed.
46 » return datastore.RunInTransaction(c, func(context.Context) error {
47 » » p := Project{ID: id}
48 » » if err := datastore.Get(c, &p); err == datastore.ErrNoSuchEntity {
49 » » » return datastore.Put(c, &p)
50 » » } else {
51 » » » return err
52 » » }
53 » }, nil)
54 }
55
56 // Console is af datastore entity representing a single console.
57 type Console struct {
58 » // Parent is a key to the parent project entity where this console was
iannucci 2017/07/19 22:54:10 capital Project (since its a type)
Ryan Tseng 2017/07/20 00:15:15 Done.
59 » // defined in.
60 » Parent *datastore.Key `gae:"$parent"`
61 » // ID is the ID of the console.
62 » ID string `gae:"$id"`
63 » // RepoURL and Ref combined defines the commits the show up on the left
64 » // hand side of a Console.
65 » RepoURL string
66 » // RepoURL and Ref combined defines the commits the show up on the left
67 » // hand side of a Console.
68 » Ref string
69 » // ManifestName is the name of the manifest to look for when querying fo r
70 » // builds under this console.
71 » ManifestName string
72 » // URL is the URL to the luci-config definition of this console.
73 » URL string
74 » // Revision is the luci-config reivision from when this Console was retr ieved.
43 Revision string 75 Revision string
76 // Builders is a list of universal builder IDs.
77 Builders []string
78 }
79
80 // GetProjectName retrieves the project name of the console out of the Console's
81 // parent key.
82 func (con *Console) GetProjectName() string {
83 return con.Parent.StringID()
84 }
85
86 // NewConsole creates a fully populated console out of the luci-config proto
87 // definition of a console.
88 func NewConsole(project *datastore.Key, URL, revision string, con *config.Consol e) *Console {
89 return &Console{
90 Parent: project,
91 ID: con.ID,
92 RepoURL: con.RepoURL,
93 Ref: con.Ref,
94 ManifestName: con.ManifestName,
95 Revision: revision,
96 URL: URL,
97 Builders: BuilderFromProto(con.Builders),
98 }
99 }
100
101 // BuilderFromProto tranforms a luci-config proto builder format into the datast ore
102 // format.
103 func BuilderFromProto(cb []*config.Builder) []string {
104 builders := make([]string, len(cb))
105 for i, b := range cb {
106 builders[i] = b.Name
107 }
108 return builders
109 }
110
111 // LuciConfigURL returns a user friendly URL that specifies where to view
112 // this console definition.
113 func LuciConfigURL(c context.Context, configSet, path, revision string) string {
114 // TODO(hinoka): This shouldn't be hardcoded, instead we should get the
115 // luci-config instance from the context. But we only use this instance at
116 // the moment so it is okay for now.
117 // TODO(hinoka): The UI doesn't allow specifying paths and revision yet. Add
118 // that in when it is supported.
119 return fmt.Sprintf("https://luci-config.appspot.com/newui#/%s", configSe t)
44 } 120 }
45 121
46 // The key for the service config entity in datastore. 122 // The key for the service config entity in datastore.
47 const ServiceConfigID = "service_config" 123 const ServiceConfigID = "service_config"
48 124
49 // ServiceConfig is a container for the instance's service config. 125 // ServiceConfig is a container for the instance's service config.
50 type ServiceConfig struct { 126 type ServiceConfig struct {
51 // ID is the datastore key. This should be static, as there should only be 127 // ID is the datastore key. This should be static, as there should only be
52 // one service config. 128 // one service config.
53 ID string `gae:"$id"` 129 ID string `gae:"$id"`
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after
171 }, nil) 247 }, nil)
172 248
173 if err != nil { 249 if err != nil {
174 return nil, fmt.Errorf("failed to update config entry in transac tion", err) 250 return nil, fmt.Errorf("failed to update config entry in transac tion", err)
175 } 251 }
176 logging.Infof(c, "successfully updated to new config") 252 logging.Infof(c, "successfully updated to new config")
177 253
178 return settings, nil 254 return settings, nil
179 } 255 }
180 256
181 // UpdateProjectConfigs internal project configuration based off luci-config. 257 func updateProjectConsoles(c context.Context, projectName string, cfg *configInt erface.Config) error {
182 // update updates Milo's configuration based off luci config. This includes 258 » // Ensure that the project exists as an entity.
iannucci 2017/07/19 22:54:10 but y?
Ryan Tseng 2017/07/20 00:15:16 Removed
183 // scanning through all project and extract all console configs. 259 » if err := maybePutProject(c, projectName); err != nil {
184 func UpdateProjectConfigs(c context.Context) error { 260 » » return errors.Annotate(err, "adding project entity").Err()
261 » }
262 » proj := config.Project{}
263 » if err := proto.UnmarshalText(cfg.Content, &proj); err != nil {
264 » » return errors.Annotate(err, "unmarshalling proto").Err()
265 » }
266
267 » // Keep a list of known consoles so we can prune unknown ones later.
268 » knownConsoles := make(map[string]bool, len(proj.Consoles))
iannucci 2017/07/19 22:54:09 stringset.New is handy (in luci-go/common/data/str
Ryan Tseng 2017/07/20 00:15:15 Done.
269 » // Iterate through all the proto consoles, adding and replacing the
270 » // known ones if needed. If any consoles errors out, then the whole pro ject fails.
iannucci 2017/07/19 22:54:09 'whole project fails' but not really; this isn't i
Ryan Tseng 2017/07/20 00:15:15 Done.
271 » parentKey := datastore.MakeKey(c, "Project", projectName)
272 » for _, pc := range proj.Consoles {
273 » » knownConsoles[pc.ID] = true
274 » » con, err := GetConsole(c, projectName, pc.ID)
275 » » switch err {
276 » » case datastore.ErrNoSuchEntity:
277 » » » // continue
278 » » case nil:
279 » » » // Check if revisions match, if so just skip it.
280 » » » if con.Revision == cfg.Revision {
281 » » » » continue
282 » » » }
283 » » default:
284 » » » return errors.Annotate(err, "checking %s", pc.ID).Err()
285 » » }
286 » » URL := LuciConfigURL(c, cfg.ConfigSet, cfg.Path, cfg.Revision)
287 » » con = NewConsole(parentKey, URL, cfg.Revision, pc)
288 » » if err = datastore.Put(c, con); err != nil {
289 » » » return errors.Annotate(err, "saving %s", pc.ID).Err()
290 » » } else {
291 » » » logging.Infof(c, "saved a new %s / %s (revision %s)", pr ojectName, con.ID, cfg.Revision)
292 » » }
293 » }
294
295 » // Now delete all of the extra consoles.
296 » q := datastore.NewQuery("Console")
297 » q = q.Ancestor(parentKey)
298 » q = q.KeysOnly(true)
iannucci 2017/07/19 22:54:10 you can string these together btw q := datastore.
Ryan Tseng 2017/07/20 00:15:16 Yeah, I like doing them line by line tho, i though
299 » existingConsoles := []*Console{}
iannucci 2017/07/19 22:54:10 make this []*datastore.Key{}
Ryan Tseng 2017/07/20 00:15:15 Done.
300 » if err := datastore.GetAll(c, q, &existingConsoles); err != nil {
301 » » return err
302 » }
303 » for _, ec := range existingConsoles {
304 » » if _, ok := knownConsoles[ec.ID]; !ok {
iannucci 2017/07/19 22:54:10 make a new []*datastore.Key{}, and append to-be-de
Ryan Tseng 2017/07/20 00:15:15 Done.
305 » » » // This console no longer exists, delete it.
306 » » » logging.Infof(c, "deleting console %s / %s", projectName , ec.ID)
307 » » » if err := datastore.Delete(c, ec); err != nil {
308 » » » » return err
309 » » » }
310 » » }
311 » }
312 » return nil
iannucci 2017/07/19 22:54:10 do a single `datastore.Delete(c, keysToDelete)`
Ryan Tseng 2017/07/20 00:15:15 Done.
313 }
314
315 // UpdateConsoles updates internal console definitions entities based off luci-c onfig.
316 func UpdateConsoles(c context.Context) error {
185 cfgName := info.AppID(c) + ".cfg" 317 cfgName := info.AppID(c) + ".cfg"
186 318
187 » var ( 319 » logging.Debugf(c, "fetching configs for %s", cfgName)
188 » » configs []*config.Project 320 » // Acquire the raw config client.
189 » » metas []*cfgclient.Meta 321 » lucicfg := backend.Get(c).GetConfigInterface(c, backend.AsService)
190 » » merr errors.MultiError 322 » // Project configs for Milo contains console definitions.
191 » ) 323 » configs, err := lucicfg.GetProjectConfigs(c, cfgName, false)
324 » if err != nil {
325 » » return errors.Annotate(err, "while fetching project configs").Er r()
326 » }
327 » logging.Infof(c, "got %d project configs", len(configs))
192 328
193 » logging.Debugf(c, "fetching configs for %s", cfgName) 329 » merr := errors.MultiError{}
194 » if err := cfgclient.Projects(c, cfgclient.AsService, cfgName, textproto. Slice(&configs), &metas); err != nil { 330 » knownProjects := map[string]bool{}
195 » » merr = err.(errors.MultiError) 331 » // 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. 332 » for _, cfg := range configs {
333 » » // This looks like "projects/<project name>"
334 » » splitPath := strings.SplitN(cfg.ConfigSet, "/", 2)
335 » » if len(splitPath) != 2 {
336 » » » return fmt.Errorf("Invalid config set path %s", cfg.Conf igSet)
337 » » }
338 » » projectName := splitPath[1]
339 » » knownProjects[projectName] = true
340 » » if err := updateProjectConsoles(c, projectName, &cfg); err != ni l {
341 » » » err = errors.Annotate(err, "processing project %s", cfg. ConfigSet).Err()
342 » » » merr = append(merr, err)
343 » » }
197 } 344 }
198 345
199 » // A map of project ID to project. 346 » // Delete all the projects that no longer exist.
200 » projects := map[string]*Project{} 347 » projects := []*Project{}
201 » for i, proj := range configs { 348 » q := datastore.NewQuery("Project")
iannucci 2017/07/19 22:54:09 projectKey := datastore.MakeKey("Project", projNam
Ryan Tseng 2017/07/20 00:15:16 Project removed.
202 » » meta := metas[i] 349 » if err := datastore.GetAll(c, q, &projects); err != nil {
203 » » projectName, _, _ := meta.ConfigSet.SplitProject() 350 » » return append(merr, err)
204 » » name := string(projectName)
205 » » projects[name] = nil
206 » » if merr != nil && merr[i] != nil {
207 » » » logging.WithError(merr[i]).Warningf(c, "skipping %s due to error", name)
208 » » » continue
209 » » }
210
211 » » p := &Project{
212 » » » ID: name,
213 » » » Revision: meta.Revision,
214 » » }
215
216 » » logging.Infof(c, "prossing %s", name)
217
218 » » var err error
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 » » }
226 » » projects[name] = p
227 } 351 }
228
229 // Now load all the data into the datastore.
230 projs := make([]*Project, 0, len(projects))
231 for _, proj := range projects { 352 for _, proj := range projects {
232 » » if proj != nil { 353 » » if _, ok := knownProjects[proj.ID]; !ok {
233 » » » projs = append(projs, proj) 354 » » » // Delete all of the project's consoles, then the projec t itself.
355 » » » parentKey := datastore.KeyForObj(c, &proj)
356 » » » q := datastore.NewQuery("Console")
357 » » » q = q.Ancestor(parentKey)
358 » » » q = q.KeysOnly(true)
359 » » » projectConsoles := []*Console{}
360 » » » if err := datastore.GetAll(c, q, &projectConsoles); err != nil {
361 » » » » return append(merr, err)
362 » » » }
363 » » » logging.Infof(c, "deleting project %s and %s", proj.ID, &projectConsoles)
364 » » » if err := datastore.Delete(c, &projectConsoles); err != nil {
365 » » » » return append(merr, err)
366 » » » }
367 » » » if err := datastore.Delete(c, &proj); err != nil {
368 » » » » return append(merr, err)
369 » » » }
234 } 370 }
235 } 371 }
236 » if err := datastore.Put(c, projs); err != nil { 372 » if len(merr) == 0 {
237 » » return err 373 » » return nil
238 } 374 }
239 375 » 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 } 376 }
252 377
253 // GetAllProjects returns all registered projects. 378 // GetAllConsoles returns all registered projects.
254 func GetAllProjects(c context.Context) ([]*config.Project, error) { 379 func GetAllConsoles(c context.Context) ([]*Console, error) {
255 » q := datastore.NewQuery("Project") 380 » q := datastore.NewQuery("Console")
256 q.Order("ID") 381 q.Order("ID")
257 382 » con := []*Console{}
258 » ps := []*Project{} 383 » err := datastore.GetAll(c, q, &con)
259 » err := datastore.GetAll(c, q, &ps) 384 » 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 } 385 }
272 386
273 // GetProject returns the requested project. 387 // GetConsole returns the requested console.
274 func GetProject(c context.Context, projName string) (*config.Project, error) { 388 func GetConsole(c context.Context, proj, id string) (*Console, error) {
275 » // Next, Try datastore 389 » // TODO(hinoka): Memcache this.
276 » p := Project{ID: projName} 390 » con := Console{
277 » if err := datastore.Get(c, &p); err != nil { 391 » » Parent: datastore.MakeKey(c, "Project", proj),
278 » » return nil, err 392 » » ID: id,
279 } 393 }
280 » mp := config.Project{} 394 » err := datastore.Get(c, &con)
281 » if err := proto.Unmarshal(p.Data, &mp); err != nil { 395 » return &con, err
282 » » return nil, err
283 » }
284
285 » return &mp, nil
286 }
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 } 396 }
301 397
302 // ProjectConsole is a simple tuple type for GetConsolesForBuilder. 398 // ProjectConsole is a simple tuple type for GetConsolesForBuilder.
303 type ProjectConsole struct { 399 type ProjectConsole struct {
304 ProjectID string 400 ProjectID string
305 Console *config.Console 401 Console *config.Console
306 } 402 }
307 403
308 // GetConsolesForBuilder retrieves all the console definitions that this builder 404 // GetConsolesForBuilder retrieves all the console definitions that this builder
309 // belongs to. 405 // belongs to.
310 func GetConsolesForBuilder(c context.Context, builderName string) ([]*ProjectCon sole, error) { 406 func GetConsolesForBuilder(c context.Context, builderName string) ([]*Console, e rror) {
311 » projs, err := GetAllProjects(c) 407 » cons, err := GetAllConsoles(c)
Ryan Tseng 2017/07/20 00:15:15 do the thing Done
312 if err != nil { 408 if err != nil {
313 return nil, err 409 return nil, err
314 } 410 }
315 » ret := []*ProjectConsole{} 411 » ret := []*Console{}
316 » for _, p := range projs { 412 » for _, con := range cons {
317 » » for _, con := range p.Consoles { 413 » » for _, b := range con.Builders {
318 » » » for _, b := range con.Builders { 414 » » » if b == builderName {
319 » » » » if b.Name == builderName { 415 » » » » ret = append(ret, con)
320 » » » » » ret = append(ret, &ProjectConsole{p.ID, con})
321 » » » » }
322 } 416 }
323 } 417 }
324 } 418 }
325 return ret, nil 419 return ret, nil
326 } 420 }
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