← Back to team overview

graphite-dev team mailing list archive

[Merge] lp:~sidnei/graphite/hardcoded-conf-dir into lp:graphite

 

Sidnei da Silva has proposed merging lp:~sidnei/graphite/hardcoded-conf-dir into lp:graphite.

Requested reviews:
  graphite-dev (graphite-dev)
Related bugs:
  Bug #797845 in Graphite: "Hardcoded path for storage-schemas.conf"
  https://bugs.launchpad.net/graphite/+bug/797845

For more details, see:
https://code.launchpad.net/~sidnei/graphite/hardcoded-conf-dir/+merge/64769

Refactors some of the config parsing logic out into the conf module, and adds some tests for it. Also took the opportunity to cleanup the carbon scripts a bit, though there's a lot more that could be done there (like Bug #797848 for example).
-- 
https://code.launchpad.net/~sidnei/graphite/hardcoded-conf-dir/+merge/64769
Your team graphite-dev is requested to review the proposed merge of lp:~sidnei/graphite/hardcoded-conf-dir into lp:graphite.
=== modified file '.bzrignore'
--- .bzrignore	2009-09-14 14:35:44 +0000
+++ .bzrignore	2011-06-15 22:55:39 +0000
@@ -1,1 +1,2 @@
 MANIFEST
+_trial_temp

=== modified file 'carbon/bin/carbon-aggregator.py'
--- carbon/bin/carbon-aggregator.py	2011-04-05 21:42:51 +0000
+++ carbon/bin/carbon-aggregator.py	2011-06-15 22:55:39 +0000
@@ -1,18 +1,17 @@
 #!/usr/bin/env python
 import sys
 import os
-import optparse
 import atexit
 from os.path import basename, dirname, exists, join, isdir
 
 
-__builtins__.program = basename( sys.argv[0] ).split('.')[0]
+program = basename( sys.argv[0] ).split('.')[0]
 
 # Initialize twisted
 try:
   from twisted.internet import epollreactor
   epollreactor.install()
-except: 
+except:
   pass
 from twisted.internet import reactor
 
@@ -20,45 +19,61 @@
 # Figure out where we're installed
 BIN_DIR = dirname( os.path.abspath(__file__) )
 ROOT_DIR = dirname(BIN_DIR)
-STORAGE_DIR = join(ROOT_DIR, 'storage')
-LOG_DIR = join(STORAGE_DIR, 'log', 'carbon-aggregator')
 LIB_DIR = join(ROOT_DIR, 'lib')
-CONF_DIR = join(ROOT_DIR, 'conf')
 
 sys.path.insert(0, LIB_DIR)
 os.environ['GRAPHITE_ROOT'] = ROOT_DIR
 
-# Parse command line options
-parser = optparse.OptionParser(usage='%prog [options] <start|stop|status>')
-parser.add_option('--debug', action='store_true', help='Run in the foreground, log to stdout')
-parser.add_option('--profile', help='Record performance profile data to the given file')
-parser.add_option('--pidfile', default=join(STORAGE_DIR, '%s.pid' % program), help='Write pid to the given file')
-parser.add_option('--config', default=join(CONF_DIR, 'carbon.conf'), help='Use the given config file')
-parser.add_option('--rules', default=join(CONF_DIR, 'aggregation-rules.conf'), help='Use the give aggregation rules file')
-parser.add_option('--logdir', default=LOG_DIR, help="Write logs in the given directory")
-
-(options, args) = parser.parse_args()
-
-if not args:
-  parser.print_usage()
-  raise SystemExit(1)
-
+# Capture useful debug info for this commonly reported problem
+try:
+  import carbon
+except ImportError:
+  print 'Failed to import carbon, debug information follows.'
+  print 'pwd=%s' % os.getcwd()
+  print 'sys.path=%s' % sys.path
+  print '__file__=%s' % __file__
+  sys.exit(1)
+
+
+# Read config (we want failures to occur before daemonizing)
+from carbon.conf import (get_default_parser, parse_options,
+                         read_config, settings as global_settings)
+
+
+parser = get_default_parser()
+parser.add_option(
+    '--rules',
+    default=None,
+    help='Use the give aggregation rules file')
+
+(options, args) = parse_options(parser)
+settings = read_config(program, options, ROOT_DIR=ROOT_DIR)
+global_settings.update(settings)
+
+if options.rules is None:
+    options.rules = join(settings.CONF_DIR, "aggregation-rules.conf")
+
+pidfile = settings.pidfile
+logdir = settings.LOG_DIR
+
+__builtins__.program = program
 action = args[0]
 
