Removes lockfile and custom python-daemon server initialization

in favour of paste.deploy.
This commit is contained in:
jaypipes@gmail.com
2011-02-10 01:16:58 +00:00
committed by Tarmac
20 changed files with 716 additions and 583 deletions

View File

@@ -2,3 +2,8 @@
glance.egg-info
glance.sqlite
*.glance-venv
dist/
ChangeLog
*.pid
*.log
glance/vcsversion.py

View File

@@ -32,8 +32,7 @@ sys.path.append(ROOT_DIR)
from glance import version
from glance.common import config
from glance.common import server
import glance.store
from glance.common import wsgi
def create_options(parser):
@@ -43,52 +42,22 @@ def create_options(parser):
:param parser: The option parser
"""
parser.add_option('-H', '--host',
dest="host", metavar="ADDRESS",
default="0.0.0.0",
help="Address of Glance API server. "
"Default: %default")
parser.add_option('-p', '--port',
dest="port", metavar="PORT", type=int,
default=9292,
help="Port the Glance API server listens on. "
"Default: %default")
parser.add_option('--registry-host',
dest="registry_host", metavar="ADDRESS",
default="0.0.0.0",
help="Address of a Glance Registry server. "
"Default: %default")
parser.add_option('--registry-port',
dest="registry_port", metavar="PORT", type=int,
default=9191,
help="Port a Glance Registry server listens on. "
"Default: %default")
glance.store.add_options(parser)
config.add_common_options(parser)
config.add_daemon_options(parser)
config.add_log_options('glance-api', parser)
def main(_args):
# NOTE(sirp): importing in main so that eventlet is imported AFTER
# daemonization. See https://bugs.launchpad.net/bugs/687661
from glance.common import wsgi
from glance.server import API
server = wsgi.Server()
server.start(API(options), options['port'], host=options['host'])
server.wait()
if __name__ == '__main__':
oparser = optparse.OptionParser(version='%%prog %s'
% version.version_string())
create_options(oparser)
conf_options = config.get_config_file_options()
(options, args) = config.parse_options(oparser, defaults=conf_options)
(options, args) = config.parse_options(oparser)
try:
config.setup_logging(options)
server.serve('glance-api', main, options, args)
conf, app = config.load_paste_app('glance-api', options, args)
server = wsgi.Server()
server.start(app, int(conf['bind_port']), conf['bind_host'])
server.wait()
except RuntimeError, e:
sys.exit("ERROR: %s" % e)

View File

@@ -34,9 +34,7 @@ sys.path.append(ROOT_DIR)
from glance import version
from glance.common import config
from glance.common import server
import glance.registry.db
import glance.store
from glance.common import wsgi
def create_options(parser):
@@ -46,56 +44,24 @@ def create_options(parser):
:param parser: The option parser
"""
parser.add_option('--api-host',
dest="api_host", metavar="ADDRESS",
default="0.0.0.0",
help="Address of Glance API server. "
"Default: %default")
parser.add_option('--api-port',
dest="api_port", metavar="PORT", type=int,
default=9292,
help="Port the Glance API server listens on. "
"Default: %default")
parser.add_option('--registry-host',
dest="registry_host", metavar="ADDRESS",
default="0.0.0.0",
help="Address of a Glance Registry server. "
"Default: %default")
parser.add_option('--registry-port',
dest="registry_port", metavar="PORT", type=int,
default=9191,
help="Port a Glance Registry server listens on. "
"Default: %default")
glance.store.add_options(parser)
glance.registry.db.add_options(parser)
config.add_common_options(parser)
config.add_daemon_options(parser)
config.add_log_options('glance-combined', parser)
def main(_args):
# NOTE(sirp): importing in main so that eventlet is imported AFTER
# daemonization. See https://bugs.launchpad.net/bugs/687661
from glance.common import wsgi
from glance.server import API
from glance.registry.server import API as rAPI
server = wsgi.Server()
server.start(API(options), options['api_port'], host=options['api_host'])
server.start(rAPI(options), options['registry_port'],
host=options['registry_host'])
server.wait()
if __name__ == '__main__':
oparser = optparse.OptionParser(version='%%prog %s'
% version.version_string())
create_options(oparser)
conf_options = config.get_config_file_options()
(options, args) = config.parse_options(oparser, defaults=conf_options)
(options, args) = config.parse_options(oparser)
try:
config.setup_logging(options)
server.serve('glance-combined', main, options, args)
server = wsgi.Server()
conf, app = config.load_paste_app('glance-api', options, args)
server.start(app, int(conf['bind_port']), conf['bind_host'])
conf, app = config.load_paste_app('glance-registry', options, args)
server.start(app, int(conf['bind_port']), conf['bind_host'])
server.wait()
except RuntimeError, e:
sys.exit("ERROR: %s" % e)

216
bin/glance-control Executable file
View File

