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

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

Issue 2949783002: [milo] appengine/* -> * (Closed)
Patch Set: rebase Created 3 years, 6 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/appengine/common/acl_test.go ('k') | milo/appengine/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
(Empty)
1 // Copyright 2016 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 package common
6
7 import (
8 "fmt"
9 "time"
10
11 "github.com/golang/protobuf/proto"
12 "golang.org/x/net/context"
13
14 "github.com/luci/gae/service/datastore"
15 "github.com/luci/gae/service/info"
16 "github.com/luci/luci-go/common/data/caching/proccache"
17 "github.com/luci/luci-go/common/logging"
18 "github.com/luci/luci-go/luci_config/server/cfgclient"
19 "github.com/luci/luci-go/luci_config/server/cfgclient/backend"
20 "github.com/luci/luci-go/luci_config/server/cfgclient/textproto"
21
22 "github.com/luci/luci-go/milo/api/config"
23 )
24
25 // Project is a LUCI project.
26 type Project struct {
27 // The ID of the project, as per self defined. This is not the luci-con fig
28 // name.
29 ID string `gae:"$id"`
30 // The luci-config name of the project.
31 Name string
32 // The Project data in protobuf binary format.
33 Data []byte `gae:",noindex"`
34 }
35
36 // The key for the service config entity in datastore.
37 const ServiceConfigID = "service_config"
38
39 // ServiceConfig is a container for the instance's service config.
40 type ServiceConfig struct {
41 // ID is the datastore key. This should be static, as there should only be
42 // one service config.
43 ID string `gae:"$id"`
44 // Revision is the revision of the config, taken from luci-config. This is used
45 // to determine if the entry needs to be refreshed.
46 Revision string
47 // Data is the binary proto of the config.
48 Data []byte `gae:",noindex"`
49 // Text is the text format of the config. For human consumption only.
50 Text string `gae:",noindex"`
51 // LastUpdated is the time this config was last updated.
52 LastUpdated time.Time
53 }
54
55 // GetServiceConfig returns the service (aka global) config for the current
56 // instance of Milo from the datastore. Returns an empty config and warn heavil y
57 // if none is found.
58 // TODO(hinoka): Use process cache to cache configs.
59 func GetSettings(c context.Context) *config.Settings {
60 settings := config.Settings{
61 Buildbot: &config.Settings_Buildbot{},
62 }
63
64 msg, err := GetCurrentServiceConfig(c)
65 if err != nil {
66 // The service config does not exist, just return an empty confi g
67 // and complain loudly in the logs.
68 logging.WithError(err).Errorf(c,
69 "Encountered error while loading service config, using e mpty config.")
70 return &settings
71 }
72
73 err = proto.Unmarshal(msg.Data, &settings)
74 if err != nil {
75 // The service config is broken, just return an empty config
76 // and complain loudly in the logs.
77 logging.WithError(err).Errorf(c,
78 "Encountered error while unmarshalling service config, u sing empty config.")
79 // Zero out the message just incase something got written in.
80 settings = config.Settings{Buildbot: &config.Settings_Buildbot{} }
81 }
82
83 return &settings
84 }
85
86 // GetCurrentServiceConfig gets the service config for the instance from either
87 // process cache or datastore cache.
88 func GetCurrentServiceConfig(c context.Context) (*ServiceConfig, error) {
89 // This maker function is used to do the actual fetch of the ServiceConf ig
90 // from datastore. It is called if the ServiceConfig is not in proc cac he.
91 maker := func() (interface{}, time.Duration, error) {
92 msg := ServiceConfig{ID: ServiceConfigID}
93 err := datastore.Get(c, &msg)
94 if err != nil {
95 return nil, time.Minute, err
96 }
97 logging.Infof(c, "loaded service config from datastore")
98 return msg, time.Minute, nil
99 }
100 item, err := proccache.GetOrMake(c, ServiceConfigID, maker)
101 if err != nil {
102 return nil, fmt.Errorf("failed to get service config: %s", err.E rror())
103 }
104 if msg, ok := item.(ServiceConfig); ok {
105 logging.Infof(c, "loaded config entry from %s", msg.LastUpdated. Format(time.RFC3339))
106 return &msg, nil
107 }
108 return nil, fmt.Errorf("could not load service config %#v", item)
109 }
110
111 // UpdateServiceConfig fetches the service config from luci-config
112 // and then stores a snapshot of the configuration in datastore.
113 func UpdateServiceConfig(c context.Context) error {
114 // Load the settings from luci-config.
115 cs := string(cfgclient.CurrentServiceConfigSet(c))
116 // Acquire the raw config client.
117 lucicfg := backend.Get(c).GetConfigInterface(c, backend.AsService)
118 // Our global config name is called settings.cfg.
119 cfg, err := lucicfg.GetConfig(c, cs, "settings.cfg", false)
120 if err != nil {
121 return fmt.Errorf("could not load settings.cfg from luci-config: %s", err)
122 }
123 settings := &config.Settings{}
124 err = proto.UnmarshalText(cfg.Content, settings)
125 if err != nil {
126 return fmt.Errorf("could not unmarshal proto from luci-config:\n %s", cfg.Content)
127 }
128 newConfig := ServiceConfig{
129 ID: ServiceConfigID,
130 Text: cfg.Content,
131 Revision: cfg.Revision,
132 LastUpdated: time.Now().UTC(),
133 }
134 newConfig.Data, err = proto.Marshal(settings)
135 if err != nil {
136 return fmt.Errorf("could not marshal proto into binary\n%s", new Config.Text)
137 }
138
139 // Do the revision check & swap in a datastore transaction.
140 err = datastore.RunInTransaction(c, func(c context.Context) error {
141 oldConfig := ServiceConfig{ID: ServiceConfigID}
142 err := datastore.Get(c, &oldConfig)
143 switch err {
144 case datastore.ErrNoSuchEntity:
145 // Might be the first time this has run.
146 logging.WithError(err).Warningf(c, "No existing service config.")
147 case nil:
148 // Continue
149 default:
150 return fmt.Errorf("could not load existing config: %s", err)
151 }
152 // Check to see if we need to update
153 if oldConfig.Revision == newConfig.Revision {
154 logging.Infof(c, "revisions matched (%s), no need to upd ate", oldConfig.Revision)
155 return nil
156 }
157 logging.Infof(c, "revisions differ (old %s, new %s), updating",
158 oldConfig.Revision, newConfig.Revision)
159 return datastore.Put(c, &newConfig)
160 }, nil)
161
162 if err != nil {
163 return fmt.Errorf("failed to update config entry in transaction" , err)
164 }
165 logging.Infof(c, "successfully updated to new config")
166
167 return nil
168 }
169
170 // UpdateProjectConfigs internal project configuration based off luci-config.
171 // update updates Milo's configuration based off luci config. This includes
172 // scanning through all project and extract all console configs.
173 func UpdateProjectConfigs(c context.Context) error {
174 cfgName := info.AppID(c) + ".cfg"
175
176 var (
177 configs []*config.Project
178 metas []*cfgclient.Meta
179 )
180 logging.Debugf(c, "fetching configs for %s", cfgName)
181 if err := cfgclient.Projects(c, cfgclient.AsService, cfgName, textproto. Slice(&configs), &metas); err != nil {
182 logging.WithError(err).Errorf(c, "Encountered error while gettin g project config for %s", cfgName)
183 return err
184 }
185
186 // A map of project ID to project.
187 projects := map[string]*Project{}
188 for i, proj := range configs {
189 projectName, _, _ := metas[i].ConfigSet.SplitProject()
190
191 logging.Infof(c, "Prossing %s", projectName)
192 if dup, ok := projects[proj.ID]; ok {
193 return fmt.Errorf(
194 "Duplicate project ID: %s. (%s and %s)", proj.ID , dup.Name, projectName)
195 }
196 p := &Project{
197 ID: proj.ID,
198 Name: string(projectName),
199 }
200 projects[proj.ID] = p
201
202 var err error
203 p.Data, err = proto.Marshal(proj)
204 if err != nil {
205 return err
206 }
207 }
208
209 // Now load all the data into the datastore.
210 projs := make([]*Project, 0, len(projects))
211 for _, proj := range projects {
212 projs = append(projs, proj)
213 }
214 if err := datastore.Put(c, projs); err != nil {
215 return err
216 }
217
218 // Delete entries that no longer exist.
219 q := datastore.NewQuery("Project").KeysOnly(true)
220 allProjs := []Project{}
221 datastore.GetAll(c, q, &allProjs)
222 toDelete := []Project{}
223 for _, proj := range allProjs {
224 if _, ok := projects[proj.ID]; !ok {
225 toDelete = append(toDelete, proj)
226 }
227 }
228 datastore.Delete(c, toDelete)
229
230 return nil
231 }
232
233 // GetAllProjects returns all registered projects.
234 func GetAllProjects(c context.Context) ([]*config.Project, error) {
235 q := datastore.NewQuery("Project")
236 q.Order("ID")
237
238 ps := []*Project{}
239 err := datastore.GetAll(c, q, &ps)
240 if err != nil {
241 return nil, err
242 }
243 results := make([]*config.Project, len(ps))
244 for i, p := range ps {
245 results[i] = &config.Project{}
246 if err := proto.Unmarshal(p.Data, results[i]); err != nil {
247 return nil, err
248 }
249 }
250 return results, nil
251 }
252
253 // GetProject returns the requested project.
254 func GetProject(c context.Context, projName string) (*config.Project, error) {
255 // Next, Try datastore
256 p := Project{ID: projName}
257 if err := datastore.Get(c, &p); err != nil {
258 return nil, err
259 }
260 mp := config.Project{}
261 if err := proto.Unmarshal(p.Data, &mp); err != nil {
262 return nil, err
263 }
264
265 return &mp, nil
266 }
267
268 // GetConsole returns the requested console instance.
269 func GetConsole(c context.Context, projName, consoleName string) (*config.Consol e, error) {
270 p, err := GetProject(c, projName)
271 if err != nil {
272 return nil, err
273 }
274 for _, cs := range p.Consoles {
275 if cs.Name == consoleName {
276 return cs, nil
277 }
278 }
279 return nil, fmt.Errorf("Console %s not found in project %s", consoleName , projName)
280 }
OLDNEW
« no previous file with comments | « milo/appengine/common/acl_test.go ('k') | milo/appengine/common/config_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698