+
 if action == 'stop':
-  if not exists(options.pidfile):
-    print 'Pidfile %s does not exist' % options.pidfile
+  if not exists(pidfile):
+    print 'Pidfile %s does not exist' % pidfile
     raise SystemExit(0)
 
-  pidfile = open(options.pidfile, 'r')
+  pf = open(pidfile, 'r')
   try:
     pid = int( pidfile.read().strip() )
   except:
-    print 'Could not read pidfile %s' % options.pidfile
+    print 'Could not read pidfile %s' % pidfile
     raise SystemExit(1)
 
-  print 'Deleting %s (contained pid %d)' % (options.pidfile, pid)
-  os.unlink(options.pidfile)
+  print 'Deleting %s (contained pid %d)' % (pidfile, pid)
+  os.unlink(pidfile)
 
   print 'Sending kill signal to pid %d' % pid
   os.kill(pid, 15)
@@ -66,15 +81,15 @@
 
 
 elif action == 'status':
-  if not exists(options.pidfile):
+  if not exists(pidfile):
     print '%s is not running' % program
     raise SystemExit(0)
 
-  pidfile = open(options.pidfile, 'r')
+  pf = open(pidfile, 'r')
   try:
     pid = int( pidfile.read().strip() )
   except:
-    print 'Failed to read pid from %s' % options.pidfile
+    print 'Failed to read pid from %s' % pidfile
     raise SystemExit(1)
 
   if exists('/proc/%d' % pid):
@@ -84,21 +99,6 @@
     print "%s is not running" % program
     raise SystemExit(0)
 
-elif action != 'start':
-  parser.print_usage()
-  raise SystemExit(1)
-
-
-if exists(options.pidfile):
-  print "Pidfile %s already exists, is %s already running?" % (options.pidfile, program)
-  raise SystemExit(1)
-
-
-# Read config (we want failures to occur before daemonizing)
-from carbon.conf import settings
-settings.readFrom(options.config, 'aggregator')
-
-
 # Import application components
 from carbon.log import logToStdout, logToDir
 from carbon.instrumentation import startRecording
@@ -112,7 +112,7 @@
 
 RuleManager.read_from(options.rules)
 
-rewrite_rules_conf = join(CONF_DIR, 'rewrite-rules.conf')
+rewrite_rules_conf = join(settings.CONF_DIR, 'rewrite-rules.conf')
 if exists(rewrite_rules_conf):
   RewriteRuleManager.read_from(rewrite_rules_conf)
 
@@ -121,22 +121,22 @@
   logToStdout()
 
 else:
-  if not isdir(options.logdir):
-    os.makedirs(options.logdir)
+  if not isdir(logdir):
+    os.makedirs(logdir)
 
   daemonize()
 
-  pidfile = open(options.pidfile, 'w')
-  pidfile.write( str(os.getpid()) )
-  pidfile.close()
+  pf = open(pidfile, 'w')
+  pf.write( str(os.getpid()) )
+  pf.close()
 
   def shutdown():
-    if os.path.exists(options.pidfile):
-      os.unlink(options.pidfile)
+    if os.path.exists(pidfile):
+      os.unlink(pidfile)
 
   atexit.register(shutdown)
 
-  logToDir(options.logdir)
+  logToDir(logdir)
 
 
 # Configure application components

=== modified file 'carbon/bin/carbon-cache.py'
--- carbon/bin/carbon-cache.py	2011-04-05 21:42:51 +0000
+++ carbon/bin/carbon-cache.py	2011-06-15 22:55:39 +0000
@@ -17,11 +17,9 @@
 import os
 import socket
 import pwd
-import optparse
 import atexit
 from os.path import basename, dirname, exists, join, isdir
 
-
 program = basename( sys.argv[0] ).split('.')[0]
 hostname = socket.gethostname().split('.')[0]
 os.umask(022)
@@ -38,14 +36,10 @@
 # Figure out where we're installed
 BIN_DIR = dirname( os.path.abspath(__file__) )
 ROOT_DIR = dirname(BIN_DIR)
-STORAGE_DIR = join(ROOT_DIR, 'storage')
-LOG_DIR = join(STORAGE_DIR, 'log', 'carbon-cache')
 LIB_DIR = join(ROOT_DIR, 'lib')
-CONF_DIR = join(ROOT_DIR, 'conf')
-__builtins__.CONF_DIR = CONF_DIR # evil I know, but effective.
-
 sys.path.insert(0, LIB_DIR)
 
+
 # Capture useful debug info for this commonly reported problem
 try:
   import carbon
@@ -57,37 +51,25 @@
   sys.exit(1)
 
 
-# Parse command line options
-parser = optparse.OptionParser(usage='%prog [options] <start|stop|status>')
-parser.add_option('--debug', action='store_true', help='Run in the foreground, log to stdout')
-parser.add_option('--profile', help='Record performance profile data to the given file')
-parser.add_option('--pidfile', default=join(STORAGE_DIR, '%s.pid' % program), help='Write pid to the given file')
-parser.add_option('--config', default=join(CONF_DIR, 'carbon.conf'), help='Use the given config file')
-parser.add_option('--logdir', default=LOG_DIR, help="Write logs in the given directory")
-parser.add_option('--instance', default=None, help="Manage a specific carbon instnace")
-
-(options, args) = parser.parse_args()
-
-if not args:
-  parser.print_usage()
-  raise SystemExit(1)
-
-# Assume standard locations when using --instance
-if options.instance:
-  instance = str(options.instance)
-  pidfile = join(STORAGE_DIR, '%s-%s.pid' % (program, instance))
-  logdir = "%s-%s/" % (LOG_DIR.rstrip('/'), instance)
-  config = options.config
-else:
-  instance = None
-  pidfile = options.pidfile
-  logdir = options.logdir
-  config = options.config
+# Read config (we want failures to occur before daemonizing)
+from carbon.conf import (get_default_parser, parse_options,
+                         read_config, settings as global_settings)
+
+
+(options, args) = parse_options(get_default_parser())
+settings = read_config(program, options, ROOT_DIR=ROOT_DIR)
+global_settings.update(settings)
+
+instance = options.instance
+pidfile = settings.pidfile
+logdir = settings.LOG_DIR
+
 
 __builtins__.instance = instance # This isn't as evil as you might think
 __builtins__.program = program
 action = args[0]
 
+
 if action == 'stop':
   if not exists(pidfile):
     print 'Pidfile %s does not exist' % pidfile
@@ -127,23 +109,10 @@
     print "%s (instance %s) is not running" % (program, instance)
     raise SystemExit(0)
 
-elif action != 'start':
-  parser.print_usage()
-  raise SystemExit(1)
-
-
 if exists(pidfile):
   print "Pidfile %s already exists, is %s already running?" % (pidfile, program)
   raise SystemExit(1)
 
-
-# Read config (we want failures to occur before daemonizing)
-from carbon.conf import settings
-settings.readFrom(config, 'cache')
-
-if instance:
-  settings.readFrom(config, 'cache:%s' % instance)
-
 # Import application components
 from carbon.log import logToStdout, logToDir
 from carbon.listeners import MetricLineReceiver, MetricPickleReceiver, CacheQueryHandler, startListener
@@ -151,7 +120,7 @@
 from carbon.instrumentation import startRecording
 from carbon.events import metricReceived
 
-storage_schemas = join(CONF_DIR, 'storage-schemas.conf')
+storage_schemas = join(settings.CONF_DIR, 'storage-schemas.conf')
 if not exists(storage_schemas):
   print "Error: missing required config %s" % storage_schemas
   sys.exit(1)