@@ -0,0 +1,216 @@
#!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 OpenStack, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Helper script for starting/stopping/reloading Glance server programs.
Thanks for some of the code, Swifties ;)
"""
from __future__ import with_statement
import errno
import glob
import os
import optparse
import resource
import signal
import sys
import time
from glance import version
from glance.common import config
ALL_COMMANDS = ['start', 'stop', 'shutdown', 'restart',
'reload', 'force-reload']
ALL_SERVERS = ['glance-api', 'glance-registry']
GRACEFUL_SHUTDOWN_SERVERS = ['glance-api', 'glance-registry']
MAX_DESCRIPTORS = 32768
MAX_MEMORY = (1024 * 1024 * 1024) * 2 # 2 GB
USAGE = """%prog [options] <SERVER> <COMMAND> [CONFPATH]
Where <SERVER> is one of:
all, api, registry
And command is one of:
start, stop, shutdown, restart, reload, force-reload
And CONFPATH is the optional configuration file to use."""
def pid_files(server):
if os.path.exists('/var/run/glance/%s.pid' % server):
pid_files = ['/var/run/glance/%s.pid' % server]
else:
pid_files = glob.glob('/var/run/glance/%s/*.pid' % server)
for pid_file in pid_files:
pid = int(open(pid_file).read().strip())
yield pid_file, pid
def do_start(server, options, args):
server_type = '-'.join(server.split('-')[:-1])
for pid_file, pid in pid_files(server):
if os.path.exists('/proc/%s' % pid):
print "%s appears to already be running: %s" % (server, pid_file)
return
else:
print "Removing stale pid file %s" % pid_file
os.unlink(pid_file)
try:
resource.setrlimit(resource.RLIMIT_NOFILE,
(MAX_DESCRIPTORS, MAX_DESCRIPTORS))
resource.setrlimit(resource.RLIMIT_DATA,
(MAX_MEMORY, MAX_MEMORY))
except ValueError:
print "Unable to increase file descriptor limit. Running as non-root?"
os.environ['PYTHON_EGG_CACHE'] = '/tmp'
def write_pid_file(pid_file, pid):
dir, file = os.path.split(pid_file)
if not os.path.exists(dir):
try:
os.makedirs(dir)
except OSError, err:
if err.errno == errno.EACCES:
sys.exit('Unable to create %s. Running as non-root?'
% dir)
fp = open(pid_file, 'w')
fp.write('%d\n' % pid)
fp.close()
def launch(ini_file, pid_file):
args = [server, ini_file]
print 'Starting %s with %s' % (server, ini_file)
pid = os.fork()
if pid == 0:
os.setsid()
with open(os.devnull, 'r+b') as nullfile:
for desc in (0, 1, 2): # close stdio
try:
os.dup2(nullfile.fileno(), desc)
except OSError:
pass
try:
os.execlp('%s' % server, server, ini_file)
except OSError, e:
sys.exit('unable to launch %s. Got error: %s'
% (server, str(e)))
sys.exit(0)
else:
write_pid_file(pid_file, pid)
pid_file = '/var/run/glance/%s.pid' % server
conf_file = config.find_config_file(options, args)
if not conf_file:
sys.exit("Could not find any configuration file to use!")
launch_args = [(conf_file, pid_file)]
# start all servers
for conf_file, pid_file in launch_args:
launch(conf_file, pid_file)
def do_stop(server, options, args, graceful=False):
if graceful and server in GRACEFUL_SHUTDOWN_SERVERS:
sig = signal.SIGHUP
else:
sig = signal.SIGTERM
did_anything = False
pfiles = pid_files(server)
for pid_file, pid in pfiles:
did_anything = True
try:
print 'Stopping %s pid: %s signal: %s' % (server, pid, sig)
os.kill(pid, sig)
except OSError:
print "Process %d not running" % pid
try:
os.unlink(pid_file)
except OSError:
pass
for pid_file, pid in pfiles:
for _junk in xrange(150): # 15 seconds
if not os.path.exists('/proc/%s' % pid):
break
time.sleep(0.1)
else:
print 'Waited 15 seconds for pid %s (%s) to die; giving up' % \
(pid, pid_file)
if not did_anything:
print 'No %s running' % server
if __name__ == '__main__':
oparser = optparse.OptionParser(usage=USAGE, version='%%prog %s'
% version.version_string())
config.add_common_options(oparser)
(options, args) = config.parse_options(oparser)
if len(args) < 2:
oparser.print_usage()
sys.exit(1)
server = args.pop(0).lower()
if server == 'all':
servers = ALL_SERVERS
else:
if not server.startswith('glance-'):
server = 'glance-%s' % server
if server not in ALL_SERVERS:
server_list = ", ".join([s.replace('glance-', '')
for s in ALL_SERVERS])
msg = ("Unknown server '%(server)s' specified. Please specify "
"all, or one of the servers: %(server_list)s" % locals())
sys.exit(msg)
servers = [server]
command = args.pop(0).lower()
if command not in ALL_COMMANDS:
command_list = ", ".join(ALL_COMMANDS)
msg = ("Unknown command %(command)s specified. Please specify a "
"command in this list: %(command_list)s" % locals())
sys.exit(msg)
if command == 'start':
for server in servers:
do_start(server, options, args)
if command == 'stop':
for server in servers:
do_stop(server, options, args)
if command == 'shutdown':
for server in servers:
do_stop(server, options, args, graceful=True)
if command == 'restart':
for server in servers:
do_stop(server, options, args)
for server in servers:
do_start(server, options, args)
if command == 'reload' or command == 'force-reload':
for server in servers:
do_stop(server, options, args, graceful=True)
do_start(server, options, args)

View File

@@ -30,10 +30,9 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(ROOT_DIR)
import glance.registry.db
from glance import version
from glance.common import config
from glance.common import server
from glance.common import wsgi
def create_options(parser):
@@ -43,42 +42,22 @@ def create_options(parser):
:param parser: The option parser
"""
parser.add_option('-H', '--host',
dest="host", metavar="ADDRESS",
default="0.0.0.0",
help="Address of Glance API server. "
"Default: %default")
parser.add_option('-p', '--port',
dest="port", metavar="PORT", type=int,
default=9191,
help="Port the Glance Registry server listens on. "
"Default: %default")
glance.registry.db.add_options(parser)
config.add_common_options(parser)
config.add_daemon_options(parser)
config.add_log_options('glance-registry', parser)
def main(_args):
# NOTE(sirp): importing in main so that eventlet is imported AFTER
# daemonization. See https://bugs.launchpad.net/bugs/687661
from glance.common import wsgi
from glance.registry.server import API
server = wsgi.Server()
server.start(API(options), int(options['port']), host=options['host'])
server.wait()
if __name__ == '__main__':
oparser = optparse.OptionParser(version='%%prog %s'
% version.version_string())
create_options(oparser)
conf_options = config.get_config_file_options()
(options, args) = config.parse_options(oparser, defaults=conf_options)
(options, args) = config.parse_options(oparser)
try:
config.setup_logging(options)
server.serve('glance-registry', main, options, args)
conf, app = config.load_paste_app('glance-registry', options, args)
server = wsgi.Server()
server.start(app, int(conf['bind_port']), conf['bind_host'])
server.wait()
except RuntimeError, e:
sys.exit("ERROR: %s" % e)

View File

@@ -0,0 +1,20 @@
..
Copyright 2011 OpenStack, LLC
All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Configuring Glance
==================
.. todo:: Complete details of configuration with paste.deploy config files

View File

@@ -0,0 +1,165 @@
..
Copyright 2011 OpenStack, LLC
All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Controlling Glance Servers
==========================
This section describes the ways to start, stop, and reload Glance's server
programs.
Starting a server
-----------------
There are two ways to start a Glance server (either the API server or the
reference implementation registry server that ships with Glance):
* Manually calling the server program
* Using the ``glance-control`` server daemon wrapper program
Manually starting the server
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The first is by directly calling the server program, passing in command-line
options and a single argument for the ``paste.deploy`` configuration file to
use when configuring the server application.
.. note::
Glance ships with an ``etc/`` directory that contains sample ``paste.deploy``
configuration files that you can copy to a standard configuation directory and
adapt for your own uses.
If you do `not` specifiy a configuration file on the command line, Glance will
do its best to locate a ``glance.conf`` configuration file in one of the
following directories, stopping at the first config file it finds:
* .
* ~/.glance
* ~/
* /etc/glance/
* /etc
If no configuration file is found, you will see any error, like so::
$> glance-api
ERROR: Unable to locate any configuration file. Cannot load application glance-api
Here is an example showing how you can manually start the ``glance-api`` server
in a shell.::
$> sudo glance-api etc/glance.conf.sample --debug
2011-02-09 14:58:29 DEBUG [glance-api] ********************************************************************************
2011-02-09 14:58:29 DEBUG [glance-api] Configuration options gathered from config file:
2011-02-09 14:58:29 DEBUG [glance-api] /home/jpipes/repos/glance/trunk/etc/glance.conf.sample
2011-02-09 14:58:29 DEBUG [glance-api] ================================================
2011-02-09 14:58:29 DEBUG [glance-api] bind_host 0.0.0.0
2011-02-09 14:58:29 DEBUG [glance-api] bind_port 9292
2011-02-09 14:58:29 DEBUG [glance-api] debug True
2011-02-09 14:58:29 DEBUG [glance-api] default_store file
2011-02-09 14:58:29 DEBUG [glance-api] filesystem_store_datadir /var/lib/glance/images/
2011-02-09 14:58:29 DEBUG [glance-api] registry_host 0.0.0.0
2011-02-09 14:58:29 DEBUG [glance-api] registry_port 9191
2011-02-09 14:58:29 DEBUG [glance-api] verbose False
2011-02-09 14:58:29 DEBUG [glance-api] ********************************************************************************
2011-02-09 14:58:29 DEBUG [routes.middleware] Initialized with method overriding = True, and path info altering = True
(16333) wsgi starting up on http://0.0.0.0:9292/
Simply supply the configuration file as the first argument
(``etc/glance.conf.sample`` in the above example) and then any common options
you want to use (``--debug`` was used above to show some of the debugging
output that the server shows when starting up. Call the server program
with ``--help`` to see all available options you can specify on the
command line.)
For more information on configuring the server via the ``paste.deploy``
configuration files, see the section entitled
:doc:`Configuring Glance servers <configuring>`
Note that the server does not `daemonize` itself when run manually
from the terminal. You can force the server to daemonize using the standard
shell backgrounding indicator, ``&``. However, for most use cases, we recommend
using the ``glance-control`` server daemon wrapper for daemonizing. See below
for more details on daemonization with ``glance-control``.
Using the ``glance-control`` program to start the server
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The second way to start up a Glance server is to use the ``glance-control``
program. ``glance-control`` is a wrapper script that allows the user to
start, stop, restart, and reload the other Glance server programs in
a fashion that is more conducive to automation and scripting.
Servers started via the ``glance-control`` program are always `daemonized`,
meaning that the server program process runs in the background.
To start a Glance server with ``glance-control``, simply call
``glance-control`` with a server and the word "start", followed by
any command-line options you wish to provide. Start the server with ``glance-control``
in the following way::
$> sudo glance-control <SERVER> start [CONFPATH]
.. note::
You must use the ``sudo`` program to run ``glance-control`` currently, as the
pid files for the server programs are written to /var/run/glance/
Here is an example that shows how to start the ``glance-registry`` server
with the ``glance-control`` wrapper script. ::
$> sudo glance-control registry start etc/glance.conf.sample
Starting glance-registry with /home/jpipes/repos/glance/trunk/etc/glance.conf.sample
The same ``paste.deploy`` configuration files are used by ``glance-control``
to start the Glance server programs, and you can specify (as the example above
shows) a configuration file when starting the server.
.. note::
To start all the Glance servers (currently the glance-api and glance-registry
programs) at once, you can specify "all" for the <SERVER>
Stopping a server
-----------------
If you started a Glance server manually and did not use the ``&`` backgrounding
function, simply send a terminate signal to the server process by typing
``Ctrl-C``
If you started the Glance server using the ``glance-control`` program, you can
use the ``glance-control`` program to stop it. Simply do the following::
$> sudo glance-control <SERVER> stop
as this example shows::
$> sudo glance-control registry stop
Stopping glance-registry pid: 17602 signal: 15
Restarting a server
-------------------
You can restart a server with the ``glance-control`` program, as demonstrated
here::
$> sudo glance-control registry restart etc/glance.conf.sample
Stopping glance-registry pid: 17611 signal: 15
Starting glance-registry with /home/jpipes/repos/glance/trunk/etc/glance.conf.sample

