Merge lp:~thumper/juju-core/logger-worker into lp:~go-bot/juju-core/trunk

Proposed by Tim Penhey
Status: Merged
Approved by: Tim Penhey
Approved revision: no longer in the source branch.
Merged at revision: 1825
Proposed branch: lp:~thumper/juju-core/logger-worker
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 259 lines (+200/-2)
5 files modified
cmd/jujud/machine.go (+4/-2)
cmd/jujud/unit.go (+4/-0)
worker/logger/logger.go (+75/-0)
worker/logger/logger_test.go (+103/-0)
worker/logger/package_test.go (+14/-0)
To merge this branch: bzr merge lp:~thumper/juju-core/logger-worker
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+185421@code.launchpad.net

Commit message

Add a worker to update loggo configuration.

With the logging config now specified in the
environment configuration, we can watch to
see if it changes. If it does, this worker
updates the loggo configuration.

You can now change the logging levels of a
running juju environment using:
  juju set-env 'logging-config=<root>=WARNING;juju=INFO;juju.api=DEBUG'

https://codereview.appspot.com/13587045/

Description of the change

Add a worker to update loggo configuration.

With the logging config now specified in the
environment configuration, we can watch to
see if it changes. If it does, this worker
updates the loggo configuration.

You can now change the logging levels of a
running juju environment using:
  juju set-env 'logging-config=<root>=WARNING;juju=INFO;juju.api=DEBUG'

https://codereview.appspot.com/13587045/

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

Reviewers: mp+185421_code.launchpad.net,

Message:
Please take a look.

Description:
Add a worker to update loggo configuration.

With the logging config now specified in the
environment configuration, we can watch to
see if it changes. If it does, this worker
updates the loggo configuration.

You can now change the logging levels of a
running juju environment using:
   juju set-env 'logging-config=<root>=WARNING;juju=INFO;juju.api=DEBUG'

https://code.launchpad.net/~thumper/juju-core/logger-worker/+merge/185421

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/13587045/

Affected files (+229, -3 lines):
   A [revision details]
   M agent/agent.go
   M cmd/jujud/machine.go
   M cmd/jujud/unit.go
   M upstart/service.go
   A worker/logger/logger.go
   A worker/logger/logger_test.go
   A worker/logger/package_test.go

Revision history for this message
William Reade (fwereade) wrote :

LGTM in itself, but I feel the agent.Config stuff is risky, and want to
know what the plan is there.

https://codereview.appspot.com/13587045/diff/1/worker/logger/logger.go
File worker/logger/logger.go (right):

