Merge lp:~jaypipes/glance/bug720816 into lp:~glance-coresec/glance/cactus-trunk

Proposed by Jay Pipes
Status: Merged
Approved by: Jay Pipes
Approved revision: 87
Merged at revision: 89
Proposed branch: lp:~jaypipes/glance/bug720816
Merge into: lp:~glance-coresec/glance/cactus-trunk
Diff against target: 436 lines (+222/-72)
7 files modified
bin/glance-api (+1/-2)
bin/glance-registry (+1/-2)
doc/source/configuring.rst (+46/-0)
glance/common/config.py (+20/-55)
run_tests.py (+10/-11)
tests/unit/test_migrations.py (+1/-2)
tests/unit/test_misc.py (+143/-0)
To merge this branch: bzr merge lp:~jaypipes/glance/bug720816
Reviewer Review Type Date Requested Status
Jay Pipes (community) Approve
Rick Harris (community) Approve
Devin Carlen (community) Approve
Dan Prince Pending
Review via email: mp+52617@code.launchpad.net

Commit message

Adds documentation on configuring logging and a unit test for checking simple log output

Description of the change

Adds documentation on configuring logging and a test that log_file works. It didn't, so this also inludes fixes for setting up log handling :)

To post a comment you must log in.
Revision history for this message
Devin Carlen (devcamcar) wrote :

good stuff!

review: Approve
Revision history for this message
Rick Harris (rconradharris) wrote :

Thanks Jay, this is a definite improvement.

review: Approve
Revision history for this message
Cory Wright (corywright) wrote :

You may want to merge trunk in once more. I just tried it and got a conflict.

Revision history for this message
Jay Pipes (jaypipes) wrote :

Merged and fixed.

lp:~jaypipes/glance/bug720816 updated
87. By Jay Pipes

Fixed run_tests.py addError() method since I noted it was faulty in another branch...

Revision history for this message
Jay Pipes (jaypipes) wrote :