View File

@@ -76,48 +76,6 @@ Glance Registry Servers
Glance registry servers are servers that conform to the Glance Registry API.
Glance ships with a reference implementation of a registry server that
complies with this API (``bin/glance-registry``).
complies with this API (``glance-registry``).
Starting Up Glance's Servers
----------------------------
To get started using Glance, you must first start the Glance API server.
After installing Glance, starting up the Glance API server is easy. Simply
start the ``glance-api`` program, like so::
$> glance-api
Configuring the Glance API server
*********************************
There are a few options that can be supplied to the API server when
starting it up:
* ``verbose``
Show more verbose/debugging output
* ``api_host``
Address of the host the registry runs on. Defaults to 0.0.0.0.
* ``api_port``
Port the registry server listens on. Defaults to 9292.
* ``default_store``
The store that the Glance API server will use by default to store
images that are added to it. The default value is `filesystem`, and
possible choices are: `filesystem`, `swift`, and `s3`.
* ``filesystem_store_datadir``
Directory where the filesystem store can write images to. This directory
must be writeable by the user that runs ``glance-api``
.. todo::
Link to docs on the different stores when the documentation on Glance
stores is complete.
For more details on Glance's architecture see :doc:`here <architecture>`

View File

@@ -58,6 +58,9 @@ Using Glance
:maxdepth: 1
gettingstarted
installing
controllingservers
configuring
glanceapi
client

100
doc/source/installing.rst Normal file
View File

@@ -0,0 +1,100 @@
..
Copyright 2011 OpenStack, LLC
All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Installing Glance
=================
Installing from packages
~~~~~~~~~~~~~~~~~~~~~~~~
To install the latest version of Glance from the Launchpad Bazaar repositories,
following the following instructions.
Debian/Ubuntu
#############
1. Add the Glance PPA to your sources.lst::
$> sudo add-apt-repository ppa:glance-core/trunk
$> sudo apt-get update
2. Install Glance::
$> sudo apt-get install glance
RedHat/Fedora
#############
.. todo:: Need some help on this one...
Mac OSX
#######
.. todo:: No idea how to do install on Mac OSX. Somebody with a Mac should complete this section
Installing from source tarballs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To install the latest version of Glance from the Launchpad Bazaar repositories,
following the following instructions.
1. Grab the source tarball from `Launchpad <http://launchpad.net/glance/+download>`_
2. Untar the source tarball::
$> tar -xzf <FILE>
3. Change into the package directory and build/install::
$> cd glance-<RELEASE>
$> sudo python setup.py install
Installing from a Bazaar Branch
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To install the latest version of Glance from the Launchpad Bazaar repositories,
following the following instructions.
Debian/Ubuntu
#############
1. Install Bazaar and build dependencies::
$> sudo apt-get install bzr python-eventlet python-routes python-greenlet
$> sudo apt-get install python-argparse python-sqlalchemy python-wsgiref python-pastedeploy
.. note::
If you want to build the Glance documentation locally, you will also want
to install the python-sphinx package
1. Branch Glance's trunk branch::
$> bzr branch lp:glance
1. Install Glance::
$> sudo python setup.py install
RedHat/Fedora
#############
.. todo:: Need some help on this one...
Mac OSX
#######
.. todo:: No idea how to do install on Mac OSX. Somebody with a Mac should complete this section

View File

@@ -1,20 +0,0 @@
[DEFAULT]
# Show more verbose log output (sets INFO log level output)
# verbose = True
# Show debugging output in logs (sets DEBUG log level output)
# debug = True
# Which backend store should Glance use by default is not specified
# in a request to add a new image to Glance? Default: 'file'
# Available choices are 'file', 'swift', and 's3'
# default_store = file
# SQLAlchemy connection string for the reference implementation
# registry server. Any valid SQLAlchemy connection string is fine.
# See: http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html#sqlalchemy.create_engine
# sql_connection = sqlite://glance.sqlite
# The directory that the Filesystem backend store will write disk
# images to. Default: /var/lib/glance/images
# filesystem_store_datadir = /var/lib/glance/images

44
etc/glance.conf.sample Normal file
View File

@@ -0,0 +1,44 @@
[DEFAULT]
# Show more verbose log output (sets INFO log level output)
verbose = True
# Show debugging output in logs (sets DEBUG log level output)
debug = False
[app:glance-api]
paste.app_factory = glance.server:app_factory
# Directory that the Filesystem backend store
# writes image data to
filesystem_store_datadir=/var/lib/glance/images/
# Which backend store should Glance use by default is not specified
# in a request to add a new image to Glance? Default: 'file'
# Available choices are 'file', 'swift', and 's3'
default_store = file
# Address to bind the API server
bind_host = 0.0.0.0
# Port the bind the API server to
bind_port = 9292
# Address to find the registry server
registry_host = 0.0.0.0
# Port the registry server is listening on
registry_port = 9191
[app:glance-registry]
paste.app_factory = glance.registry.server:app_factory
# Address to bind the registry server
bind_host = 0.0.0.0
# Port the bind the registry server to
bind_port = 9191
# SQLAlchemy connection string for the reference implementation
# registry server. Any valid SQLAlchemy connection string is fine.
# See: http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html#sqlalchemy.create_engine
sql_connection = sqlite:///glance.sqlite

View File