https://codereview.appspot.com/13587045/diff/1/worker/logger/logger.go#newcode57
worker/logger/logger.go:57: if err := logger.agentConfig.Write(); err !=
nil {
The way I see it, we have now used our one strike. We can't write
anything else to the agent config now, because any two writes can race
and cause one to be overwritten... but nothing has happened around the
config itself that might tip us off to that fact.

And I'm really worried that we *won't* write agent config soon enough
that anyone remembers this pitfall exists when we come to do so. Do you
have any suggestions for mitigation?

https://codereview.appspot.com/13587045/

Revision history for this message
William Reade (fwereade) wrote :

NOT LGTM I'm afraid. I know it's just writing a field that's not
currently read by other things, but there's literally no way to tell
it's dangerous.

https://codereview.appspot.com/13587045/diff/1/worker/logger/logger.go
File worker/logger/logger.go (right):

https://codereview.appspot.com/13587045/diff/1/worker/logger/logger.go#newcode57
worker/logger/logger.go:57: if err := logger.agentConfig.Write(); err !=
nil {
On 2013/09/13 10:06:59, fwereade wrote:
> The way I see it, we have now used our one strike. We can't write
anything else
> to the agent config now, because any two writes can race and cause one
to be
> overwritten... but nothing has happened around the config itself that
might tip
> us off to that fact.

> And I'm really worried that we *won't* write agent config soon enough
that
> anyone remembers this pitfall exists when we come to do so. Do you
have any
> suggestions for mitigation?

Actually it's worse... this write is past our zeroth strike, because
this object is shared by multiple goroutines. I think that config needs
fixes before this can go in.

https://codereview.appspot.com/13587045/

Revision history for this message
Tim Penhey (thumper) wrote :

Please take a look.

https://codereview.appspot.com/13587045/diff/1/worker/logger/logger.go
File worker/logger/logger.go (right):

https://codereview.appspot.com/13587045/diff/1/worker/logger/logger.go#newcode57
worker/logger/logger.go:57: if err := logger.agentConfig.Write(); err !=
nil {
On 2013/09/13 14:03:20, fwereade wrote:
> On 2013/09/13 10:06:59, fwereade wrote:
> > The way I see it, we have now used our one strike. We can't write
anything
> else
> > to the agent config now, because any two writes can race and cause
one to be
> > overwritten... but nothing has happened around the config itself
that might
> tip
> > us off to that fact.
> >
> > And I'm really worried that we *won't* write agent config soon
enough that
> > anyone remembers this pitfall exists when we come to do so. Do you
have any
> > suggestions for mitigation?

> Actually it's worse... this write is past our zeroth strike, because
this object
> is shared by multiple goroutines. I think that config needs fixes
before this
> can go in.

No, I don't see any problem here at all.

In order to avoid the controversy at this stage, I have moved the actual
saving of the updated logging config to a different branch so we can
continue the discussion there and not block the dynamic logging changes.

Ok, so here is what happens to the agent config when changes are made.
There is exactly one copy of the agent config instance that is passed
between the different go routines and workers inside the unit and
machine agents. Any update to the values stored in the the agent are
protected by the agent mutex for both writes and reads of that config
value. One worker writing to a value and another worker reading from
that value has no race conditions. It is immediately available in the
other go routine once the mutex is unlocked.

Since the write is also protected by the mutex, there is no possibility
of overwriting the changes made by a different worker. Ok, there is one
possibility, where two different workers are trying to modify and save
one value, but that is somewhat outside the scope of the agent config as
it is a normal race condition that needs to be handled by the competing
tasks and has nothing to do with the synchronization of the agent config
itself. Also, not an issue here.

Why do you think that once we have written something, we can't write
something else? This doesn't make sense to me and I can't see where you
are coming from. Can you please explain?

The config has fixes around synchronization of writes and setting and
getting values. What other fixes do you think are needed?

What are your concerns about this object being used in multiple go
routines?

https://codereview.appspot.com/13587045/

Revision history for this message
Tim Penhey (thumper) wrote :

Also had a chat with Ian about this just now.

We have decided that we don't actually need to save the new logging
config in the agent config at all. The main use case for it was for
dealing with the initial debugging of the agent process starting prior
to the worker getting the initial config over the API. My next branch
handles this by passing the information over the command line in the
upstart script.

Since this initial logging config is used only until the worker starts
and gets the updated config, we don't really need to save it at all.
Which does make me a little frustrated that I spent all that time
futzing with the config, but at least it is in a cleaner state now.

https://codereview.appspot.com/13587045/

Revision history for this message
Tim Penhey (thumper) wrote :
Revision history for this message
William Reade (fwereade) wrote :

LGTM. Sorry about the frustration -- maybe we can argue about
appropriate use of a Config over beers at some point :).

https://codereview.appspot.com/13587045/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/jujud/machine.go'
2--- cmd/jujud/machine.go 2013-09-12 21:32:12 +0000
3+++ cmd/jujud/machine.go 2013-09-15 23:43:09 +0000
4@@ -28,6 +28,7 @@
5 "launchpad.net/juju-core/worker/cleaner"
6 "launchpad.net/juju-core/worker/deployer"
7 "launchpad.net/juju-core/worker/firewaller"
8+ "launchpad.net/juju-core/worker/logger"
9 "launchpad.net/juju-core/worker/machiner"
10 "launchpad.net/juju-core/worker/minunitsworker"
11 "launchpad.net/juju-core/worker/provisioner"
12@@ -173,14 +174,15 @@
13 ensureStateWorker()
14 }
15 runner := newRunner(connectionIsFatal(st), moreImportant)
16- // Only the machiner currently connects to the API.
17- // Add other workers here as they are ready.
18 runner.StartWorker("machiner", func() (worker.Worker, error) {
19 return machiner.NewMachiner(st.Machiner(), agentConfig), nil
20 })
21 runner.StartWorker("upgrader", func() (worker.Worker, error) {
22 return upgrader.NewUpgrader(st.Upgrader(), agentConfig), nil
23 })
24+ runner.StartWorker("logger", func() (worker.Worker, error) {
25+ return logger.NewLogger(st.Logger(), agentConfig), nil
26+ })
27 for _, job := range entity.Jobs() {
28 switch job {
29 case params.JobHostUnits:
30
31=== modified file 'cmd/jujud/unit.go'
32--- cmd/jujud/unit.go 2013-09-12 17:10:20 +0000
33+++ cmd/jujud/unit.go 2013-09-15 23:43:09 +0000
34@@ -14,6 +14,7 @@
35 "launchpad.net/juju-core/names"
36 "launchpad.net/juju-core/state"
37 "launchpad.net/juju-core/worker"
38+ "launchpad.net/juju-core/worker/logger"
39 "launchpad.net/juju-core/worker/uniter"
40 "launchpad.net/juju-core/worker/upgrader"
41 )
42@@ -87,6 +88,9 @@
43 runner.StartWorker("upgrader", func() (worker.Worker, error) {
44 return upgrader.NewUpgrader(st.Upgrader(), agentConfig), nil
45 })
46+ runner.StartWorker("logger", func() (worker.Worker, error) {
47+ return logger.NewLogger(st.Logger(), agentConfig), nil
48+ })
49 runner.StartWorker("uniter", func() (worker.Worker, error) {
50 return uniter.NewUniter(st.Uniter(), entity.Tag(), dataDir), nil
51 })
52
53=== added directory 'worker/logger'
54=== added file 'worker/logger/logger.go'
55--- worker/logger/logger.go 1970-01-01 00:00:00 +0000
56+++ worker/logger/logger.go 2013-09-15 23:43:09 +0000
57@@ -0,0 +1,75 @@
58+// Copyright 2013 Canonical Ltd.
59+// Licensed under the AGPLv3, see LICENCE file for details.
60+
61+package logger
62+
63+import (
64+ "launchpad.net/loggo"
65+
66+ "launchpad.net/juju-core/agent"
67+ "launchpad.net/juju-core/state/api/logger"
68+ "launchpad.net/juju-core/state/api/watcher"
69+ "launchpad.net/juju-core/worker"
70+)
71+
72+var log = loggo.GetLogger("juju.worker.logger")
73+
74+// Logger is responsible for updating the loggo configuration when the
75+// environment watcher tells the agent that the value has changed.
76+type Logger struct {
77+ api *logger.State
78+ agentConfig agent.Config
79+ lastConfig string
80+}
81+
82+var _ worker.NotifyWatchHandler = (*Logger)(nil)
83+
84+// NewLogger returns a worker.Worker that uses the notify watcher returned
85+// from the setup.
86+func NewLogger(api *logger.State, agentConfig agent.Config) worker.Worker {
87+ logger := &Logger{
88+ api: api,
89+ agentConfig: agentConfig,
90+ lastConfig: loggo.LoggerInfo(),
91+ }
92+ log.Debugf("initial log config: %q", logger.lastConfig)
93+ return worker.NewNotifyWorker(logger)
94+}
95+
96+func (logger *Logger) setLogging() {
97+ loggingConfig, err := logger.api.LoggingConfig(logger.agentConfig.Tag())
98+ if err != nil {
99+ log.Errorf("%v", err)
100+ } else {
101+ if loggingConfig != logger.lastConfig {
102+ log.Debugf("reconfiguring logging from %q to %q", logger.lastConfig, loggingConfig)
103+ loggo.ResetLoggers()
104+ if err := loggo.ConfigureLoggers(loggingConfig); err != nil {
105+ // This shouldn't occur as the loggingConfig should be
106+ // validated by the original Config before it gets here.
107+ log.Warningf("configure loggers failed: %v", err)
108+ // Try to reset to what we had before
109+ loggo.ConfigureLoggers(logger.lastConfig)
110+ }
111+ logger.lastConfig = loggingConfig
112+ }
113+ }
114+}
115+
116+func (logger *Logger) SetUp() (watcher.NotifyWatcher, error) {
117+ log.Debugf("logger setup")
118+ // We need to set this up initially as the NotifyWorker sucks up the first
119+ // event.
120+ logger.setLogging()
121+ return logger.api.WatchLoggingConfig(logger.agentConfig.Tag())
122+}
123+
124+func (logger *Logger) Handle() error {
125+ logger.setLogging()
126+ return nil
127+}
128+
129+func (logger *Logger) TearDown() error {
130+ // Nothing to cleanup, only state is the watcher
131+ return nil
132+}
133
134=== added file 'worker/logger/logger_test.go'
135--- worker/logger/logger_test.go 1970-01-01 00:00:00 +0000
136+++ worker/logger/logger_test.go 2013-09-15 23:43:09 +0000
137@@ -0,0 +1,103 @@
138+// Copyright 2013 Canonical Ltd.
139+// Licensed under the AGPLv3, see LICENCE file for details.
140+
141+package logger_test
142+
143+import (
144+ "time"
145+
146+ gc "launchpad.net/gocheck"
147+ "launchpad.net/loggo"
148+
149+ "launchpad.net/juju-core/agent"
150+ "launchpad.net/juju-core/juju/testing"
151+ "launchpad.net/juju-core/state"
152+ "launchpad.net/juju-core/state/api"
153+ apilogger "launchpad.net/juju-core/state/api/logger"
154+ "launchpad.net/juju-core/worker"
155+ "launchpad.net/juju-core/worker/logger"
156+)
157+
158+// worstCase is used for timeouts when timing out
159+// will fail the test. Raising this value should
160+// not affect the overall running time of the tests
161+// unless they fail.
162+const worstCase = 5 * time.Second
163+
164+type LoggerSuite struct {
165+ testing.JujuConnSuite
166+
167+ apiRoot *api.State
168+ loggerApi *apilogger.State
169+ machine *state.Machine
170+}
171+
172+var _ = gc.Suite(&LoggerSuite{})
173+
174+func (s *LoggerSuite) SetUpTest(c *gc.C) {
175+ s.JujuConnSuite.SetUpTest(c)
176+ s.apiRoot, s.machine = s.OpenAPIAsNewMachine(c)
177+ s.AddCleanup(func() { s.apiRoot.Close() })
178+ // Create the machiner API facade.
179+ s.loggerApi = s.apiRoot.Logger()
180+ c.Assert(s.loggerApi, gc.NotNil)
181+}
182+
183+func (s *LoggerSuite) waitLoggingInfo(c *gc.C, expected string) {
184+ timeout := time.After(worstCase)
185+ for {
186+ select {
187+ case <-timeout:
188+ c.Fatalf("timeout while waiting for logging info to change")
189+ case <-time.After(10 * time.Millisecond):
190+ loggerInfo := loggo.LoggerInfo()
191+ if loggerInfo != expected {
192+ c.Logf("logging is %q, still waiting", loggerInfo)
193+ continue
194+ }
195+ return
196+ }
197+ }
198+}
199+
200+type mockConfig struct {
201+ agent.Config
202+ c *gc.C
203+ tag string
204+}
205+
206+func (mock *mockConfig) Tag() string {
207+ return mock.tag
208+}
209+
210+func agentConfig(c *gc.C, tag string) *mockConfig {
211+ return &mockConfig{c: c, tag: tag}
212+}
213+
214+func (s *LoggerSuite) makeLogger(c *gc.C) (worker.Worker, *mockConfig) {
215+ config := agentConfig(c, s.machine.Tag())
216+ return logger.NewLogger(s.loggerApi, config), config
217+}
218+
219+func (s *LoggerSuite) TestRunStop(c *gc.C) {
220+ loggingWorker, _ := s.makeLogger(c)
221+ c.Assert(worker.Stop(loggingWorker), gc.IsNil)
222+}
223+
224+func (s *LoggerSuite) TestInitialState(c *gc.C) {
225+ config, err := s.State.EnvironConfig()
226+ c.Assert(err, gc.IsNil)
227+ expected := config.LoggingConfig()
228+
229+ initial := "<root>=DEBUG;wibble=ERROR"
230+ c.Assert(expected, gc.Not(gc.Equals), initial)
231+
232+ loggo.ResetLoggers()
233+ err = loggo.ConfigureLoggers(initial)
234+ c.Assert(err, gc.IsNil)
235+
236+ loggingWorker, _ := s.makeLogger(c)
237+ defer worker.Stop(loggingWorker)
238+
239+ s.waitLoggingInfo(c, expected)
240+}
241
242=== added file 'worker/logger/package_test.go'
243--- worker/logger/package_test.go 1970-01-01 00:00:00 +0000
244+++ worker/logger/package_test.go 2013-09-15 23:43:09 +0000
245@@ -0,0 +1,14 @@
246+// Copyright 2013 Canonical Ltd.
247+// Licensed under the AGPLv3, see LICENCE file for details.
248+
249+package logger_test
250+
251+import (
252+ stdtesting "testing"
253+
254+ "launchpad.net/juju-core/testing"
255+)
256+
257+func TestAll(t *stdtesting.T) {
258+ testing.MgoTestPackage(t)
259+}

Subscribers

People subscribed via source and target branches

to status/vote changes: