Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
...
Checking mergeability… Don't worry, you can still create the pull request.
  • 4 commits
  • 7 files changed
  • 0 commit comments
  • 1 contributor
Commits on Apr 28, 2012
@progrium making sure cwd is in python path so you can load services in modules…
… in your current path
48e1ab9
@progrium adding ability to force set a setting, preventing it from being set a…
…gain unless forced. used by ginkgoctl to force set the daemon setting, otherwise configuration could tell it not to
41ab7b5
@progrium turns out to be nice to use ginkgoctl on services running in the fore…
…ground with ginkgo, so we'll always create a pidfile unless explicitly set to False
0aab84a
Commits on Apr 29, 2012
@progrium got further on quickstart. added inspiration to intro. cd9cc4f
View
7 docs/conf.py
@@ -12,6 +12,7 @@
# serve to show the default.
import sys, os
+from ginkgo import __version__
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -40,7 +41,7 @@
master_doc = 'index'
# General information about the project.
-project = u'ginkgo'
+project = u'Ginkgo'
copyright = u'2012, Jeff Lindsay'
# The version info for the project you're documenting, acts as replacement for
@@ -48,9 +49,9 @@
# built documents.
#
# The short X.Y version.
-version = '0.1'
+version = __version__
# The full version, including alpha/beta/rc tags.
-release = '0.1'
+release = __version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
View
4 docs/index.rst
@@ -36,10 +36,8 @@ contain other services, manage async operations, and expose configuration.
Around this little bit of structure and convention, Ginkgo provides just a few
baseline features to make building both complex and simple network daemons much
-easier.
+easier:
-Features
-========
- Service class primitive for composing daemon apps from simple components
- Dynamic configuration loaded from regular Python source files
- Runner and service manager tool for easy, consistent usage and deployment
View
28 docs/user/advanced.rst
@@ -0,0 +1,28 @@
+Advanced Usage and Patterns
+===========================
+
+Service State Machine
+---------------------
+
+TODO
+
+Service Factory in Config
+-------------------------
+
+TODO
+
+Using Configuration Groups
+--------------------------
+
+TODO
+
+Using ZeroMQ
+------------
+
+TODO
+
+Async Backends
+--------------
+
+TODO
+
View
20 docs/user/intro.rst
@@ -35,3 +35,23 @@ While Ginkgo will remain focused on "baseline" features common to pretty much
all network daemons, a supplementary project to act as a "standard library" for
Ginkgo applications is planned. Together with Ginkgo, the vision would be to
quickly "throw together" distributed systems from simple primitives.
+
+Inspiration
+-----------
+Most of Ginkgo was envisioned by taking good ideas from other projects,
+simplifying to their essential properties, and integrating them together. A lot
+of thanks goes out to these projects.
+
+Twisted is the first great Python evented daemon framework. The two big ideas
+borrowed from Twisted are their application framework and twistd. They directly
+inspired the service model and the Ginkgo runner.
+
+Trac is known for the problem it solves, and not so much for its great
+architecture. However, its component model and configuration API were a big
+influence on Ginkgo. Trac components are how we think of Ginkgo services, and
+the way Ginkgo defines configuration settings is directly inspired by the Trac
+configuration API.
+
+These projects also had some influence on Ginkgo's design and philosophy:
+Gunicorn, Mongrel, Apache, Django, Flask, python-daemon, Diesel, Tornado,
+Erlang/OTP, Typeface, Akka, Configgy, Ostrich, and others.
View
325 docs/user/quickstart.rst
@@ -1,3 +1,306 @@
+Quickstart
+==========
+
+Before you get started, be sure you have Ginkgo installed.
+
+Hello World Service
+-------------------
+The simplest service you could write looks something like this:
+
+::
+
+ from ginkgo import Service
+
+ class HelloWorld(Service):
+ def do_start(self):
+ self.spawn(self.hello_forever)
+
+ def hello_forever(self):
+ while True:
+ print "Hello World"
+ self.async.sleep(1)
+
+If you save this as *service.py* you can run it with the Ginkgo runner:
+
+::
+
+ $ ginkgo service.HelloWorld
+
+This should run your service, giving you a stream of "Hello World" lines.
+
+To stop your service, hit Ctrl+C.
+
+Using Configuration
+-------------------
+Add the ``-h`` argument flag to our runner call:
+
+::
+
+ $ ginkgo service.HelloWorld -h
+
+You'll see that the ``ginkgo`` runner command itself is very simple, but what's
+interesting is the last section:
+
+::
+
+ config settings:
+ daemon True or False whether to daemonize [False]
+ group Change to a different group before running [None]
+ logconfig Configuration of standard Python logger. Can be dict for basicConfig,
+ dict with version key for dictConfig, or ini filepath for fileConfig. [None]
+ logfile Path to primary log file. Ignored if logconfig is set. [/tmp/HelloWorld.log]
+ loglevel Log level to use. Valid options: debug, info, warning, critical
+ Ignored if logconfig is set. [debug]
+ pidfile Path to pidfile to use when daemonizing [None]
+ rundir Change to a directory before running [None]
+ umask Change file mode creation mask before running [None]
+ user Change to a different user before running [None]
+
+These are builtin settings and their default values. If you want to set any of
+these, you have to create a configuration file. But you can also create your
+own settings, so let's first change our Hello World service to be configurable:
+
+::
+
+ from ginkgo import Service, Setting
+
+ class HelloWorld(Service):
+ message = Setting("message", default="Hello World",
+ help="Message to print out while running")
+
+ def do_start(self):
+ self.spawn(self.message_forever)
+
+ def message_forever(self):
+ while True:
+ print self.message
+ self.async.sleep(1)
+
+Running ``ginkgo service.HelloWorld -h`` again should now include your new
+setting. Let's create a configuration file now called *service.conf.py*:
+
+::
+
+ import os
+ daemon = bool(os.environ.get("DAEMONIZE", False))
+ message = "Services all the way down."
+ service = "service.HelloWorld"
+
+A configuration file is simply a valid Python source file. In it, you define
+variables of any type using the setting name to set them.
+
+There's a special setting calling ``service`` that must be set, which is the
+class path target telling it what service to run. To run with this
+configuration, you just point ``ginkgo`` to the configuration file:
+
+::
+
+ $ ginkgo service.conf.py
+
+And it should start and you should see "Services all the way down" repeating.
+
+You don't have direct access to set config settings from the ``ginkgo`` tool,
+but you can set values in your config to pull from the environment. For
+example, our configuration above lets us force our service to daemonize by
+setting the ``DAEMONIZE`` environment variable.
+
+::
+
+ $ DAEMONIZE=yes ginkgo service.conf.py
+
+To stop the daemonized process, you can manually kill it or use the service
+management tool ``ginkgoctl``:
+
+::
+
+ $ ginkgoctl service.conf.py stop
+
+Service Manager
+---------------
+Running and stopping your service is easy with ``ginkgo``, but once you
+daemonize, it gets harder to interface with it. The ``ginkgoctl`` utility is
+for managing a daemonized service process.
+
+::
+
+ $ ginkgoctl -h
+ usage: ginkgoctl [-h] [-v] [-p PID]
+ [target] {start,stop,restart,reload,status,log,logtail}
+
+ positional arguments:
+ target service class path to use (modulename.ServiceClass) or
+ configuration file path to use (/path/to/config.py)
+ {start,stop,restart,reload,status,log,logtail}
+
+ optional arguments:
+ -h, --help show this help message and exit
+ -v, --version show program's version number and exit
+ -p PID, --pid PID pid or pidfile to use instead of target
+
+Like ``ginkgo`` it takes a target class path or configuration file. For
+``stop``, ``reload``, and ``status`` it can also just take a pid or pidfile
+with the ``pid`` argument.
+
+Using ``ginkgoctl`` will always force your service to daemonize
+when you use the ``start`` action.
+
+Service Model and Reloading
+---------------------------
+Our service model lets you implement three main hooks on services:
+``do_start()``, ``do_stop()``, and ``do_reload()``. We've used ``do_start()``,
+which is run when a service is starting up. Not surprisingly, ``do_stop()`` is
+run when a service is shutting down. When is ``do_reload()`` run? Well,
+whenever ``reload()`` is called. :)
+
+Services are designed to contain other services like object composition. Though
+after adding services to a service, when you call any of the service interface
+methods, they will propogate down to child services. This is done in the actual
+``start()``, ``stop()``, and ``reload()`` methods. The ``do_`` methods are for
+you to implement specifically what happens for *that* service to
+start/stop/reload.
+
+So when is ``reload()`` called? Okay, I'll skip ahead and just say it gets
+called when the process receives a SIGHUP signal. As you may have guessed, for
+convenience, this is exposed in ``ginkgoctl`` with the ``reload`` action.
+
+The semantics of ``reload`` are up to you and your application or service.
+Though one thing happens automatically when a process gets a reload signal:
+configuration is reloaded.
+
+One use of ``do_reload()`` is to take new configuration and perform any
+operations to apply that configuration to your running service. However, as
+long as you access a configuration setting by reference via the ``Setting``
+descriptor, you may not need to do anything -- the value will just update in
+real-time.
+
+Let's see this in action. We'll change our Hello World service to have a
+``rate_per_minute`` setting that will be used for our delay between messages.
+
+::
+
+ from ginkgo import Service, Setting
+
+ class HelloWorld(Service):
+ message = Setting("message", default="Hello World",
+ help="Message to print out while running")
+
+ rate = Setting("rate_per_minute", default=60,
+ help="Rate at which to emit message")
+
+ def do_start(self):
+ self.spawn(self.message_forever)
+
+ def message_forever(self):
+ while True:
+ print self.message
+ self.async.sleep(60.0 / self.rate)
+
+The default is 60 messages a minute, which results in the same behavior as
+before. So let's change our configuration to use a different rate:
+
+::
+
+ import os
+ daemon = bool(os.environ.get("DAEMONIZE", False))
+ message = "Services all the way down."
+ rate_per_minute = 180
+ service = "service.HelloWorld"
+
+Use ``ginkgo`` to start the service:
+
+::
+
+ $ ginkgo service.conf.py
+
+As you can see, it's emitting messages a bit faster now. About 3 per second.
+Now while that's running, open the configuration file and change
+rate_per_minute to some other value. Then, in another terminal, change to that
+directory and reload:
+
+::
+
+ $ ginkgoctl service.conf.py reload
+
+Look back at your running service to see that it's now using the new emit rate.
+
+Using Logging
+-------------
+Logging with Ginkgo is based on standard Python logging. We make sure it works
+with daemonization and provide Ginkgo-friendly ways to configure it with good
+defaults. We even support reloading logging configuration.
+
+Out of the box, you can just start logging. We encourage you to use the common
+convention of module level loggers, but obviously there is a lot of freedom in
+how you use Python logging. Let's add some logging to our Hello World,
+including changing our print call to a logger call as it's better practice:
+
+::
+
+ import logging
+ from ginkgo import Service, Setting
+
+ logger = logging.getLogger(__name__)
+
+ class HelloWorld(Service):
+ message = Setting("message", default="Hello World",
+ help="Message to print out while running")
+
+ rate = Setting("rate_per_minute", default=60,
+ help="Rate at which to emit message")
+
+ def do_start(self):
+ logger.info("Starting up!")
+ self.spawn(self.message_forever)
+
+ def do_stop(self):
+ logger.info("Goodbye.")
+
+ def message_forever(self):
+ while True:
+ logger.info(self.message)
+ self.async.sleep(60.0 / self.rate)
+
+Let's run it with our existing configuration for a bit and then stop:
+
+::
+
+ $ ginkgo service.conf.py
+ Starting process with service.conf.py...
+ 2012-04-28 17:21:32,608 INFO service: Starting up!
+ 2012-04-28 17:21:32,608 INFO service: Services all the way down.
+ 2012-04-28 17:21:33,609 INFO service: Services all the way down.
+ 2012-04-28 17:21:34,610 INFO service: Services all the way down.
+ 2012-04-28 17:21:35,714 INFO service: Goodbye.
+ 2012-04-28 17:21:35,714 INFO runner: Stopping.
+
+Running ``-h`` will show you that the default logfile is going to be
+*/tmp/HelloWorld.log*, which logging will create and append to if you
+daemonize.
+
+To configure logging, Ginkgo exposes two settings for simple case
+configuration: ``logfile`` and ``loglevel``. If that's not enough, you can use
+``logconfig``, which will override any value for ``logfile`` and ``loglevel``.
+
+Using ``logconfig`` you can configure logging as expressed by
+``logging.basicConfig``. By default, if you set ``logconfig`` to a dictionary,
+it will apply those keyword arguments to ``logging.basicConfig``. You can
+learn more about ``logging.basicConfig``
+`here <http://docs.python.org/library/logging.html#logging.basicConfig>`_.
+
+For advanced configuration, we also let you use ``logging.config`` from the
+``logconfig`` setting. If ``logconfig`` is a dictionary with a ``version`` key,
+we will load it into ``logging.config.dictConfig``. If ``logconfig`` is a path
+to a file, we load it into ``logging.config.fileConfig``. Both of these are
+ways to define a configuration structure that lets you create just about any
+logging configuration. Read more about ``logging.config``
+`here <http://docs.python.org/library/logging.config.html#module-logging.config>`_.
+
+
+Writing a Server
+----------------
+
+TODO
+
Here's what writing a simple server application looks like:
::
@@ -30,16 +333,24 @@ Here's what writing a simple server application looks like:
except IOError:
break # Connection lost
-With this module you now have a configurable, daemonizable server ready to be
-deployed. Ginkgo gives you a simple runner to execute your app:
-::
- $ ginkgo server.NumberServer
+Writing a Client
+----------------
-As well as a more full featured service management tool:
+TODO
-::
+Composing Services
+------------------
+
+TODO
+
+Async Programming
+-----------------
+
+TODO
- $ ginkgoctl server.NumberServer start
+Using a Web Framework
+---------------------
+TODO
View
10 ginkgo/config.py
@@ -12,6 +12,7 @@ class Config(object):
"""
_settings = {}
_descriptors = []
+ _forced_settings = set()
_last_file = None
def _normalize_path(self, path):
@@ -21,9 +22,14 @@ def get(self, path, default=None):
"""gets the current value of a setting"""
return self._settings.get(self._normalize_path(path), default)
- def set(self, path, value):
+ def set(self, path, value, force=False):
"""sets the value of a setting"""
- self._settings[self._normalize_path(path)] = value
+ path = self._normalize_path(path)
+ if force or path not in self._forced_settings:
+ self._settings[self._normalize_path(path)] = value
+ if force:
+ self._forced_settings.add(path)
+
def group(self, path=''):
"""returns a Group object for the given path"""
View
16 ginkgo/runner.py
@@ -13,6 +13,8 @@
STOP_SIGNAL = signal.SIGTERM
RELOAD_SIGNAL = signal.SIGHUP
+sys.path.insert(0, os.getcwd())
+
logger = logging.getLogger(__name__)
def run_ginkgo():
@@ -61,7 +63,7 @@ def run_ginkgoctl():
args = parser.parse_args()
if args.pid and args.target:
parser.error("You cannot specify both a target and a pid")
- ginkgo.settings.set("daemon", True)
+ ginkgo.settings.set("daemon", True, force=True)
try:
if args.action in "start restart log logtail".split():
if not args.target:
@@ -112,8 +114,8 @@ def load_class(class_path):
try:
return module[class_name]
except KeyError, e:
- raise RuntimeError("Unable to load class path: {}:\n{}".format(
- class_path, e))
+ raise RuntimeError("Unable to find class in module: {}".format(
+ class_path))
def prepare_app(target):
if os.path.exists(target):
@@ -208,7 +210,7 @@ def __init__(self, app_factory, config=None):
self.config = config or ginkgo.settings
self.logger = ginkgo.logger.Logger(self)
- if self.daemon:
+ if self.pidfile is not False:
if self.pidfile is None:
self.config.set("pidfile",
"/tmp/{}.pid".format(self.service_name))
@@ -239,9 +241,11 @@ def do_start(self):
ginkgo.util.prevent_core_dump()
ginkgo.util.daemonize(
preserve_fds=self.logger.file_descriptors)
+ self.logger.capture_stdio()
self.pid = os.getpid()
+
+ if self.pidfile:
self.pidfile.create(self.pid)
- self.logger.capture_stdio()
if self.umask is not None:
os.umask(self.umask)
@@ -276,7 +280,7 @@ def post_start(self):
def do_stop(self):
logger.info("Stopping.")
self.logger.shutdown()
- if self.daemon:
+ if self.pidfile:
self.pidfile.unlink()
def do_reload(self):

No commit comments for this range

Something went wrong with that request. Please try again.