=== modified file 'carbon/bin/carbon-relay.py'
--- carbon/bin/carbon-relay.py	2011-04-05 21:42:51 +0000
+++ carbon/bin/carbon-relay.py	2011-06-15 22:55:39 +0000
@@ -15,18 +15,18 @@
 
 import sys
 import os
-import optparse
 import atexit
 from os.path import basename, dirname, exists, join, isdir
 
+program = basename( sys.argv[0] ).split('.')[0]
+os.umask(022)
 
-__builtins__.program = basename( sys.argv[0] ).split('.')[0]
 
 # Initialize twisted
 try:
   from twisted.internet import epollreactor
   epollreactor.install()
-except: 
+except:
   pass
 from twisted.internet import reactor
 
@@ -34,46 +34,60 @@
 # Figure out where we're installed
 BIN_DIR = dirname(__file__)
 ROOT_DIR = dirname(BIN_DIR)
-STORAGE_DIR = join(ROOT_DIR, 'storage')
-LOG_DIR = join(STORAGE_DIR, 'log', 'carbon-relay')
 LIB_DIR = join(ROOT_DIR, 'lib')
-CONF_DIR = join(ROOT_DIR, 'conf')
-__builtins__.CONF_DIR = CONF_DIR
-
 sys.path.insert(0, LIB_DIR)
 
 
-# Parse command line options
-parser = optparse.OptionParser(usage='%prog [options] <start|stop|status>')
-parser.add_option('--debug', action='store_true', help='Run in the foreground, log to stdout')
-parser.add_option('--profile', help='Record performance profile data to the given file')
-parser.add_option('--pidfile', default=join(STORAGE_DIR, '%s.pid' % program), help='Write pid to the given file')
-parser.add_option('--config', default=join(CONF_DIR, 'carbon.conf'), help='Use the given config file')
-parser.add_option('--rules', default=join(CONF_DIR, 'relay-rules.conf'), help='Use the give relay rules file')
-parser.add_option('--logdir', default=LOG_DIR, help="Write logs in the given directory")
-
-(options, args) = parser.parse_args()
-
-if not args:
-  parser.print_usage()
-  raise SystemExit(1)
-
+# Capture useful debug info for this commonly reported problem
+try:
+  import carbon
+except ImportError:
+  print 'Failed to import carbon, debug information follows.'
+  print 'pwd=%s' % os.getcwd()
+  print 'sys.path=%s' % sys.path
+  print '__file__=%s' % __file__
+  sys.exit(1)
+
+
+# Read config (we want failures to occur before daemonizing)
+from carbon.conf import (get_default_parser, parse_options,
+                         read_config, settings as global_settings)
+
+
+parser = get_default_parser()
+parser.add_option(
+    '--rules',
+    default=None,
+    help='Use the given relay rules file')
+
+(options, args) = parse_options(parser)
+settings = read_config(program, options, ROOT_DIR=ROOT_DIR)
+global_settings.update(settings)
+
+if options.rules is None:
+    options.rules = join(settings.CONF_DIR, "relay-rules.conf")
+
+pidfile = settings.pidfile
+logdir = settings.LOG_DIR
+
+__builtins__.program = program
 action = args[0]
 
+
 if action == 'stop':
-  if not exists(options.pidfile):
-    print 'Pidfile %s does not exist' % options.pidfile
+  if not exists(pidfile):
+    print 'Pidfile %s does not exist' % pidfile
     raise SystemExit(0)
 
-  pidfile = open(options.pidfile, 'r')
+  pf = open(pidfile, 'r')
   try:
-    pid = int( pidfile.read().strip() )
+    pid = int( pf.read().strip() )
   except:
-    print 'Could not read pidfile %s' % options.pidfile
+    print 'Could not read pidfile %s' % pidfile
     raise SystemExit(1)
 
-  print 'Deleting %s (contained pid %d)' % (options.pidfile, pid)
-  os.unlink(options.pidfile)
+  print 'Deleting %s (contained pid %d)' % (pidfile, pid)
+  os.unlink(pidfile)
 
   print 'Sending kill signal to pid %d' % pid
   os.kill(pid, 15)
