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

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

Issue 2801463002: Milo: Use custom config caching layer (Closed)
Patch Set: Fix swarming leftovers Created 3 years, 8 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
OLDNEW
1 // Copyright 2016 The LUCI Authors. All rights reserved. 1 // Copyright 2016 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0 2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file. 3 // that can be found in the LICENSE file.
4 4
5 package common 5 package common
6 6
7 import ( 7 import (
8 "fmt" 8 "fmt"
9 "time"
9 10
10 "github.com/luci/gae/service/datastore" 11 "github.com/luci/gae/service/datastore"
11 "github.com/luci/gae/service/info" 12 "github.com/luci/gae/service/info"
13 "github.com/luci/luci-go/common/data/caching/proccache"
12 "github.com/luci/luci-go/common/logging" 14 "github.com/luci/luci-go/common/logging"
13 "github.com/luci/luci-go/luci_config/server/cfgclient" 15 "github.com/luci/luci-go/luci_config/server/cfgclient"
16 "github.com/luci/luci-go/luci_config/server/cfgclient/backend"
14 "github.com/luci/luci-go/luci_config/server/cfgclient/textproto" 17 "github.com/luci/luci-go/luci_config/server/cfgclient/textproto"
15 "github.com/luci/luci-go/milo/common/config" 18 "github.com/luci/luci-go/milo/common/config"
16 19
17 "github.com/golang/protobuf/proto" 20 "github.com/golang/protobuf/proto"
18 "golang.org/x/net/context" 21 "golang.org/x/net/context"
19 ) 22 )
20 23
21 // Project is a LUCI project. 24 // Project is a LUCI project.
22 type Project struct { 25 type Project struct {
23 » // The ID of the project, as per self defined. This is not the luci-cfg 26 » // The ID of the project, as per self defined. This is not the luci-con fig
24 // name. 27 // name.
25 ID string `gae:"$id"` 28 ID string `gae:"$id"`
26 » // The luci-cfg name of the project. 29 » // The luci-config name of the project.
27 Name string 30 Name string
28 // The Project data in protobuf binary format. 31 // The Project data in protobuf binary format.
29 Data []byte `gae:",noindex"` 32 Data []byte `gae:",noindex"`
30 } 33 }
31 34
35 // The key for the service config entity in datastore.
36 const ServiceConfigID = "service_config"
37
38 // ServiceConfig is a container for the instance's service config.
39 type ServiceConfig struct {
40 // ID is the datastore key. This should be static, as there should only be
41 // one service config.
42 ID string `gae:"$id"`
43 // Revision is the revision of the config, taken from luci-config. This is used
44 // to determine if the entry needs to be refreshed.
45 Revision string
46 // Data is the binary proto of the config.
47 Data []byte `gae:",noindex"`
48 // Text is the text format of the config. For human consumption only.
49 Text string `gae:",noindex"`
50 // LastUpdated is the time this config was last updated.
51 LastUpdated time.Time
52 }
53
32 // GetServiceConfig returns the service (aka global) config for the current 54 // GetServiceConfig returns the service (aka global) config for the current
33 // instance of Milo. 55 // instance of Milo from the datastore. Returns an empty config and warn heavil y
34 func GetSettings(c context.Context) (*config.Settings, error) { 56 // if none is found.
35 » cs := cfgclient.CurrentServiceConfigSet(c) 57 // TODO(hinoka): Use process cache to cache configs.
36 » msg := &config.Settings{} 58 func GetSettings(c context.Context) *config.Settings {
37 » // Our global config name is called settings.cfg. 59 » settings := config.Settings{
38 » err := cfgclient.Get(c, cfgclient.AsService, cs, "settings.cfg", textpro to.Message(msg), nil) 60 » » Buildbot: &config.Settings_Buildbot{},
39 » switch err { 61 » }
40 » case cfgclient.ErrNoConfig: 62
41 » » // Just warn very heavily in the logs, but don't 500, instead re turn an 63 » msg, err := GetCurrentServiceConfig(c)
42 » » // empty config. 64 » if err != nil {
43 » » logging.WithError(err).Errorf(c, "settings.cfg does not exist") 65 » » // The service config does not exist, just return an empty confi g
44 » » msg.Buildbot = &config.Settings_Buildbot{} 66 » » // and complain loudly in the logs.
45 » » err = nil 67 » » logging.WithError(err).Errorf(c,
46 » case nil: 68 » » » "Encountered error while loading service config, using e mpty config.")
47 » » // continue 69 » » return &settings
48 » default: 70 » }
71
72 » err = proto.Unmarshal(msg.Data, &settings)
73 » if err != nil {
74 » » // The service config is broken, just return an empty config
75 » » // and complain loudly in the logs.
76 » » logging.WithError(err).Errorf(c,
77 » » » "Encountered error while unmarshalling service config, u sing empty config.")
78 » » // Zero out the message just incase something got written in.
79 » » settings = config.Settings{Buildbot: &config.Settings_Buildbot{} }
80 » }
81
82 » return &settings
83 }
84
85 // GetTextSettings gets
86 func GetCurrentServiceConfig(c context.Context) (*ServiceConfig, error) {
87 » // Try Proccess Cache first
88 » if item, ok := proccache.Get(c, ServiceConfigID); ok {
nodir 2017/04/05 21:47:50 if 2 goroutines call GetCurrentServiceConfig at th
hinoka 2017/04/06 03:34:33 Done.
89 » » if msg, ok := item.(ServiceConfig); ok {
90 » » » logging.Infof(
91 » » » » c, "Loaded proccached config entry from %s", msg .LastUpdated.Format(time.RFC3339))
92 » » » return &msg, nil
93 » » }
94 » }
95
96 » msg := ServiceConfig{ID: ServiceConfigID}
97 » err := datastore.Get(c, &msg)
98 » if err != nil {
49 return nil, err 99 return nil, err
50 } 100 }
51 » return msg, err 101 » // Save it in process cache.
102 » proccache.Put(c, ServiceConfigID, msg, time.Minute*10)
103 » logging.Infof(c, "Loaded config entry from %s", msg.LastUpdated.Format(t ime.RFC3339))
104 » return &msg, nil
52 } 105 }
53 106
54 // UpdateProjectConfigs internal project configuration based off luci-cfg. 107 // UpdateServiceConfig fetches the service config from luci-config
108 // and then stores a snapshot of the configuration in datastore.
109 func UpdateServiceConfig(c context.Context) error {
110 » // Load the settings from luci-config.
111 » cs := string(cfgclient.CurrentServiceConfigSet(c))
112 » // Acquire the raw config client.
113 » lucicfg := backend.Get(c).GetConfigInterface(c, backend.AsService)
114 » // Our global config name is called settings.cfg.
115 » cfg, err := lucicfg.GetConfig(c, cs, "settings.cfg", false)
116 » if err != nil {
117 » » logging.WithError(err).Errorf(c, "could not load settings.cfg fr om luci-config")
118 » » return err
nodir 2017/04/05 21:47:50 you said you will wrap it, but this is not wrappin
hinoka 2017/04/06 03:34:34 Done.
119 » }
120 » settings := &config.Settings{}
121 » err = proto.UnmarshalText(cfg.Content, settings)
122 » if err != nil {
123 » » logging.WithError(err).Errorf(
124 » » » c, "could not unmarshal proto from luci-config:\n%s", cf g.Content)
nodir 2017/04/05 21:47:50 did you mean to wrap and return the error? current
hinoka 2017/04/06 03:34:34 Done.
125 » }
126 » newConfig := ServiceConfig{ID: ServiceConfigID}
nodir 2017/04/05 21:47:50 nit: all fields ut but ID are set with assignment
hinoka 2017/04/06 03:34:33 Done.
127 » newConfig.Text = cfg.Content
128 » newConfig.Data, err = proto.Marshal(settings)
129 » if err != nil {
130 » » logging.Errorf(c, "could not marshal proto into binary\n%s", new Config.Text)
nodir 2017/04/05 21:47:50 this double logs. wrap and return without logging?
131 » » return err
132 » }
133 » newConfig.Revision = cfg.Revision
134 » newConfig.LastUpdated = time.Now().UTC()
135
136 » // Do the revision check & swap in a datastore transaction.
137 » err = datastore.RunInTransaction(c, func(c context.Context) error {
138 » » oldConfig := ServiceConfig{ID: ServiceConfigID}
139 » » err := datastore.Get(c, &oldConfig)
140 » » switch err {
141 » » case datastore.ErrNoSuchEntity:
142 » » » // Might be the first time this has run.
143 » » » logging.WithError(err).Warningf(c, "No existing service config.")
144 » » case nil:
145 » » » // Continue
146 » » default:
147 » » » return fmt.Errorf("could not load existing config: %s", err)
148 » » }
149 » » // Check to see if we need to update
150 » » if oldConfig.Revision == newConfig.Revision {
151 » » » logging.Infof(c, "revisions matched (%s), no need to upd ate", oldConfig.Revision)
152 » » » return nil
153 » » }
154 » » logging.Infof(c, "revisions differ (old %s, new %s), updating",
155 » » » oldConfig.Revision, newConfig.Revision)
156 » » return datastore.Put(c, &newConfig)
157 » }, nil)
158
159 » if err != nil {
160 » » return fmt.Errorf("failed to update config entry in transaction" , err)
161 » }
162 » logging.Infof(c, "successfully updated to new config")
163
164 » // Save it in process cache also.
165 » proccache.Put(c, ServiceConfigID, newConfig, time.Minute*10)
nodir 2017/04/05 21:47:50 note that this will affect only one of your many a
hinoka 2017/04/06 03:34:34 Done.
166 » return nil
167 }
168
169 // UpdateProjectConfigs internal project configuration based off luci-config.
55 // update updates Milo's configuration based off luci config. This includes 170 // update updates Milo's configuration based off luci config. This includes
56 // scanning through all project and extract all console configs. 171 // scanning through all project and extract all console configs.
57 func UpdateProjectConfigs(c context.Context) error { 172 func UpdateProjectConfigs(c context.Context) error {
58 cfgName := info.AppID(c) + ".cfg" 173 cfgName := info.AppID(c) + ".cfg"
59 174
60 var ( 175 var (
61 configs []*config.Project 176 configs []*config.Project
62 metas []*cfgclient.Meta 177 metas []*cfgclient.Meta
63 ) 178 )
179 logging.Debugf(c, "fetching configs for %s", cfgName)
64 if err := cfgclient.Projects(c, cfgclient.AsService, cfgName, textproto. Slice(&configs), &metas); err != nil { 180 if err := cfgclient.Projects(c, cfgclient.AsService, cfgName, textproto. Slice(&configs), &metas); err != nil {
65 logging.WithError(err).Errorf(c, "Encountered error while gettin g project config for %s", cfgName) 181 logging.WithError(err).Errorf(c, "Encountered error while gettin g project config for %s", cfgName)
66 return err 182 return err
67 } 183 }
68 184
69 // A map of project ID to project. 185 // A map of project ID to project.
70 projects := map[string]*Project{} 186 projects := map[string]*Project{}
71 for i, proj := range configs { 187 for i, proj := range configs {
72 projectName, _, _ := metas[i].ConfigSet.SplitProject() 188 projectName, _, _ := metas[i].ConfigSet.SplitProject()
73 189
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
154 if err != nil { 270 if err != nil {
155 return nil, err 271 return nil, err
156 } 272 }
157 for _, cs := range p.Consoles { 273 for _, cs := range p.Consoles {
158 if cs.Name == consoleName { 274 if cs.Name == consoleName {
159 return cs, nil 275 return cs, nil
160 } 276 }
161 } 277 }
162 return nil, fmt.Errorf("Console %s not found in project %s", consoleName , projName) 278 return nil, fmt.Errorf("Console %s not found in project %s", consoleName , projName)
163 } 279 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698