@@ -29,6 +29,8 @@ import os
import re
import sys
from paste import deploy
import glance.common.exception as exception
DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
@@ -37,7 +39,7 @@ DEFAULT_LOG_HANDLER = 'stream'
LOGGING_HANDLER_CHOICES = ['syslog', 'file', 'stream']
def parse_options(parser, cli_args=None, defaults=None):
def parse_options(parser, cli_args=None):
"""
Returns the parsed CLI options, command to run and its arguments, merged
with any same-named options found in a configuration file.
@@ -52,47 +54,14 @@ def parse_options(parser, cli_args=None, defaults=None):
:param parser: The option parser
:param cli_args: (Optional) Set of arguments to process. If not present,
sys.argv[1:] is used.
:param defaults: (optional) mapping of default values for options
:retval tuple of (options, args)
"""
if defaults:
int_re = re.compile(r'^\d+$')
float_re = re.compile(r'^([+-]?(((\d+(\.)?)|(\d*\.\d+))'
'([eE][+-]?\d+)?))$')
for key, value in defaults.items():
# Do our best to figure out what the actual option
# type is underneath...
if value.lower() in ('true', 'on'):
value = True
elif value.lower() in ('false', 'off'):
value = False
elif int_re.match(value):
value = int(value)
elif float_re.match(value):
value = float(value)
defaults[key] = value
parser.set_defaults(**defaults)
(options, args) = parser.parse_args(cli_args)
return (vars(options), args)
def options_to_conf(options):
"""
Converts a mapping of options having typed values into
a mapping of configuration options having only stringified values.
This method is used to convert the return of parse_options()[0]
into the configuration mapping that is expected by ConfigParser
and paste.deploy.
:params options: Mapping of typed option key/values
"""
return dict([(k, str(v)) for k, v in options.items()])
def add_common_options(parser):
"""
Given a supplied optparse.OptionParser, adds an OptionGroup that
@@ -124,10 +93,16 @@ def add_daemon_options(parser):
"the daemonizing of this program."
group = optparse.OptionGroup(parser, "Daemon Options", help_text)
group.add_option('--daemonize', default=False, action="store_true",
help="Daemonize this process")
group.add_option("--pidfile", default=None,
help="(Optional) Name of pid file for the server")
group.add_option('--config', default=None,
help="Configuration file to read when loading "
"application. If missing, the first argument is "
"used. If no arguments are found, then a set of "
"standard directories are searched for a config "
"file.")
group.add_option("--pid-file", default=None, metavar="PATH",
help="(Optional) Name of pid file for the server. "
"If not specified, the pid file will be named "
"/var/run/glance/<SERVER>.pid.")
group.add_option("--uid", type=int, default=os.getuid(),
help="uid under which to run. Default: %default")
group.add_option("--gid", type=int, default=os.getgid(),
@@ -160,9 +135,6 @@ def add_log_options(prog_name, parser):
choices=LOGGING_HANDLER_CHOICES,
help="What logging handler to use? "
"Default: %default")
group.add_option('--log-format', metavar="FORMAT",
default=DEFAULT_LOG_FORMAT,
help="Format string for log records. Default: %default")
group.add_option('--log-date-format', metavar="FORMAT",
default=DEFAULT_LOG_DATE_FORMAT,
help="Format string for %(asctime)s in log records. "
@@ -203,6 +175,9 @@ def setup_logging(options):
root_logger.setLevel(logging.WARNING)
# Set log configuration from options...
# Note that we use a hard-coded log format in the options
# because of Paste.Deploy bug #379
# http://trac.pythonpaste.org/pythonpaste/ticket/379
log_format = options.get('log_format', DEFAULT_LOG_FORMAT)
log_date_format = options.get('log_date_format', DEFAULT_LOG_DATE_FORMAT)
formatter = logging.Formatter(log_format, log_date_format)
@@ -229,79 +204,92 @@ def setup_logging(options):
raise exception.BadInputError(
"unrecognized log handler '%(log_handler)s'" % locals())
# Log the options used when starting if we're in debug mode...
if debug:
root_logger.debug("*" * 80)
root_logger.debug("Options:")
root_logger.debug("========")
for key, value in sorted(options.items()):
root_logger.debug("%(key)-30s %(value)s" % locals())
root_logger.debug("*" * 80)
def get_config_file_options(conf_file=None, conf_dirs=None, app_name=None):
def find_config_file(options, args):
"""
Look for configuration files in a number of standard directories and
return a mapping of options found in the files.
Return the first config file found.
The files that are searched for are in the following order, with
options found in later files overriding options found in earlier
files::
We search for the paste config file in the following order:
* If --config-file option is used, use that
* If args[0] is a file, use that
* Search for glance.conf in standard directories:
* .
* ~.glance/
* ~
* /etc/glance
* /etc
/etc/glance.cnf
/etc/glance/glance.cnf
~/glance.cnf
~/.glance/glance.cnf
./glance.cnf
supplied conf_file param, if any.
:param conf_file: (optional) config file to read options from. Options
from this config file override all others
:param conf_dirs: (optional) sequence of directory paths to search for
config files. Generally just used in testing
:param app_name: (optional) name of application we're interested in.
Supplying this will ensure that only the [DEFAULT]
section and the [app_name] sections of the config
files will be read. If not supplied (the default), all
sections are read for configuration options.
:retval Mapping of configuration options read from config files
:retval Full path to config file, or None if no config file found
"""
# Note that we do this in reverse priority order because
# later configs overwrite the values of previously-read
# configuration options
fix_path = lambda p: os.path.abspath(os.path.expanduser(p))
if getattr(options, 'config', None):
if os.path.exists(options.config_file):
return fix_path(getattr(options, 'config'))
elif args:
if os.path.exists(args[0]):
return fix_path(args[0])
fixup_path = lambda p: os.path.abspath(os.path.expanduser(p))
config_file_dirs = conf_dirs or \
['/etc',
# Handle standard directory search for glance.conf
config_file_dirs = [fix_path(os.getcwd()),
fix_path(os.path.join('~', '.glance')),
fix_path('~'),
'/etc/glance/',
fixup_path('~'),
fixup_path(os.path.join('~', '.glance')),
fixup_path(os.getcwd())]
'/etc']
config_files = []
results = {}
for cfg_dir in config_file_dirs:
cfg_file = os.path.join(cfg_dir, 'glance.cnf')
cfg_file = os.path.join(cfg_dir, 'glance.conf')
if os.path.exists(cfg_file):
config_files.append(cfg_file)
return cfg_file
if conf_file:
config_files.append(fixup_path(conf_file))
cp = ConfigParser.ConfigParser()
for config_file in config_files:
if not cp.read(config_file):
msg = 'Unable to read config file: %s' % config_file
raise RuntimeError(msg)
def load_paste_app(app_name, options, args):
"""
Builds and returns a WSGI app from a paste config file.
results.update(cp.defaults())
# Add any sections we have in the configuration file, too...
for section in cp.sections():
section_option_keys = cp.options(section)
if not app_name or (app_name == section):
for k in section_option_keys:
results[k] = cp.get(section, k)
We search for the paste config file in the following order:
* If --config-file option is used, use that
* If args[0] is a file, use that
* Search for glance.conf in standard directories:
* .
* ~.glance/
* ~
* /etc/glance
* /etc
return results
:param app_name: Name of the application to load
:param options: Set of typed options returned from parse_options()
:param args: Command line arguments from argv[1:]
:raises RuntimeError when config file cannot be located or application
cannot be loaded from config file
"""
conf_file = find_config_file(options, args)
if not conf_file:
raise RuntimeError("Unable to locate any configuration file. "
"Cannot load application %s" % app_name)
try:
conf = deploy.appconfig("config:%s" % conf_file, name=app_name)
# We only update the conf dict for the verbose and debug
# flags. Everything else must be set up in the conf file...
conf['verbose'] = options['verbose']
conf['debug'] = options['debug']
# Log the options used when starting if we're in debug mode...
if conf['debug']:
logger = logging.getLogger(app_name)
logger.debug("*" * 80)
logger.debug("Configuration options gathered from config file:")
logger.debug(conf_file)
logger.debug("================================================")
items = dict([(k, v) for k, v in conf.items()
if k not in ('__file__', 'here')])
for key, value in sorted(items.items()):
logger.debug("%(key)-30s %(value)s" % locals())
logger.debug("*" * 80)
app = deploy.loadapp("config:%s" % conf_file, name=app_name)
except (LookupError, ImportError), e:
raise RuntimeError("Unable to load %(app_name)s from "
"configuration file %(conf_file)s."
"\nGot: %(e)r" % locals())
return conf, app

View File

@@ -1,129 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Base functionality for nova daemons - gradually being replaced with twistd.py.
"""
import daemon
from daemon import pidlockfile
import logging
import logging.handlers
import os
import pprint
import signal
import sys
import time
def stop(pidfile):
"""
Stop the daemon
"""
# Get the pid from the pidfile
try:
pid = int(open(pidfile, 'r').read().strip())
except IOError:
message = "pidfile %s does not exist. Daemon not running?\n"
sys.stderr.write(message % pidfile)
return
# Try killing the daemon process
try:
print "Killing process from pidfile %s" % pidfile
while 1:
os.kill(pid, signal.SIGTERM)
time.sleep(0.1)
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
if os.path.exists(pidfile):
os.remove(pidfile)
else:
print str(err)
sys.exit(1)
def serve(name, main, options, args):
"""Controller for server"""
pidfile = options['pidfile']
if not pidfile:
options['pidfile'] = '%s.pid' % name
action = 'start'
if len(args):
action = args.pop()
if action == 'stop':
stop(options['pidfile'])
sys.exit()
elif action == 'restart':
stop(options['pidfile'])
elif action == 'start':
pass
else:
print 'usage: %s [options] [start|stop|restart]' % name
sys.exit(1)
daemonize(args, name, main, options)
def daemonize(args, name, main, options):
"""Does the work of daemonizing the process"""
logging.getLogger('amqplib').setLevel(logging.WARN)
pidfile = options['pidfile']
logfile = options['log_file']
logdir = options['log_dir']
daemonize = options['daemonize']
use_syslog = options['log_handler'] == 'syslog'
files_to_keep = []
if daemonize:
logger = logging.getLogger(name)
formatter = logging.Formatter(
name + '(%(name)s): %(levelname)s %(message)s')
if use_syslog and not logfile:
syslog = logging.handlers.SysLogHandler(address='/dev/log')
syslog.setFormatter(formatter)
logger.addHandler(syslog)
files_to_keep.append(syslog.socket)
elif options['log_handler'] == 'file':
if not logfile:
logfile = '%s.log' % name
if logdir:
logfile = os.path.join(logdir, logfile)
logfile = logging.FileHandler(logfile)
logfile.setFormatter(formatter)
logger.addHandler(logfile)
files_to_keep.append(logfile.stream)
stdin, stdout, stderr = None, None, None
else:
stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr
with daemon.DaemonContext(
detach_process=daemonize,
working_directory=options['working_directory'],
pidfile=pidlockfile.TimeoutPIDLockFile(pidfile,
acquire_timeout=1,
threaded=False),
stdin=stdin,
stdout=stdout,
stderr=stderr,
uid=options['uid'],
gid=options['gid'],
files_preserve=files_to_keep):
main(args)

View File

@@ -35,9 +35,6 @@ import webob.dec
import webob.exc
logging.getLogger("routes.middleware").addHandler(logging.StreamHandler())
def run_server(application, port):
"""Run a WSGI server with the given application."""
sock = eventlet.listen(('0.0.0.0', port))
@@ -67,48 +64,7 @@ class Server(object):
eventlet.wsgi.server(socket, application, custom_pool=self.pool)
class Application(object):
# TODO(gundlach): I think we should toss this class, now that it has no
# purpose.
"""Base WSGI application wrapper. Subclasses need to implement __call__."""
def __call__(self, environ, start_response):
r"""Subclasses will probably want to implement __call__ like this:
@webob.dec.wsgify
def __call__(self, req):
# Any of the following objects work as responses:
# Option 1: simple string
res = 'message\n'
# Option 2: a nicely formatted HTTP exception page
res = exc.HTTPForbidden(detail='Nice try')
# Option 3: a webob Response object (in case you need to play with
# headers, or you want to be treated like an iterable, or or or)
res = Response();
res.app_iter = open('somefile')
# Option 4: any wsgi app to be run next
res = self.application
# Option 5: you can get a Response object for a wsgi app, too, to
# play with headers etc
res = req.get_response(self.application)
# You can then just return your response...
return res
# ... or set req.response and return None.
req.response = res
See the end of http://pythonpaste.org/webob/modules/dec.html
for more info.
"""
raise NotImplementedError("You must implement __call__")
class Middleware(Application):
class Middleware(object):
"""
Base WSGI middleware wrapper. These classes require an application to be
initialized that will be called next. By default the middleware will
@@ -116,18 +72,38 @@ class Middleware(Application):
behavior.
"""
def __init__(self, application): # pylint: disable-msg=W0231
def __init__(self, application):
self.application = application
def process_request(self, req):
"""
Called on each request.
If this returns None, the next application down the stack will be
executed. If it returns a response then that response will be returned
and execution will stop here.
"""
return None
def process_response(self, response):
"""Do whatever you'd like to the response."""
return response
@webob.dec.wsgify
def __call__(self, req): # pylint: disable-msg=W0221
"""Override to implement middleware behavior."""
return self.application
def __call__(self, req):
response = self.process_request(req)
if response:
return response
response = req.get_response(self.application)
return self.process_response(response)
class Debug(Middleware):
"""Helper class that can be inserted into any WSGI application chain
to get information about the request and response."""
"""
Helper class that can be inserted into any WSGI application chain
to get information about the request and response.
"""
@webob.dec.wsgify
def __call__(self, req):

View File

@@ -175,3 +175,13 @@ def make_image_dict(image):
image_dict['properties'] = properties
return image_dict
def app_factory(global_conf, **local_conf):
"""
paste.deploy app factory for creating Glance reference implementation
registry server apps
"""
conf = global_conf.copy()
conf.update(local_conf)
return API(conf)

View File

@@ -439,3 +439,10 @@ class API(wsgi.Router):
mapper.connect("/images/{id}", controller=controller, action="meta",
conditions=dict(method=["HEAD"]))
super(API, self).__init__(mapper)
def app_factory(global_conf, **local_conf):
"""paste.deploy app factory for creating Glance API server apps"""
conf = global_conf.copy()
conf.update(local_conf)
return API(conf)

View File

@@ -87,6 +87,7 @@ setup(
],
scripts=['bin/glance-api',
'bin/glance-combined',
'bin/glance-control',
'bin/glance-manage',
'bin/glance-registry',
'bin/glance-upload'])

View File

@@ -62,128 +62,4 @@ class TestConfig(unittest.TestCase):
parser = optparse.OptionParser()
config.add_common_options(parser)
self.assertRaises(SystemExit, config.parse_options,
parser,['--unknown'])
def test_options_to_conf(self):
parser = optparse.OptionParser()
config.add_common_options(parser)
parsed_options, args = config.parse_options(parser)
conf_options = config.options_to_conf(parsed_options)
expected_options = {'verbose': 'False', 'debug': 'False'}
self.assertEquals(expected_options, conf_options)
def test_get_config_file_options(self):
# Test when no conf files are found...
expected_options = {}
conf_options = config.get_config_file_options(conf_dirs=['tests'])
self.assertEquals(expected_options, conf_options)
# Test when a conf file is supplied and only DEFAULT
# section is present
with tempfile.NamedTemporaryFile() as f:
contents = """[DEFAULT]
verbose = True
"""
f.write(contents)
f.flush()
conf_file = f.name
expected_options = {'verbose': 'True'}
conf_options = config.get_config_file_options(conf_file)
self.assertEquals(expected_options, conf_options)
# Test when a conf file is supplied and it has a DEFAULT
# section and another section called glance-api, with
# no specified app_name when calling get_config_file_options()
with tempfile.NamedTemporaryFile() as f:
contents = """[DEFAULT]
verbose = True
[glance-api]
default_store = swift
"""
f.write(contents)
f.flush()
conf_file = f.name
expected_options = {'verbose': 'True',
'default_store': 'swift'}
conf_options = config.get_config_file_options(conf_file)
self.assertEquals(expected_options, conf_options)
# Test when a conf file is supplied and it has a DEFAULT
# section and another section called glance-api, with
# specified app_name is NOT glance-api
with tempfile.NamedTemporaryFile() as f:
contents = """[DEFAULT]
verbose = True
[glance-api]
default_store = swift
"""
f.write(contents)
f.flush()
conf_file = f.name
expected_options = {'verbose': 'True'}
app_name = 'glance-registry'
conf_options = config.get_config_file_options(conf_file,
app_name=app_name)
self.assertEquals(expected_options, conf_options)
# Test when a conf file is supplied and it has a DEFAULT
# section and two other sections. Check that the later section
# overrides the value of the former section...
with tempfile.NamedTemporaryFile() as f:
contents = """[DEFAULT]
verbose = True
[glance-api]
default_store = swift
[glance-combined]
default_store = s3
"""
f.write(contents)
f.flush()
conf_file = f.name
expected_options = {'verbose': 'True',
'default_store': 's3'}
conf_options = config.get_config_file_options(conf_file)
self.assertEquals(expected_options, conf_options)
def test_parse_options_with_defaults(self):
# Test the integration of parse_options() with a set
# of defaults. These defaults generally come from a
# configuration file
defaults = {'verbose': 'on'}
parser = optparse.OptionParser()
config.add_common_options(parser)
parsed_options, args = config.parse_options(parser, defaults=defaults)
expected_options = {'verbose': True, 'debug': False}
self.assertEquals(expected_options, parsed_options)
# Write a sample conf file and merge the conf file defaults
# with the parsed options.
with tempfile.NamedTemporaryFile() as f:
contents = """[DEFAULT]
verbose = True
debug = off
"""
f.write(contents)
f.flush()
conf_file = f.name
expected_options = {'verbose': True,
'debug': False}
conf_options = config.get_config_file_options(conf_file)
parser = optparse.OptionParser()
config.add_common_options(parser)
parsed_options, args = config.parse_options(parser,
defaults=conf_options)
self.assertEquals(expected_options, parsed_options)
parser, ['--unknown'])

View File

@@ -4,8 +4,7 @@ pep8==0.5.0
pylint==0.19
anyjson
eventlet>=0.9.12
lockfile==0.8
python-daemon==1.5.5
PasteDeploy
routes
webob
wsgiref