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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « milo/common/acl_test.go ('k') | milo/common/config_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: milo/common/config.go
diff --git a/milo/common/config.go b/milo/common/config.go
index 42097a94b966bd2e55a9a23c77f7463c74efc7e7..dc0ef8dafb81d3db83c3db36a754623a2e2031b0 100644
--- a/milo/common/config.go
+++ b/milo/common/config.go
@@ -16,6 +16,7 @@ package common
import (
"fmt"
+ "strings"
"time"
"github.com/golang/protobuf/proto"
@@ -23,24 +24,81 @@ import (
"github.com/luci/gae/service/datastore"
"github.com/luci/gae/service/info"
+ configInterface "github.com/luci/luci-go/common/config"
"github.com/luci/luci-go/common/data/caching/proccache"
+ "github.com/luci/luci-go/common/data/stringset"
"github.com/luci/luci-go/common/errors"
"github.com/luci/luci-go/common/logging"
"github.com/luci/luci-go/luci_config/server/cfgclient"
"github.com/luci/luci-go/luci_config/server/cfgclient/backend"
- "github.com/luci/luci-go/luci_config/server/cfgclient/textproto"
"github.com/luci/luci-go/milo/api/config"
)
-// Project is a LUCI project.
-type Project struct {
- // ID of the project, as per self defined. This is the luci-config name.
+// Console is af datastore entity representing a single console.
+type Console struct {
+ // Parent is a key to the parent Project entity where this console was
+ // defined in.
+ Parent *datastore.Key `gae:"$parent"`
+ // ID is the ID of the console.
ID string `gae:"$id"`
- // Data is the Project data in protobuf binary format.
- Data []byte `gae:",noindex"`
- // Revision is the latest revision we have of this project's config.
+ // RepoURL and Ref combined defines the commits the show up on the left
+ // hand side of a Console.
+ RepoURL string
+ // RepoURL and Ref combined defines the commits the show up on the left
+ // hand side of a Console.
+ Ref string
+ // ManifestName is the name of the manifest to look for when querying for
+ // builds under this console.
+ ManifestName string
+ // URL is the URL to the luci-config definition of this console.
+ URL string
+ // Revision is the luci-config reivision from when this Console was retrieved.
Revision string
+ // Builders is a list of universal builder IDs.
+ Builders []string
+}
+
+// GetProjectName retrieves the project name of the console out of the Console's
+// parent key.
+func (con *Console) GetProjectName() string {
+ return con.Parent.StringID()
+}
+
+// NewConsole creates a fully populated console out of the luci-config proto
+// definition of a console.
+func NewConsole(project *datastore.Key, URL, revision string, con *config.Console) *Console {
+ return &Console{
+ Parent: project,
+ ID: con.ID,
+ RepoURL: con.RepoURL,
+ Ref: con.Ref,
+ ManifestName: con.ManifestName,
+ Revision: revision,
+ URL: URL,
+ Builders: BuilderFromProto(con.Builders),
+ }
+}
+
+// BuilderFromProto tranforms a luci-config proto builder format into the datastore
+// format.
+func BuilderFromProto(cb []*config.Builder) []string {
+ builders := make([]string, len(cb))
+ for i, b := range cb {
+ builders[i] = b.Name
+ }
+ return builders
+}
+
+// LuciConfigURL returns a user friendly URL that specifies where to view
+// this console definition.
+func LuciConfigURL(c context.Context, configSet, path, revision string) string {
+ // TODO(hinoka): This shouldn't be hardcoded, instead we should get the
+ // luci-config instance from the context. But we only use this instance at
+ // the moment so it is okay for now.
+ // TODO(hinoka): The UI doesn't allow specifying paths and revision yet. Add
+ // that in when it is supported.
+ return fmt.Sprintf("https://luci-config.appspot.com/newui#/%s", configSet)
}
// The key for the service config entity in datastore.
@@ -178,149 +236,144 @@ func UpdateServiceConfig(c context.Context) (*config.Settings, error) {
return settings, nil
}
-// UpdateProjectConfigs internal project configuration based off luci-config.
-// update updates Milo's configuration based off luci config. This includes
-// scanning through all project and extract all console configs.
-func UpdateProjectConfigs(c context.Context) error {
- cfgName := info.AppID(c) + ".cfg"
-
- var (
- configs []*config.Project
- metas []*cfgclient.Meta
- merr errors.MultiError
- )
-
- logging.Debugf(c, "fetching configs for %s", cfgName)
- if err := cfgclient.Projects(c, cfgclient.AsService, cfgName, textproto.Slice(&configs), &metas); err != nil {
- merr = err.(errors.MultiError)
- // Some configs errored out, but we can continue with the ones that work still.
+// updateProjectConsoles updates all of the consoles for a given project,
+// and then returns a set of known console names.
+func updateProjectConsoles(c context.Context, projectName string, cfg *configInterface.Config) (stringset.Set, error) {
+ proj := config.Project{}
+ if err := proto.UnmarshalText(cfg.Content, &proj); err != nil {
+ return nil, errors.Annotate(err, "unmarshalling proto").Err()
}
- // A map of project ID to project.
- projects := map[string]*Project{}
- for i, proj := range configs {
- meta := metas[i]
- projectName, _, _ := meta.ConfigSet.SplitProject()
- name := string(projectName)
- projects[name] = nil
- if merr != nil && merr[i] != nil {
- logging.WithError(merr[i]).Warningf(c, "skipping %s due to error", name)
- continue
- }
-
- p := &Project{
- ID: name,
- Revision: meta.Revision,
- }
-
- logging.Infof(c, "prossing %s", name)
-
- var err error
- p.Data, err = proto.Marshal(proj)
- if err != nil {
- logging.WithError(err).Errorf(c, "Encountered error while processing project %s", name)
- // Set this to nil to signal this project exists but we don't want to update it.
- projects[name] = nil
- continue
+ // Keep a list of known consoles so we can prune deleted ones later.
+ knownConsoles := stringset.New(len(proj.Consoles))
+ // Iterate through all the proto consoles, adding and replacing the
+ // known ones if needed.
+ parentKey := datastore.MakeKey(c, "Project", projectName)
+ for _, pc := range proj.Consoles {
+ knownConsoles.Add(pc.ID)
+ con, err := GetConsole(c, projectName, pc.ID)
+ switch err {
+ case datastore.ErrNoSuchEntity:
+ // continue
+ case nil:
+ // Check if revisions match, if so just skip it.
+ if con.Revision == cfg.Revision {
+ continue
+ }
+ default:
+ return nil, errors.Annotate(err, "checking %s", pc.ID).Err()
}
- projects[name] = p
- }
-
- // Now load all the data into the datastore.
- projs := make([]*Project, 0, len(projects))
- for _, proj := range projects {
- if proj != nil {
- projs = append(projs, proj)
+ URL := LuciConfigURL(c, cfg.ConfigSet, cfg.Path, cfg.Revision)
+ con = NewConsole(parentKey, URL, cfg.Revision, pc)
+ if err = datastore.Put(c, con); err != nil {
+ return nil, errors.Annotate(err, "saving %s", pc.ID).Err()
+ } else {
+ logging.Infof(c, "saved a new %s / %s (revision %s)", projectName, con.ID, cfg.Revision)
}
}
- if err := datastore.Put(c, projs); err != nil {
- return err
- }
-
- // Delete entries that no longer exist.
- q := datastore.NewQuery("Project").KeysOnly(true)
- allProjs := []Project{}
- datastore.GetAll(c, q, &allProjs)
- toDelete := []Project{}
- for _, proj := range allProjs {
- if _, ok := projects[proj.ID]; !ok {
- toDelete = append(toDelete, proj)
- }
- }
- return datastore.Delete(c, toDelete)
+ return knownConsoles, nil
}
-// GetAllProjects returns all registered projects.
-func GetAllProjects(c context.Context) ([]*config.Project, error) {
- q := datastore.NewQuery("Project")
- q.Order("ID")
+// UpdateConsoles updates internal console definitions entities based off luci-config.
+func UpdateConsoles(c context.Context) error {
+ cfgName := info.AppID(c) + ".cfg"
- ps := []*Project{}
- err := datastore.GetAll(c, q, &ps)
+ logging.Debugf(c, "fetching configs for %s", cfgName)
+ // Acquire the raw config client.
+ lucicfg := backend.Get(c).GetConfigInterface(c, backend.AsService)
+ // Project configs for Milo contains console definitions.
+ configs, err := lucicfg.GetProjectConfigs(c, cfgName, false)
if err != nil {
- return nil, err
+ return errors.Annotate(err, "while fetching project configs").Err()
}
- results := make([]*config.Project, len(ps))
- for i, p := range ps {
- results[i] = &config.Project{}
- if err := proto.Unmarshal(p.Data, results[i]); err != nil {
- return nil, err
+ logging.Infof(c, "got %d project configs", len(configs))
+
+ merr := errors.MultiError{}
+ knownProjects := map[string]stringset.Set{}
+ // Iterate through each project config, extracting the console definition.
+ for _, cfg := range configs {
+ // This looks like "projects/<project name>"
+ splitPath := strings.SplitN(cfg.ConfigSet, "/", 2)
+ if len(splitPath) != 2 {
+ return fmt.Errorf("Invalid config set path %s", cfg.ConfigSet)
+ }
+ projectName := splitPath[1]
+ knownProjects[projectName] = nil
+ if kp, err := updateProjectConsoles(c, projectName, &cfg); err != nil {
+ err = errors.Annotate(err, "processing project %s", cfg.ConfigSet).Err()
+ merr = append(merr, err)
+ } else {
+ knownProjects[projectName] = kp
}
}
- return results, nil
-}
-// GetProject returns the requested project.
-func GetProject(c context.Context, projName string) (*config.Project, error) {
- // Next, Try datastore
- p := Project{ID: projName}
- if err := datastore.Get(c, &p); err != nil {
- return nil, err
- }
- mp := config.Project{}
- if err := proto.Unmarshal(p.Data, &mp); err != nil {
- return nil, err
+ // Delete all the consoles that no longer exists or are part of deleted projects.
+ toDelete := []*datastore.Key{}
+ err = datastore.Run(c, datastore.NewQuery("Console"), func(key *datastore.Key) error {
+ proj := key.Parent().StringID()
+ id := key.StringID()
+ // If this console is either:
+ // 1. In a project that no longer exists, or
+ // 2. Not in the project, then delete it.
+ knownConsoles, ok := knownProjects[proj]
+ if !ok {
+ logging.Infof(
+ c, "deleting %s/%s because the project no longer exists", proj, id)
+ toDelete = append(toDelete, key)
+ return nil
+ }
+ if knownConsoles == nil {
+ // The project exists but we couldn't check it this time. Skip it and
+ // try again the next cron cycle.
+ return nil
+ }
+ if !knownConsoles.Has(id) {
+ logging.Infof(
+ c, "deleting %s/%s because the console no longer exists", proj, id)
+ toDelete = append(toDelete, key)
+ }
+ return nil
+ })
+ if err != nil {
+ merr = append(merr, err)
+ } else if err := datastore.Delete(c, toDelete); err != nil {
+ merr = append(merr, err)
}
- return &mp, nil
-}
-
-// GetConsole returns the requested console instance.
-func GetConsole(c context.Context, projName, consoleName string) (*config.Console, error) {
- p, err := GetProject(c, projName)
- if err != nil {
- return nil, err
+ // Print some stats.
+ processedConsoles := 0
+ for _, cons := range knownProjects {
+ processedConsoles += cons.Len()
}
- for _, cs := range p.Consoles {
- if cs.Name == consoleName {
- return cs, nil
- }
+ logging.Infof(
+ c, "processed %d consoles over %d projects", len(knownProjects), processedConsoles)
+
+ if len(merr) == 0 {
+ return nil
}
- return nil, fmt.Errorf("Console %s not found in project %s", consoleName, projName)
+ return merr
}
-// ProjectConsole is a simple tuple type for GetConsolesForBuilder.
-type ProjectConsole struct {
- ProjectID string
- Console *config.Console
+// GetAllConsoles returns all registered projects with the builder name.
+// If builderName is empty, then this retrieves all Consoles.
+func GetAllConsoles(c context.Context, builderName string) ([]*Console, error) {
+ q := datastore.NewQuery("Console")
+ if builderName != "" {
+ q = q.Eq("Builders", builderName)
+ }
+ q.Order("ID")
+ con := []*Console{}
+ err := datastore.GetAll(c, q, &con)
+ return con, err
}
-// GetConsolesForBuilder retrieves all the console definitions that this builder
-// belongs to.
-func GetConsolesForBuilder(c context.Context, builderName string) ([]*ProjectConsole, error) {
- projs, err := GetAllProjects(c)
- if err != nil {
- return nil, err
- }
- ret := []*ProjectConsole{}
- for _, p := range projs {
- for _, con := range p.Consoles {
- for _, b := range con.Builders {
- if b.Name == builderName {
- ret = append(ret, &ProjectConsole{p.ID, con})
- }
- }
- }
+// GetConsole returns the requested console.
+func GetConsole(c context.Context, proj, id string) (*Console, error) {
+ // TODO(hinoka): Memcache this.
+ con := Console{
+ Parent: datastore.MakeKey(c, "Project", proj),
+ ID: id,
}
- return ret, nil
+ err := datastore.Get(c, &con)
+ return &con, err
}
« 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