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

Subscribers

People subscribed via source and target branches