pushing to trunk...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/glance-api'
2--- bin/glance-api 2011-02-21 22:11:27 +0000
3+++ bin/glance-api 2011-03-16 16:32:09 +0000
4@@ -47,7 +47,7 @@
5 :param parser: The option parser
6 """
7 config.add_common_options(parser)
8- config.add_log_options('glance-api', parser)
9+ config.add_log_options(parser)
10
11
12 if __name__ == '__main__':
13@@ -57,7 +57,6 @@
14 (options, args) = config.parse_options(oparser)
15
16 try:
17- config.setup_logging(options)
18 conf, app = config.load_paste_app('glance-api', options, args)
19
20 server = wsgi.Server()
21
22=== modified file 'bin/glance-registry'
23--- bin/glance-registry 2011-02-21 22:11:27 +0000
24+++ bin/glance-registry 2011-03-16 16:32:09 +0000
25@@ -47,7 +47,7 @@
26 :param parser: The option parser
27 """
28 config.add_common_options(parser)
29- config.add_log_options('glance-registry', parser)
30+ config.add_log_options(parser)
31
32
33 if __name__ == '__main__':
34@@ -57,7 +57,6 @@
35 (options, args) = config.parse_options(oparser)
36
37 try:
38- config.setup_logging(options)
39 conf, app = config.load_paste_app('glance-registry', options, args)
40
41 server = wsgi.Server()
42
43=== modified file 'doc/source/configuring.rst'
44--- doc/source/configuring.rst 2011-02-04 23:59:52 +0000
45+++ doc/source/configuring.rst 2011-03-16 16:32:09 +0000
46@@ -18,3 +18,49 @@
47 ==================
48
49 .. todo:: Complete details of configuration with paste.deploy config files
50+
51+Configuring Logging in Glance
52+-----------------------------
53+
54+There are a number of configuration options in Glance that control how Glance
55+servers log messages. The configuration options are specified in the
56+``glance.conf`` config file.
57+
58+* ``--log-config=PATH``
59+
60+Optional. Default: ``None``
61+
62+Specified on the command line only.
63+
64+Takes a path to a configuration file to use for configuring logging.
65+
66+* ``--log-format``
67+
68+*Because of a bug in the PasteDeploy package, this option is only available
69+on the command line.*
70+
71+Optional. Default: ``%(asctime)s %(levelname)8s [%(name)s] %(message)s``
72+
73+The format of the log records. See the
74+`logging module <http://docs.python.org/library/logging.html>`_ documentation for
75+more information on setting this format string.
76+
77+* ``log_file`` (``--log-file`` when specified on the command line)
78+
79+The filepath of the file to use for logging messages from Glance's servers. If
80+missing, the default is to output messages to ``stdout``, so if you are running
81+Glance servers in a daemon mode (using ``glance-control``) you should make
82+sure that the ``log_file`` option is set appropriately.
83+
84+* ``log_dir`` (``--log-dir`` when specified on the command line)
85+
86+The filepath of the directory to use for log files. If not specified (the default)
87+the ``log_file`` is used as an absolute filepath.
88+
89+* ``log_date_format`` (``--log-date-format`` when specified from the command line)
90+
91+The format string for timestamps in the log output.
92+
93+Defaults to ``%Y-%m-%d %H:%M:%S``. See the
94+`logging module <http://docs.python.org/library/logging.html>`_ documentation for
95+more information on setting this format string.
96
97=== modified file 'glance/common/config.py'
98--- glance/common/config.py 2011-02-11 00:12:51 +0000
99+++ glance/common/config.py 2011-03-16 16:32:09 +0000
100@@ -35,8 +35,6 @@
101
102 DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
103 DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
104-DEFAULT_LOG_HANDLER = 'stream'
105-LOGGING_HANDLER_CHOICES = ['syslog', 'file', 'stream']
106
107
108 def parse_options(parser, cli_args=None):
109@@ -82,38 +80,7 @@
110 parser.add_option_group(group)
111
112
113-def add_daemon_options(parser):
114- """
115- Given a supplied optparse.OptionParser, adds an OptionGroup that
116- represents all the configuration options around daemonization.
117-
118- :param parser: optparse.OptionParser
119- """
120- help_text = "The following configuration options are specific to "\
121- "the daemonizing of this program."
122-
123- group = optparse.OptionGroup(parser, "Daemon Options", help_text)
124- group.add_option('--config', default=None,
125- help="Configuration file to read when loading "
126- "application. If missing, the first argument is "
127- "used. If no arguments are found, then a set of "
128- "standard directories are searched for a config "
129- "file.")
130- group.add_option("--pid-file", default=None, metavar="PATH",
131- help="(Optional) Name of pid file for the server. "
132- "If not specified, the pid file will be named "
133- "/var/run/glance/<SERVER>.pid.")
134- group.add_option("--uid", type=int, default=os.getuid(),
135- help="uid under which to run. Default: %default")
136- group.add_option("--gid", type=int, default=os.getgid(),
137- help="gid under which to run. Default: %default")
138- group.add_option('--working-directory', '--working-dir',
139- default=os.path.abspath(os.getcwd()),
140- help="The working directory. Default: %default")
141- parser.add_option_group(group)
142-
143-
144-def add_log_options(prog_name, parser):
145+def add_log_options(parser):
146 """
147 Given a supplied optparse.OptionParser, adds an OptionGroup that
148 represents all the configuration options around logging.
149@@ -130,29 +97,25 @@
150 "any other logging options specified. Please see "
151 "the Python logging module documentation for "
152 "details on logging configuration files.")
153- group.add_option('--log-handler', default=DEFAULT_LOG_HANDLER,
154- metavar="HANDLER",
155- choices=LOGGING_HANDLER_CHOICES,
156- help="What logging handler to use? "
157- "Default: %default")
158 group.add_option('--log-date-format', metavar="FORMAT",
159 default=DEFAULT_LOG_DATE_FORMAT,
160 help="Format string for %(asctime)s in log records. "
161 "Default: %default")
162- group.add_option('--log-file', default="%s.log" % prog_name,
163- metavar="PATH",
164- help="(Optional) Name of log file to output to.")
165+ group.add_option('--log-file', default=None, metavar="PATH",
166+ help="(Optional) Name of log file to output to. "
167+ "If not set, logging will go to stdout.")
168 group.add_option("--log-dir", default=None,
169 help="(Optional) The directory to keep log files in "
170 "(will be prepended to --logfile)")
171 parser.add_option_group(group)
172
173
174-def setup_logging(options):
175+def setup_logging(options, conf):
176 """
177 Sets up the logging options for a log with supplied name
178
179 :param options: Mapping of typed option key/values
180+ :param conf: Mapping of untyped key/values from config file
181 """
182
183 if options.get('log_config', None):
184@@ -182,27 +145,24 @@
185 log_date_format = options.get('log_date_format', DEFAULT_LOG_DATE_FORMAT)
186 formatter = logging.Formatter(log_format, log_date_format)
187
188- log_handler = options.get('log_handler', DEFAULT_LOG_HANDLER)
189- if log_handler == 'syslog':
190- syslog = logging.handlers.SysLogHandler(address='/dev/log')
191- syslog.setFormatter(formatter)
192- root_logger.addHandler(syslog)
193- elif log_handler == 'file':
194- logfile = options['log_file']
195- logdir = options['log_dir']
196+ logfile = options.get('log_file')
197+ if not logfile:
198+ logfile = conf.get('log_file')
199+
200+ if logfile:
201+ logdir = options.get('log_dir')
202+ if not logdir:
203+ logdir = conf.get('log_dir')
204 if logdir:
205 logfile = os.path.join(logdir, logfile)
206 logfile = logging.FileHandler(logfile)
207 logfile.setFormatter(formatter)
208 logfile.setFormatter(formatter)
209 root_logger.addHandler(logfile)
210- elif log_handler == 'stream':
211+ else:
212 handler = logging.StreamHandler(sys.stdout)
213 handler.setFormatter(formatter)
214 root_logger.addHandler(handler)
215- else:
216- raise exception.BadInputError(
217- "unrecognized log handler '%(log_handler)s'" % locals())
218
219
220 def find_config_file(options, args):
221@@ -270,6 +230,11 @@
222 "Cannot load application %s" % app_name)
223 try:
224 conf = deploy.appconfig("config:%s" % conf_file, name=app_name)
225+
226+ # Setup logging early, supplying both the CLI options and the
227+ # configuration mapping from the config file
228+ setup_logging(options, conf)
229+
230 # We only update the conf dict for the verbose and debug
231 # flags. Everything else must be set up in the conf file...
232 conf['verbose'] = options['verbose']
233
234=== modified file 'run_tests.py'
235--- run_tests.py 2011-03-16 06:43:16 +0000
236+++ run_tests.py 2011-03-16 16:32:09 +0000
237@@ -129,8 +129,7 @@
238 'yellow': red | green | bold,
239 'magenta': red | blue | bold,
240 'cyan': green | blue | bold,
241- 'white': red | green | blue | bold
242- }
243+ 'white': red | green | blue | bold}
244
245 def supported(cls, stream=sys.stdout):
246 try:
247@@ -238,15 +237,15 @@
248 elif self.dots:
249 stream.write(label[:1])
250 return
251- self.errors.append((test, exc_info))
252- test.passed = False
253- if stream is not None:
254- if self.showAll:
255- self.colorizer.write("ERROR", 'red')
256- self.stream.writeln()
257- elif self.dots:
258- stream.write('E')
259-
260+ self.errors.append((test, exc_info))
261+ test.passed = False
262+ if stream is not None:
263+ if self.showAll:
264+ self.colorizer.write("ERROR", 'red')
265+ self.stream.writeln()
266+ elif self.dots:
267+ stream.write('E')
268+
269 def startTest(self, test):
270 unittest.TestResult.startTest(self, test)
271 current_case = test.test.__class__.__name__
272
273=== modified file 'tests/unit/test_migrations.py'
274--- tests/unit/test_migrations.py 2011-03-08 17:53:25 +0000
275+++ tests/unit/test_migrations.py 2011-03-16 16:32:09 +0000
276@@ -33,8 +33,7 @@
277
278 self.options = dict(sql_connection=sql_connection,
279 verbose=False)
280-
281- config.setup_logging(self.options)
282+ config.setup_logging(self.options, {})
283
284 def tearDown(self):
285 api.configure_db(self.options)
286
287=== modified file 'tests/unit/test_misc.py'
288--- tests/unit/test_misc.py 2011-03-14 19:10:24 +0000
289+++ tests/unit/test_misc.py 2011-03-16 16:32:09 +0000
290@@ -207,3 +207,146 @@
291 cmd = "./bin/glance-control registry stop "\
292 "%s --pid-file=glance-registry.pid" % conf_file_name
293 ignored, out, err = execute(cmd)
294+
295+
296+# TODO(jaypipes): Move this to separate test file once
297+# LP Bug#731304 moves execute() out to a common file, etc
298+class TestLogging(unittest.TestCase):
299+
300+ """Tests that logging can be configured correctly"""
301+
302+ def setUp(self):
303+ self.logfiles = []
304+
305+ def tearDown(self):
306+ self._cleanup_test_servers()
307+ self._cleanup_log_files()
308+
309+ def _cleanup_test_servers(self):
310+ # Clean up any leftover test servers...
311+ pid_files = ('glance-api.pid', 'glance-registry.pid')
312+ for pid_file in pid_files:
313+ if os.path.exists(pid_file):
314+ pid = int(open(pid_file).read().strip())
315+ try:
316+ os.killpg(pid, signal.SIGTERM)
317+ except:
318+ pass # Ignore if the process group is dead
319+ os.unlink(pid_file)
320+
321+ def _cleanup_log_files(self):
322+ for f in self.logfiles:
323+ if os.path.exists(f):
324+ os.unlink(f)
325+
326+ def test_logfile(self):
327+ """
328+ A test that logging can be configured properly from the
329+ glance.conf file with the log_file option.
330+
331+ We start both servers daemonized with a temporary config
332+ file that has some logging options in it.
333+
334+ We then use curl to issue a few requests and verify that each server's
335+ logging statements were logged to the one log file
336+ """
337+ logfile = "/tmp/test_logfile.log"
338+ self.logfiles.append(logfile)
339+
340+ if os.path.exists(logfile):
341+ os.unlink(logfile)
342+
343+ self._cleanup_test_servers()
344+
345+ # Port numbers hopefully not used by anything...
346+ api_port = 32001
347+ reg_port = 32000
348+ image_dir = "/tmp/test.images.%d" % api_port
349+ if os.path.exists(image_dir):
350+ shutil.rmtree(image_dir)
351+
352+ # A config file to use just for this test...we don't want
353+ # to trample on currently-running Glance servers, now do we?
354+ with tempfile.NamedTemporaryFile() as conf_file:
355+ conf_contents = """[DEFAULT]
356+verbose = True
357+debug = True
358+log_file = %(logfile)s
359+
360+[app:glance-api]
361+paste.app_factory = glance.server:app_factory
362+filesystem_store_datadir=%(image_dir)s
363+default_store = file
364+bind_host = 0.0.0.0
365+bind_port = %(api_port)s
366+registry_host = 0.0.0.0
367+registry_port = %(reg_port)s
368+
369+[app:glance-registry]
370+paste.app_factory = glance.registry.server:app_factory
371+bind_host = 0.0.0.0
372+bind_port = %(reg_port)s
373+sql_connection = sqlite://
374+sql_idle_timeout = 3600
375+""" % locals()
376+ conf_file.write(conf_contents)
377+ conf_file.flush()
378+ conf_file_name = conf_file.name
379+
380+ venv = ""
381+ if 'VIRTUAL_ENV' in os.environ:
382+ venv = "tools/with_venv.sh "
383+
384+ # Start up the API and default registry server
385+ cmd = venv + "./bin/glance-control api start "\
386+ "%s --pid-file=glance-api.pid" % conf_file_name
387+ exitcode, out, err = execute(cmd)
388+
389+ self.assertEquals(0, exitcode)
390+ self.assertTrue("Starting glance-api with" in out)
391+
392+ cmd = venv + "./bin/glance-control registry start "\
393+ "%s --pid-file=glance-registry.pid" % conf_file_name
394+ exitcode, out, err = execute(cmd)
395+
396+ self.assertEquals(0, exitcode)
397+ self.assertTrue("Starting glance-registry with" in out)
398+
399+ time.sleep(2) # Gotta give some time for spin up...
400+
401+ cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
402+ "-H 'X-Image-Meta-Name: ImageName' "\
403+ "-H 'X-Image-Meta-Disk-Format: Invalid' "\
404+ "http://0.0.0.0:%d/images" % api_port
405+ ignored, out, err = execute(cmd)
406+
407+ self.assertTrue('Invalid disk format' in out,
408+ "Could not find 'Invalid disk format' "
409+ "in output: %s" % out)
410+
411+ self.assertTrue(os.path.exists(logfile),
412+ "Logfile %s does not exist!" % logfile)
413+
414+ logfile_contents = open(logfile, 'rb').read()
415+
416+ # Check that BOTH the glance API and registry server
417+ # modules are logged to the file.
418+ self.assertTrue('[glance.server]' in logfile_contents,
419+ "Could not find '[glance.server]' "
420+ "in logfile: %s" % logfile_contents)
421+ self.assertTrue('[glance.registry.server]' in logfile_contents,
422+ "Could not find '[glance.registry.server]' "
423+ "in logfile: %s" % logfile_contents)
424+
425+ # Test that the error we caused above is in the log
426+ self.assertTrue('Invalid disk format' in logfile_contents,
427+ "Could not find 'Invalid disk format' "
428+ "in logfile: %s" % logfile_contents)
429+
430+ # Spin down the API and default registry server
431+ cmd = "./bin/glance-control api stop "\
432+ "%s --pid-file=glance-api.pid" % conf_file_name
433+ ignored, out, err = execute(cmd)
434+ cmd = "./bin/glance-control registry stop "\
435+ "%s --pid-file=glance-registry.pid" % conf_file_name
436+ ignored, out, err = execute(cmd)

Subscribers

People subscribed via source and target branches