@@ -81,15 +95,15 @@
 
 
 elif action == 'status':
-  if not exists(options.pidfile):
+  if not exists(pidfile):
     print '%s is not running' % program
     raise SystemExit(0)
 
-  pidfile = open(options.pidfile, 'r')
+  pf = open(pidfile, 'r')
   try:
-    pid = int( pidfile.read().strip() )
+    pid = int( pf.read().strip() )
   except:
-    print 'Failed to read pid from %s' % options.pidfile
+    print 'Failed to read pid from %s' % pidfile
     raise SystemExit(1)
 
   if exists('/proc/%d' % pid):
@@ -99,19 +113,9 @@
     print "%s is not running" % program
     raise SystemExit(0)
 
-elif action != 'start':
-  parser.print_usage()
-  raise SystemExit(1)
-
-
-if exists(options.pidfile):
-  print "Pidfile %s already exists, is %s already running?" % (options.pidfile, program)
-  raise SystemExit(1)
-
-
-# Read config (we want failures to occur before daemonizing)
-from carbon.conf import settings
-settings.readFrom(options.config, 'relay')
+if exists(pidfile):
+  print "Pidfile %s already exists, is %s already running?" % (pidfile, program)
+  raise SystemExit(1)
 
 # Quick validation
 if settings.RELAY_METHOD not in ('rules', 'consistent-hashing'):
@@ -131,20 +135,20 @@
 if options.debug:
   logToStdout()
 else:
-  if not isdir(options.logdir):
-    os.makedirs(options.logdir)
+  if not isdir(logdir):
+    os.makedirs(logdir)
 
   from carbon.util import daemonize
   daemonize()
-  logToDir(options.logdir)
+  logToDir(logdir)
 
-  pidfile = open(options.pidfile, 'w')
+  pidfile = open(pidfile, 'w')
   pidfile.write( str(os.getpid()) )
   pidfile.close()
 
   def shutdown():
-    if os.path.exists(options.pidfile):
-      os.unlink(options.pidfile)
+    if os.path.exists(pidfile):
+      os.unlink(pidfile)
 
   atexit.register(shutdown)
 

=== modified file 'carbon/lib/carbon/conf.py'
--- carbon/lib/carbon/conf.py	2011-04-05 20:30:50 +0000
+++ carbon/lib/carbon/conf.py	2011-06-15 22:55:39 +0000
@@ -12,6 +12,8 @@
 See the License for the specific language governing permissions and
 limitations under the License."""
 
+from os.path import join, dirname, normpath
+from optparse import OptionParser
 from ConfigParser import ConfigParser
 
 
@@ -113,3 +115,100 @@
 
 settings = Settings()
 settings.update(defaults)
+
+
+def get_default_parser(usage="%prog [options] <start|stop|status>"):
+    """Create a parser for command line options."""
+    parser = OptionParser(usage=usage)
+    parser.add_option(
+        "--debug", action="store_true",
+        help="Run in the foreground, log to stdout")
+    parser.add_option(
+        "--profile",
+        help="Record performance profile data to the given file")
+    parser.add_option(
+        "--pidfile", default=None,
+        help="Write pid to the given file")
+    parser.add_option(
+        "--config",
+        default=None,
+        help="Use the given config file")
+    parser.add_option(
+        "--logdir",
+        default=None,
+        help="Write logs in the given directory")
+    parser.add_option(
+        "--instance",
+        default=None,
+        help="Manage a specific carbon instance")
+
+    return parser
+
+
+def parse_options(parser, args):
+    """
+    Parse command line options and print usage message if no arguments were
+    provided for the command.
+    """
+    (options, args) = parser.parse_args(args)
+
+    if not args:
+        parser.print_usage()
+        raise SystemExit(1)
+
+    if args[0] not in ("start", "stop", "status"):
+        parser.print_usage()
+        raise SystemExit(1)
+
+    return options, args
+
+
+def read_config(program, options, **kwargs):
+    """
+    Read settings for 'program' from configuration file specified by
+    'options.config', with missing values provided by 'defaults'.
+    """
+    settings = Settings()
+    settings.update(defaults)
+
+    # Initialize default values if not set yet.
+    for name, value in kwargs.items():
+        settings.setdefault(name, value)
+
+    # At minimum, 'ROOT_DIR' needs to be set.
+    if settings.get("ROOT_DIR", None) is None:
+        raise ValueError("ROOT_DIR needs to be provided.")
+
+    if options.config is None:
+        raise ValueError("--config needs to be provided.")
+
+    # Default config directory to same directory as the specified carbon
+    # configuration file.
+    settings.setdefault("CONF_DIR", dirname(normpath(options.config)))
+
+    # Other settings default to relative paths from the root directory, for
+    # backwards-compatibility.
+    settings.setdefault("STORAGE_DIR", join(settings.ROOT_DIR, "storage"))
+    settings.setdefault("LOG_DIR", join(settings.STORAGE_DIR, "log", program))
+
+    # Read configuration options from program-specific section.
+    section = program[len("carbon-"):]
+    settings.readFrom(options.config, section)
+
+    # If a specific instance of the program is specified, augment the settings
+    # with the instance-specific settings and provide sane defaults for
+    # optional settings.
+    if options.instance:
+        settings.readFrom(options.config, "%s:%s" % (section, options.instance))
+        settings["pidfile"] = (options.pidfile or
+                               join(settings.STORAGE_DIR,
+                                    "%s-%s.pid" % (program, options.instance)))
+        settings["LOG_DIR"] = (options.logdir or
+                              "%s-%s" % (settings.LOG_DIR.rstrip('/'),
+                                          options.instance))
+    else:
+        settings["pidfile"] = (options.pidfile or
+                               join(settings.STORAGE_DIR, '%s.pid' % program))
+        settings["LOG_DIR"] = (options.logdir or settings.LOG_DIR)
+
+    return settings

=== modified file 'carbon/lib/carbon/storage.py'
--- carbon/lib/carbon/storage.py	2011-02-04 06:09:28 +0000
+++ carbon/lib/carbon/storage.py	2011-06-15 22:55:39 +0000
@@ -14,7 +14,7 @@
 
 import os, re
 from os.path import join, exists
-from carbon.conf import OrderedConfigParser, Settings, settings
+from carbon.conf import OrderedConfigParser, settings
 
 try:
   import cPickle as pickle
@@ -22,8 +22,8 @@
   import pickle
 
 
-STORAGE_SCHEMAS_CONFIG = join(CONF_DIR, 'storage-schemas.conf')
-STORAGE_LISTS_DIR = join(CONF_DIR, 'lists')
+STORAGE_SCHEMAS_CONFIG = join(settings.CONF_DIR, 'storage-schemas.conf')
+STORAGE_LISTS_DIR = join(settings.CONF_DIR, 'lists')
 
 
 def getFilesystemPath(metric):

=== added directory 'carbon/lib/carbon/tests'
=== added file 'carbon/lib/carbon/tests/__init__.py'
=== added file 'carbon/lib/carbon/tests/test_conf.py'
--- carbon/lib/carbon/tests/test_conf.py	1970-01-01 00:00:00 +0000
+++ carbon/lib/carbon/tests/test_conf.py	2011-06-15 22:55:39 +0000
@@ -0,0 +1,313 @@
+from os.path import dirname, join
+from unittest import TestCase
+from mocker import MockerTestCase
+from carbon.conf import get_default_parser, parse_options, read_config
+
+
+class FakeParser(object):
+
+    def __init__(self):
+        self.called = []
+
+    def parse_args(self, args):
+        return object(), args
+
+    def print_usage(self):
+        self.called.append("print_usage")
+
+
+class FakeOptions(object):
+
+    def __init__(self, **kwargs):
+        self.__dict__.update(kwargs)
+
+
+class DefaultParserTest(TestCase):
+
+    def test_default_parser(self):
+        """Check default parser settings."""
+        parser = get_default_parser()
+        self.assertTrue(parser.has_option("--debug"))
+        self.assertEqual(None, parser.defaults["debug"])
+        self.assertTrue(parser.has_option("--profile"))
+        self.assertEqual(None, parser.defaults["profile"])
+        self.assertTrue(parser.has_option("--pidfile"))
+        self.assertEqual(None, parser.defaults["pidfile"])
+        self.assertTrue(parser.has_option("--config"))
+        self.assertEqual(None, parser.defaults["config"])
+        self.assertTrue(parser.has_option("--logdir"))
+        self.assertEqual(None, parser.defaults["logdir"])
+        self.assertTrue(parser.has_option("--instance"))
+        self.assertEqual(None, parser.defaults["instance"])
+
+
+class ParseOptionsTest(TestCase):
+
+    def test_no_args_prints_usage_and_exit(self):
+        """
+        If no arguments are provided, the usage help will be printed and a
+        SystemExit exception will be raised.
+        """
+        parser = FakeParser()
+        self.assertRaises(SystemExit, parse_options, parser, ())
+        self.assertEqual(["print_usage"], parser.called)
+
+    def test_no_valid_args_prints_usage_and_exit(self):
+        """
+        If an argument which isn't a valid command was provided, 'print_usage'
+        will be called and a SystemExit exception will be raised.
+        """
+        parser = FakeParser()
+        self.assertRaises(SystemExit, parse_options, parser, ("bazinga!",))
+        self.assertEqual(["print_usage"], parser.called)
+
+    def test_valid_args(self):
+        """
+        If a valid argument is provided, it will be returned along with
+        options.
+        """
+        parser = FakeParser()
+        options, args = parser.parse_args(("start",))
+        self.assertEqual(("start",), args)
+
+
+class ReadConfigTest(MockerTestCase):
+
+    def test_root_dir_is_required(self):
+        """
+        At minimum, the caller must provide a 'ROOT_DIR' setting.
+        """
+        try:
+            read_config("carbon-foo", FakeOptions())
+        except ValueError, e:
+            self.assertEqual("ROOT_DIR needs to be provided.", e.message)
+        else:
+            self.fail("Did not raise exception.")
+
+    def test_config_is_required(self):
+        """
+        The 'options' object must have a non-None 'config' attribute.
+        """
+        try:
+            read_config("carbon-foo", FakeOptions(config=None), ROOT_DIR="foo")
+        except ValueError, e:
+            self.assertEqual("--config needs to be provided.", e.message)
+        else:
+            self.fail("Did not raise exception.")
+
+    def test_conf_dir_defaults_to_config_dirname(self):
+        """
+        The 'CONF_DIR' setting defaults to the parent directory of the
+        provided configuration file.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance=None,
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo")
+        self.assertEqual(dirname(config), settings.CONF_DIR)
+
+    def test_storage_dir_relative_to_root_dir(self):
+        """
+        The 'STORAGE_DIR' setting defaults to the 'storage' directory relative
+        to the 'ROOT_DIR' setting.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance=None,
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo")
+        self.assertEqual(join("foo", "storage"), settings.STORAGE_DIR)
+
+    def test_log_dir_relative_to_storage_dir(self):
+        """
+        The 'LOG_DIR' setting defaults to a program-specific directory relative
+        to the 'STORAGE_DIR' setting.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance=None,
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo")
+        self.assertEqual(join("foo", "storage", "log", "carbon-foo"),
+                         settings.LOG_DIR)
+
+    def test_log_dir_relative_to_provided_storage_dir(self):
+        """
+        Providing a different 'STORAGE_DIR' in defaults overrides the default
+        of being relative to 'ROOT_DIR'.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance=None,
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo", STORAGE_DIR="bar")
+        self.assertEqual(join("bar", "log", "carbon-foo"),
+                         settings.LOG_DIR)
+
+    def test_log_dir_for_instance_relative_to_storage_dir(self):
+        """
+        The 'LOG_DIR' setting defaults to a program-specific directory relative
+        to the 'STORAGE_DIR' setting. In the case of an instance, the instance
+        name is appended to the directory.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance="x",
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo")
+        self.assertEqual(join("foo", "storage", "log", "carbon-foo-x"),
+                         settings.LOG_DIR)
+
+    def test_log_dir_for_instance_relative_to_provided_storage_dir(self):
+        """
+        Providing a different 'STORAGE_DIR' in defaults overrides the default
+        of being relative to 'ROOT_DIR'. In the case of an instance, the
+        instance name is appended to the directory.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance="x",
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo", STORAGE_DIR="bar")
+        self.assertEqual(join("bar", "log", "carbon-foo-x"),
+                         settings.LOG_DIR)
+
+    def test_pidfile_relative_to_storage_dir(self):
+        """
+        The 'pidfile' setting defaults to a program-specific filename relative
+        to the 'STORAGE_DIR' setting.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance=None,
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo")
+        self.assertEqual(join("foo", "storage", "carbon-foo.pid"),
+                         settings.pidfile)
+
+    def test_pidfile_in_options_has_precedence(self):
+        """
+        The 'pidfile' option from command line overrides the default setting.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance=None,
+                        pidfile="foo.pid", logdir=None),
+            ROOT_DIR="foo")
+        self.assertEqual("foo.pid", settings.pidfile)
+
+    def test_pidfile_for_instance_in_options_has_precedence(self):
+        """
+        The 'pidfile' option from command line overrides the default setting
+        for the instance, if one is specified.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance="x",
+                        pidfile="foo.pid", logdir=None),
+            ROOT_DIR="foo")
+        self.assertEqual("foo.pid", settings.pidfile)
+
+    def test_storage_dir_as_provided(self):
+        """
+        Providing a 'STORAGE_DIR' in defaults overrides the root-relative
+        default.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance=None,
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo", STORAGE_DIR="bar")
+        self.assertEqual("bar", settings.STORAGE_DIR)
+
+    def test_log_dir_as_provided(self):
+        """
+        Providing a 'LOG_DIR' in defaults overrides the storage-relative
+        default.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance=None,
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo", STORAGE_DIR="bar", LOG_DIR='baz')
+        self.assertEqual("baz", settings.LOG_DIR)
+
+    def test_log_dir_from_options(self):
+        """
+        Providing a 'LOG_DIR' in the command line overrides the
+        storage-relative default.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance=None,
+                        pidfile=None, logdir="baz"),
+            ROOT_DIR="foo")
+        self.assertEqual("baz", settings.LOG_DIR)
+
+    def test_log_dir_for_instance_from_options(self):
+        """
+        Providing a 'LOG_DIR' in the command line overrides the
+        storage-relative default for the instance.
+        """
+        config = self.makeFile(content="[foo]")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance="x",
+                        pidfile=None, logdir="baz"),
+            ROOT_DIR="foo")
+        self.assertEqual("baz", settings.LOG_DIR)
+
+    def test_storage_dir_from_config(self):
+        """
+        Providing a 'STORAGE_DIR' in the configuration file overrides the
+        root-relative default.
+        """
+        config = self.makeFile(content="[foo]\nSTORAGE_DIR = bar")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance=None,
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo")
+        self.assertEqual("bar", settings.STORAGE_DIR)
+
+    def test_log_dir_from_config(self):
+        """
+        Providing a 'LOG_DIR' in the configuration file overrides the
+        storage-relative default.
+        """
+        config = self.makeFile(content="[foo]\nLOG_DIR = baz")
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance=None,
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo")
+        self.assertEqual("baz", settings.LOG_DIR)
+
+    def test_log_dir_from_instance_config(self):
+        """
+        Providing a 'LOG_DIR' for the specific instance in the configuration
+        file overrides the storage-relative default. The actual value will have
+        the instance name appended to it and ends with a forward slash.
+        """
+        config = self.makeFile(
+            content=("[foo]\nLOG_DIR = baz\n"
+                     "[foo:x]\nLOG_DIR = boo"))
+        settings = read_config(
+            "carbon-foo",
+            FakeOptions(config=config, instance="x",
+                        pidfile=None, logdir=None),
+            ROOT_DIR="foo")
+        self.assertEqual("boo-x", settings.LOG_DIR)


Follow ups