merged to trunk rev781
This commit is contained in:
49
bin/nova-api
49
bin/nova-api
@@ -36,51 +36,15 @@ gettext.install('nova', unicode=1)
|
|||||||
|
|
||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import log as logging
|
from nova import log as logging
|
||||||
|
from nova import service
|
||||||
from nova import utils
|
from nova import utils
|
||||||
from nova import version
|
from nova import version
|
||||||
from nova import wsgi
|
from nova import wsgi
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger('nova.api')
|
LOG = logging.getLogger('nova.api')
|
||||||
|
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
flags.DEFINE_string('paste_config', "api-paste.ini",
|
|
||||||
'File name for the paste.deploy config for nova-api')
|
|
||||||
flags.DEFINE_string('ec2_listen', "0.0.0.0",
|
|
||||||
'IP address for EC2 API to listen')
|
|
||||||
flags.DEFINE_integer('ec2_listen_port', 8773, 'port for ec2 api to listen')
|
|
||||||
flags.DEFINE_string('osapi_listen', "0.0.0.0",
|
|
||||||
'IP address for OpenStack API to listen')
|
|
||||||
flags.DEFINE_integer('osapi_listen_port', 8774, 'port for os api to listen')
|
|
||||||
flags.DEFINE_flag(flags.HelpFlag())
|
|
||||||
flags.DEFINE_flag(flags.HelpshortFlag())
|
|
||||||
flags.DEFINE_flag(flags.HelpXMLFlag())
|
|
||||||
|
|
||||||
API_ENDPOINTS = ['ec2', 'osapi']
|
|
||||||
|
|
||||||
|
|
||||||
def run_app(paste_config_file):
|
|
||||||
LOG.debug(_("Using paste.deploy config at: %s"), paste_config_file)
|
|
||||||
apps = []
|
|
||||||
for api in API_ENDPOINTS:
|
|
||||||
config = wsgi.load_paste_configuration(paste_config_file, api)
|
|
||||||
if config is None:
|
|
||||||
LOG.debug(_("No paste configuration for app: %s"), api)
|
|
||||||
continue
|
|
||||||
LOG.debug(_("App Config: %(api)s\n%(config)r") % locals())
|
|
||||||
LOG.info(_("Running %s API"), api)
|
|
||||||
app = wsgi.load_paste_app(paste_config_file, api)
|
|
||||||
apps.append((app, getattr(FLAGS, "%s_listen_port" % api),
|
|
||||||
getattr(FLAGS, "%s_listen" % api)))
|
|
||||||
if len(apps) == 0:
|
|
||||||
LOG.error(_("No known API applications configured in %s."),
|
|
||||||
paste_config_file)
|
|
||||||
return
|
|
||||||
|
|
||||||
server = wsgi.Server()
|
|
||||||
for app in apps:
|
|
||||||
server.start(*app)
|
|
||||||
server.wait()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
utils.default_flagfile()
|
utils.default_flagfile()
|
||||||
@@ -92,9 +56,6 @@ if __name__ == '__main__':
|
|||||||
for flag in FLAGS:
|
for flag in FLAGS:
|
||||||
flag_get = FLAGS.get(flag, None)
|
flag_get = FLAGS.get(flag, None)
|
||||||
LOG.debug("%(flag)s : %(flag_get)s" % locals())
|
LOG.debug("%(flag)s : %(flag_get)s" % locals())
|
||||||
conf = wsgi.paste_config_file(FLAGS.paste_config)
|
|
||||||
if conf:
|
service = service.serve_wsgi(service.ApiService)
|
||||||
run_app(conf)
|
service.wait()
|
||||||
else:
|
|
||||||
LOG.error(_("No paste configuration found for: %s"),
|
|
||||||
FLAGS.paste_config)
|
|
||||||
|
|||||||
166
bin/nova-manage
166
bin/nova-manage
@@ -55,6 +55,8 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import gettext
|
import gettext
|
||||||
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -81,7 +83,7 @@ from nova import log as logging
|
|||||||
from nova import quota
|
from nova import quota
|
||||||
from nova import rpc
|
from nova import rpc
|
||||||
from nova import utils
|
from nova import utils
|
||||||
from nova.api.ec2.cloud import ec2_id_to_id
|
from nova.api.ec2 import ec2utils
|
||||||
from nova.auth import manager
|
from nova.auth import manager
|
||||||
from nova.cloudpipe import pipelib
|
from nova.cloudpipe import pipelib
|
||||||
from nova.compute import instance_types
|
from nova.compute import instance_types
|
||||||
@@ -94,6 +96,7 @@ flags.DECLARE('network_size', 'nova.network.manager')
|
|||||||
flags.DECLARE('vlan_start', 'nova.network.manager')
|
flags.DECLARE('vlan_start', 'nova.network.manager')
|
||||||
flags.DECLARE('vpn_start', 'nova.network.manager')
|
flags.DECLARE('vpn_start', 'nova.network.manager')
|
||||||
flags.DECLARE('fixed_range_v6', 'nova.network.manager')
|
flags.DECLARE('fixed_range_v6', 'nova.network.manager')
|
||||||
|
flags.DECLARE('images_path', 'nova.image.local')
|
||||||
flags.DEFINE_flag(flags.HelpFlag())
|
flags.DEFINE_flag(flags.HelpFlag())
|
||||||
flags.DEFINE_flag(flags.HelpshortFlag())
|
flags.DEFINE_flag(flags.HelpshortFlag())
|
||||||
flags.DEFINE_flag(flags.HelpXMLFlag())
|
flags.DEFINE_flag(flags.HelpXMLFlag())
|
||||||
@@ -104,7 +107,7 @@ def param2id(object_id):
|
|||||||
args: [object_id], e.g. 'vol-0000000a' or 'volume-0000000a' or '10'
|
args: [object_id], e.g. 'vol-0000000a' or 'volume-0000000a' or '10'
|
||||||
"""
|
"""
|
||||||
if '-' in object_id:
|
if '-' in object_id:
|
||||||
return ec2_id_to_id(object_id)
|
return ec2utils.ec2_id_to_id(object_id)
|
||||||
else:
|
else:
|
||||||
return int(object_id)
|
return int(object_id)
|
||||||
|
|
||||||
@@ -545,6 +548,15 @@ class NetworkCommands(object):
|
|||||||
network.dhcp_start,
|
network.dhcp_start,
|
||||||
network.dns)
|
network.dns)
|
||||||
|
|
||||||
|
def delete(self, fixed_range):
|
||||||
|
"""Deletes a network"""
|
||||||
|
network = db.network_get_by_cidr(context.get_admin_context(), \
|
||||||
|
fixed_range)
|
||||||
|
if network.project_id is not None:
|
||||||
|
raise ValueError(_('Network must be disassociated from project %s'
|
||||||
|
' before delete' % network.project_id))
|
||||||
|
db.network_delete_safe(context.get_admin_context(), network.id)
|
||||||
|
|
||||||
|
|
||||||
class VmCommands(object):
|
class VmCommands(object):
|
||||||
"""Class for mangaging VM instances."""
|
"""Class for mangaging VM instances."""
|
||||||
@@ -822,6 +834,155 @@ class InstanceTypeCommands(object):
|
|||||||
self._print_instance_types(name, inst_types)
|
self._print_instance_types(name, inst_types)
|
||||||
|
|
||||||
|
|
||||||
|
class ImageCommands(object):
|
||||||
|
"""Methods for dealing with a cloud in an odd state"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.image_service = utils.import_object(FLAGS.image_service)
|
||||||
|
|
||||||
|
def _register(self, image_type, disk_format, container_format,
|
||||||
|
path, owner, name=None, is_public='T',
|
||||||
|
architecture='x86_64', kernel_id=None, ramdisk_id=None):
|
||||||
|
meta = {'is_public': True,
|
||||||
|
'name': name,
|
||||||
|
'disk_format': disk_format,
|
||||||
|
'container_format': container_format,
|
||||||
|
'properties': {'image_state': 'available',
|
||||||
|
'owner': owner,
|
||||||
|
'type': image_type,
|
||||||
|
'architecture': architecture,
|
||||||
|
'image_location': 'local',
|
||||||
|
'is_public': (is_public == 'T')}}
|
||||||
|
print image_type, meta
|
||||||
|
if kernel_id:
|
||||||
|
meta['properties']['kernel_id'] = int(kernel_id)
|
||||||
|
if ramdisk_id:
|
||||||
|
meta['properties']['ramdisk_id'] = int(ramdisk_id)
|
||||||
|
elevated = context.get_admin_context()
|
||||||
|
try:
|
||||||
|
with open(path) as ifile:
|
||||||
|
image = self.image_service.create(elevated, meta, ifile)
|
||||||
|
new = image['id']
|
||||||
|
print _("Image registered to %(new)s (%(new)08x).") % locals()
|
||||||
|
return new
|
||||||
|
except Exception as exc:
|
||||||
|
print _("Failed to register %(path)s: %(exc)s") % locals()
|
||||||
|
|
||||||
|
def all_register(self, image, kernel, ramdisk, owner, name=None,
|
||||||
|
is_public='T', architecture='x86_64'):
|
||||||
|
"""Uploads an image, kernel, and ramdisk into the image_service
|
||||||
|
arguments: image kernel ramdisk owner [name] [is_public='T']
|
||||||
|
[architecture='x86_64']"""
|
||||||
|
kernel_id = self.kernel_register(kernel, owner, None,
|
||||||
|
is_public, architecture)
|
||||||
|
ramdisk_id = self.ramdisk_register(ramdisk, owner, None,
|
||||||
|
is_public, architecture)
|
||||||
|
self.image_register(image, owner, name, is_public,
|
||||||
|
architecture, kernel_id, ramdisk_id)
|
||||||
|
|
||||||
|
def image_register(self, path, owner, name=None, is_public='T',
|
||||||
|
architecture='x86_64', kernel_id=None, ramdisk_id=None,
|
||||||
|
disk_format='ami', container_format='ami'):
|
||||||
|
"""Uploads an image into the image_service
|
||||||
|
arguments: path owner [name] [is_public='T'] [architecture='x86_64']
|
||||||
|
[kernel_id=None] [ramdisk_id=None]
|
||||||
|
[disk_format='ami'] [container_format='ami']"""
|
||||||
|
return self._register('machine', disk_format, container_format, path,
|
||||||
|
owner, name, is_public, architecture,
|
||||||
|
kernel_id, ramdisk_id)
|
||||||
|
|
||||||
|
def kernel_register(self, path, owner, name=None, is_public='T',
|
||||||
|
architecture='x86_64'):
|
||||||
|
"""Uploads a kernel into the image_service
|
||||||
|
arguments: path owner [name] [is_public='T'] [architecture='x86_64']
|
||||||
|
"""
|
||||||
|
return self._register('kernel', 'aki', 'aki', path, owner, name,
|
||||||
|
is_public, architecture)
|
||||||
|
|
||||||
|
def ramdisk_register(self, path, owner, name=None, is_public='T',
|
||||||
|
architecture='x86_64'):
|
||||||
|
"""Uploads a ramdisk into the image_service
|
||||||
|
arguments: path owner [name] [is_public='T'] [architecture='x86_64']
|
||||||
|
"""
|
||||||
|
return self._register('ramdisk', 'ari', 'ari', path, owner, name,
|
||||||
|
is_public, architecture)
|
||||||
|
|
||||||
|
def _lookup(self, old_image_id):
|
||||||
|
try:
|
||||||
|
internal_id = ec2utils.ec2_id_to_id(old_image_id)
|
||||||
|
image = self.image_service.show(context, internal_id)
|
||||||
|
except exception.NotFound:
|
||||||
|
image = self.image_service.show_by_name(context, old_image_id)
|
||||||
|
return image['id']
|
||||||
|
|
||||||
|
def _old_to_new(self, old):
|
||||||
|
mapping = {'machine': 'ami',
|
||||||
|
'kernel': 'aki',
|
||||||
|
'ramdisk': 'ari'}
|
||||||
|
container_format = mapping[old['type']]
|
||||||
|
disk_format = container_format
|
||||||
|
new = {'disk_format': disk_format,
|
||||||
|
'container_format': container_format,
|
||||||
|
'is_public': True,
|
||||||
|
'name': old['imageId'],
|
||||||
|
'properties': {'image_state': old['imageState'],
|
||||||
|
'owner': old['imageOwnerId'],
|
||||||
|
'architecture': old['architecture'],
|
||||||
|
'type': old['type'],
|
||||||
|
'image_location': old['imageLocation'],
|
||||||
|
'is_public': old['isPublic']}}
|
||||||
|
if old.get('kernelId'):
|
||||||
|
new['properties']['kernel_id'] = self._lookup(old['kernelId'])
|
||||||
|
if old.get('ramdiskId'):
|
||||||
|
new['properties']['ramdisk_id'] = self._lookup(old['ramdiskId'])
|
||||||
|
return new
|
||||||
|
|
||||||
|
def _convert_images(self, images):
|
||||||
|
elevated = context.get_admin_context()
|
||||||
|
for image_path, image_metadata in images.iteritems():
|
||||||
|
meta = self._old_to_new(image_metadata)
|
||||||
|
old = meta['name']
|
||||||
|
try:
|
||||||
|
with open(image_path) as ifile:
|
||||||
|
image = self.image_service.create(elevated, meta, ifile)
|
||||||
|
new = image['id']
|
||||||
|
print _("Image %(old)s converted to " \
|
||||||
|
"%(new)s (%(new)08x).") % locals()
|
||||||
|
except Exception as exc:
|
||||||
|
print _("Failed to convert %(old)s: %(exc)s") % locals()
|
||||||
|
|
||||||
|
def convert(self, directory):
|
||||||
|
"""Uploads old objectstore images in directory to new service
|
||||||
|
arguments: directory"""
|
||||||
|
machine_images = {}
|
||||||
|
other_images = {}
|
||||||
|
directory = os.path.abspath(directory)
|
||||||
|
# NOTE(vish): If we're importing from the images path dir, attempt
|
||||||
|
# to move the files out of the way before importing
|
||||||
|
# so we aren't writing to the same directory. This
|
||||||
|
# may fail if the dir was a mointpoint.
|
||||||
|
if (FLAGS.image_service == 'nova.image.local.LocalImageService'
|
||||||
|
and directory == os.path.abspath(FLAGS.images_path)):
|
||||||
|
new_dir = "%s_bak" % directory
|
||||||
|
os.move(directory, new_dir)
|
||||||
|
os.mkdir(directory)
|
||||||
|
directory = new_dir
|
||||||
|
for fn in glob.glob("%s/*/info.json" % directory):
|
||||||
|
try:
|
||||||
|
image_path = os.path.join(fn.rpartition('/')[0], 'image')
|
||||||
|
with open(fn) as metadata_file:
|
||||||
|
image_metadata = json.load(metadata_file)
|
||||||
|
if image_metadata['type'] == 'machine':
|
||||||
|
machine_images[image_path] = image_metadata
|
||||||
|
else:
|
||||||
|
other_images[image_path] = image_metadata
|
||||||
|
except Exception as exc:
|
||||||
|
print _("Failed to load %(fn)s.") % locals()
|
||||||
|
# NOTE(vish): do kernels and ramdisks first so images
|
||||||
|
self._convert_images(other_images)
|
||||||
|
self._convert_images(machine_images)
|
||||||
|
|
||||||
|
|
||||||
CATEGORIES = [
|
CATEGORIES = [
|
||||||
('user', UserCommands),
|
('user', UserCommands),
|
||||||
('project', ProjectCommands),
|
('project', ProjectCommands),
|
||||||
@@ -837,6 +998,7 @@ CATEGORIES = [
|
|||||||
('db', DbCommands),
|
('db', DbCommands),
|
||||||
('volume', VolumeCommands),
|
('volume', VolumeCommands),
|
||||||
('instance_type', InstanceTypeCommands),
|
('instance_type', InstanceTypeCommands),
|
||||||
|
('image', ImageCommands),
|
||||||
('flavor', InstanceTypeCommands)]
|
('flavor', InstanceTypeCommands)]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ from nova import utils
|
|||||||
def setup(app):
|
def setup(app):
|
||||||
rootdir = os.path.abspath(app.srcdir + '/..')
|
rootdir = os.path.abspath(app.srcdir + '/..')
|
||||||
print "**Autodocumenting from %s" % rootdir
|
print "**Autodocumenting from %s" % rootdir
|
||||||
rv = utils.execute('cd %s && ./generate_autodoc_index.sh' % rootdir)
|
os.chdir(rootdir)
|
||||||
|
rv = utils.execute('./generate_autodoc_index.sh')
|
||||||
print rv[0]
|
print rv[0]
|
||||||
|
|||||||
@@ -173,7 +173,10 @@ Nova Floating IPs
|
|||||||
``nova-manage floating create <host> <ip_range>``
|
``nova-manage floating create <host> <ip_range>``
|
||||||
|
|
||||||
Creates floating IP addresses for the named host by the given range.
|
Creates floating IP addresses for the named host by the given range.
|
||||||
floating delete <ip_range> Deletes floating IP addresses in the range given.
|
|
||||||
|
``nova-manage floating delete <ip_range>``
|
||||||
|
|
||||||
|
Deletes floating IP addresses in the range given.
|
||||||
|
|
||||||
``nova-manage floating list``
|
``nova-manage floating list``
|
||||||
|
|
||||||
@@ -209,12 +212,33 @@ Nova Flavor
|
|||||||
|
|
||||||
Purges the flavor with the name <name>. This removes this flavor from the database.
|
Purges the flavor with the name <name>. This removes this flavor from the database.
|
||||||
|
|
||||||
|
|
||||||
Nova Instance_type
|
Nova Instance_type
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The instance_type command is provided as an alias for the flavor command. All the same subcommands and arguments from nova-manage flavor can be used.
|
The instance_type command is provided as an alias for the flavor command. All the same subcommands and arguments from nova-manage flavor can be used.
|
||||||
|
|
||||||
|
Nova Images
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
``nova-manage image image_register <path> <owner>``
|
||||||
|
|
||||||
|
Registers an image with the image service.
|
||||||
|
|
||||||
|
``nova-manage image kernel_register <path> <owner>``
|
||||||
|
|
||||||
|
Registers a kernel with the image service.
|
||||||
|
|
||||||
|
``nova-manage image ramdisk_register <path> <owner>``
|
||||||
|
|
||||||
|
Registers a ramdisk with the image service.
|
||||||
|
|
||||||
|
``nova-manage image all_register <image_path> <kernel_path> <ramdisk_path> <owner>``
|
||||||
|
|
||||||
|
Registers an image kernel and ramdisk with the image service.
|
||||||
|
|
||||||
|
``nova-manage image convert <directory>``
|
||||||
|
|
||||||
|
Converts all images in directory from the old (Bexar) format to the new format.
|
||||||
|
|
||||||
FILES
|
FILES
|
||||||
========
|
========
|
||||||
|
|||||||
@@ -182,6 +182,29 @@ Nova Floating IPs
|
|||||||
|
|
||||||
Displays a list of all floating IP addresses.
|
Displays a list of all floating IP addresses.
|
||||||
|
|
||||||
|
Nova Images
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
``nova-manage image image_register <path> <owner>``
|
||||||
|
|
||||||
|
Registers an image with the image service.
|
||||||
|
|
||||||
|
``nova-manage image kernel_register <path> <owner>``
|
||||||
|
|
||||||
|
Registers a kernel with the image service.
|
||||||
|
|
||||||
|
``nova-manage image ramdisk_register <path> <owner>``
|
||||||
|
|
||||||
|
Registers a ramdisk with the image service.
|
||||||
|
|
||||||
|
``nova-manage image all_register <image_path> <kernel_path> <ramdisk_path> <owner>``
|
||||||
|
|
||||||
|
Registers an image kernel and ramdisk with the image service.
|
||||||
|
|
||||||
|
``nova-manage image convert <directory>``
|
||||||
|
|
||||||
|
Converts all images in directory from the old (Bexar) format to the new format.
|
||||||
|
|
||||||
Concept: Flags
|
Concept: Flags
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,10 @@ class InvalidInputException(Error):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidContentType(Error):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TimeoutException(Error):
|
class TimeoutException(Error):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -321,6 +321,8 @@ DEFINE_integer('auth_token_ttl', 3600, 'Seconds for auth tokens to linger')
|
|||||||
|
|
||||||
DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),
|
DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),
|
||||||
"Top-level directory for maintaining nova's state")
|
"Top-level directory for maintaining nova's state")
|
||||||
|
DEFINE_string('lock_path', os.path.join(os.path.dirname(__file__), '../'),
|
||||||
|
"Directory for lock files")
|
||||||
DEFINE_string('logdir', None, 'output to a per-service log file in named '
|
DEFINE_string('logdir', None, 'output to a per-service log file in named '
|
||||||
'directory')
|
'directory')
|
||||||
|
|
||||||
@@ -346,7 +348,7 @@ DEFINE_string('scheduler_manager', 'nova.scheduler.manager.SchedulerManager',
|
|||||||
'Manager for scheduler')
|
'Manager for scheduler')
|
||||||
|
|
||||||
# The service to use for image search and retrieval
|
# The service to use for image search and retrieval
|
||||||
DEFINE_string('image_service', 'nova.image.s3.S3ImageService',
|
DEFINE_string('image_service', 'nova.image.local.LocalImageService',
|
||||||
'The service to use for retrieving and searching for images.')
|
'The service to use for retrieving and searching for images.')
|
||||||
|
|
||||||
DEFINE_string('host', socket.gethostname(),
|
DEFINE_string('host', socket.gethostname(),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
# Copyright 2010 United States Government as represented by the
|
# Copyright 2010 United States Government as represented by the
|
||||||
# Administrator of the National Aeronautics and Space Administration.
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# Copyright 2011 Justin Santa Barbara
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
@@ -39,6 +40,7 @@ from nova import flags
|
|||||||
from nova import rpc
|
from nova import rpc
|
||||||
from nova import utils
|
from nova import utils
|
||||||
from nova import version
|
from nova import version
|
||||||
|
from nova import wsgi
|
||||||
|
|
||||||
|
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
@@ -48,6 +50,14 @@ flags.DEFINE_integer('report_interval', 10,
|
|||||||
flags.DEFINE_integer('periodic_interval', 60,
|
flags.DEFINE_integer('periodic_interval', 60,
|
||||||
'seconds between running periodic tasks',
|
'seconds between running periodic tasks',
|
||||||
lower_bound=1)
|
lower_bound=1)
|
||||||
|
flags.DEFINE_string('ec2_listen', "0.0.0.0",
|
||||||
|
'IP address for EC2 API to listen')
|
||||||
|
flags.DEFINE_integer('ec2_listen_port', 8773, 'port for ec2 api to listen')
|
||||||
|
flags.DEFINE_string('osapi_listen', "0.0.0.0",
|
||||||
|
'IP address for OpenStack API to listen')
|
||||||
|
flags.DEFINE_integer('osapi_listen_port', 8774, 'port for os api to listen')
|
||||||
|
flags.DEFINE_string('api_paste_config', "api-paste.ini",
|
||||||
|
'File name for the paste.deploy config for nova-api')
|
||||||
|
|
||||||
|
|
||||||
class Service(object):
|
class Service(object):
|
||||||
@@ -213,6 +223,41 @@ class Service(object):
|
|||||||
logging.exception(_("model server went away"))
|
logging.exception(_("model server went away"))
|
||||||
|
|
||||||
|
|
||||||
|
class WsgiService(object):
|
||||||
|
"""Base class for WSGI based services.
|
||||||
|
|
||||||
|
For each api you define, you must also define these flags:
|
||||||
|
:<api>_listen: The address on which to listen
|
||||||
|
:<api>_listen_port: The port on which to listen
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, conf, apis):
|
||||||
|
self.conf = conf
|
||||||
|
self.apis = apis
|
||||||
|
self.wsgi_app = None
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.wsgi_app = _run_wsgi(self.conf, self.apis)
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
self.wsgi_app.wait()
|
||||||
|
|
||||||
|
|
||||||
|
class ApiService(WsgiService):
|
||||||
|
"""Class for our nova-api service"""
|
||||||
|
@classmethod
|
||||||
|
def create(cls, conf=None):
|
||||||
|
if not conf:
|
||||||
|
conf = wsgi.paste_config_file(FLAGS.api_paste_config)
|
||||||
|
if not conf:
|
||||||
|
message = (_("No paste configuration found for: %s"),
|
||||||
|
FLAGS.api_paste_config)
|
||||||
|
raise exception.Error(message)
|
||||||
|
api_endpoints = ['ec2', 'osapi']
|
||||||
|
service = cls(conf, api_endpoints)
|
||||||
|
return service
|
||||||
|
|
||||||
|
|
||||||
def serve(*services):
|
def serve(*services):
|
||||||
try:
|
try:
|
||||||
if not services:
|
if not services:
|
||||||
@@ -242,3 +287,46 @@ def serve(*services):
|
|||||||
def wait():
|
def wait():
|
||||||
while True:
|
while True:
|
||||||
greenthread.sleep(5)
|
greenthread.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
def serve_wsgi(cls, conf=None):
|
||||||
|
try:
|
||||||
|
service = cls.create(conf)
|
||||||
|
except Exception:
|
||||||
|
logging.exception('in WsgiService.create()')
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
# After we've loaded up all our dynamic bits, check
|
||||||
|
# whether we should print help
|
||||||
|
flags.DEFINE_flag(flags.HelpFlag())
|
||||||
|
flags.DEFINE_flag(flags.HelpshortFlag())
|
||||||
|
flags.DEFINE_flag(flags.HelpXMLFlag())
|
||||||
|
FLAGS.ParseNewFlags()
|
||||||
|
|
||||||
|
service.start()
|
||||||
|
|
||||||
|
return service
|
||||||
|
|
||||||
|
|
||||||
|
def _run_wsgi(paste_config_file, apis):
|
||||||
|
logging.debug(_("Using paste.deploy config at: %s"), paste_config_file)
|
||||||
|
apps = []
|
||||||
|
for api in apis:
|
||||||
|
config = wsgi.load_paste_configuration(paste_config_file, api)
|
||||||
|
if config is None:
|
||||||
|
logging.debug(_("No paste configuration for app: %s"), api)
|
||||||
|
continue
|
||||||
|
logging.debug(_("App Config: %(api)s\n%(config)r") % locals())
|
||||||
|
logging.info(_("Running %s API"), api)
|
||||||
|
app = wsgi.load_paste_app(paste_config_file, api)
|
||||||
|
apps.append((app, getattr(FLAGS, "%s_listen_port" % api),
|
||||||
|
getattr(FLAGS, "%s_listen" % api)))
|
||||||
|
if len(apps) == 0:
|
||||||
|
logging.error(_("No known API applications configured in %s."),
|
||||||
|
paste_config_file)
|
||||||
|
return
|
||||||
|
|
||||||
|
server = wsgi.Server()
|
||||||
|
for app in apps:
|
||||||
|
server.start(*app)
|
||||||
|
return server
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ flags.DECLARE('fake_network', 'nova.network.manager')
|
|||||||
FLAGS.network_size = 8
|
FLAGS.network_size = 8
|
||||||
FLAGS.num_networks = 2
|
FLAGS.num_networks = 2
|
||||||
FLAGS.fake_network = True
|
FLAGS.fake_network = True
|
||||||
|
FLAGS.image_service = 'nova.image.local.LocalImageService'
|
||||||
flags.DECLARE('num_shelves', 'nova.volume.driver')
|
flags.DECLARE('num_shelves', 'nova.volume.driver')
|
||||||
flags.DECLARE('blades_per_shelf', 'nova.volume.driver')
|
flags.DECLARE('blades_per_shelf', 'nova.volume.driver')
|
||||||
flags.DECLARE('iscsi_num_targets', 'nova.volume.driver')
|
flags.DECLARE('iscsi_num_targets', 'nova.volume.driver')
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ from nova import test
|
|||||||
from nova.auth import manager
|
from nova.auth import manager
|
||||||
from nova.compute import power_state
|
from nova.compute import power_state
|
||||||
from nova.api.ec2 import cloud
|
from nova.api.ec2 import cloud
|
||||||
|
from nova.api.ec2 import ec2utils
|
||||||
|
from nova.image import local
|
||||||
from nova.objectstore import image
|
from nova.objectstore import image
|
||||||
|
|
||||||
|
|
||||||
@@ -76,6 +78,12 @@ class CloudTestCase(test.TestCase):
|
|||||||
project=self.project)
|
project=self.project)
|
||||||
host = self.network.get_network_host(self.context.elevated())
|
host = self.network.get_network_host(self.context.elevated())
|
||||||
|
|
||||||
|
def fake_show(meh, context, id):
|
||||||
|
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
|
||||||
|
|
||||||
|
self.stubs.Set(local.LocalImageService, 'show', fake_show)
|
||||||
|
self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
network_ref = db.project_get_network(self.context,
|
network_ref = db.project_get_network(self.context,
|
||||||
self.project.id)
|
self.project.id)
|
||||||
@@ -122,7 +130,7 @@ class CloudTestCase(test.TestCase):
|
|||||||
self.cloud.allocate_address(self.context)
|
self.cloud.allocate_address(self.context)
|
||||||
inst = db.instance_create(self.context, {'host': self.compute.host})
|
inst = db.instance_create(self.context, {'host': self.compute.host})
|
||||||
fixed = self.network.allocate_fixed_ip(self.context, inst['id'])
|
fixed = self.network.allocate_fixed_ip(self.context, inst['id'])
|
||||||
ec2_id = cloud.id_to_ec2_id(inst['id'])
|
ec2_id = ec2utils.id_to_ec2_id(inst['id'])
|
||||||
self.cloud.associate_address(self.context,
|
self.cloud.associate_address(self.context,
|
||||||
instance_id=ec2_id,
|
instance_id=ec2_id,
|
||||||
public_ip=address)
|
public_ip=address)
|
||||||
@@ -158,12 +166,12 @@ class CloudTestCase(test.TestCase):
|
|||||||
vol2 = db.volume_create(self.context, {})
|
vol2 = db.volume_create(self.context, {})
|
||||||
result = self.cloud.describe_volumes(self.context)
|
result = self.cloud.describe_volumes(self.context)
|
||||||
self.assertEqual(len(result['volumeSet']), 2)
|
self.assertEqual(len(result['volumeSet']), 2)
|
||||||
volume_id = cloud.id_to_ec2_id(vol2['id'], 'vol-%08x')
|
volume_id = ec2utils.id_to_ec2_id(vol2['id'], 'vol-%08x')
|
||||||
result = self.cloud.describe_volumes(self.context,
|
result = self.cloud.describe_volumes(self.context,
|
||||||
volume_id=[volume_id])
|
volume_id=[volume_id])
|
||||||
self.assertEqual(len(result['volumeSet']), 1)
|
self.assertEqual(len(result['volumeSet']), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
cloud.ec2_id_to_id(result['volumeSet'][0]['volumeId']),
|
ec2utils.ec2_id_to_id(result['volumeSet'][0]['volumeId']),
|
||||||
vol2['id'])
|
vol2['id'])
|
||||||
db.volume_destroy(self.context, vol1['id'])
|
db.volume_destroy(self.context, vol1['id'])
|
||||||
db.volume_destroy(self.context, vol2['id'])
|
db.volume_destroy(self.context, vol2['id'])
|
||||||
@@ -188,8 +196,10 @@ class CloudTestCase(test.TestCase):
|
|||||||
def test_describe_instances(self):
|
def test_describe_instances(self):
|
||||||
"""Makes sure describe_instances works and filters results."""
|
"""Makes sure describe_instances works and filters results."""
|
||||||
inst1 = db.instance_create(self.context, {'reservation_id': 'a',
|
inst1 = db.instance_create(self.context, {'reservation_id': 'a',
|
||||||
|
'image_id': 1,
|
||||||
'host': 'host1'})
|
'host': 'host1'})
|
||||||
inst2 = db.instance_create(self.context, {'reservation_id': 'a',
|
inst2 = db.instance_create(self.context, {'reservation_id': 'a',
|
||||||
|
'image_id': 1,
|
||||||
'host': 'host2'})
|
'host': 'host2'})
|
||||||
comp1 = db.service_create(self.context, {'host': 'host1',
|
comp1 = db.service_create(self.context, {'host': 'host1',
|
||||||
'availability_zone': 'zone1',
|
'availability_zone': 'zone1',
|
||||||
@@ -200,7 +210,7 @@ class CloudTestCase(test.TestCase):
|
|||||||
result = self.cloud.describe_instances(self.context)
|
result = self.cloud.describe_instances(self.context)
|
||||||
result = result['reservationSet'][0]
|
result = result['reservationSet'][0]
|
||||||
self.assertEqual(len(result['instancesSet']), 2)
|
self.assertEqual(len(result['instancesSet']), 2)
|
||||||
instance_id = cloud.id_to_ec2_id(inst2['id'])
|
instance_id = ec2utils.id_to_ec2_id(inst2['id'])
|
||||||
result = self.cloud.describe_instances(self.context,
|
result = self.cloud.describe_instances(self.context,
|
||||||
instance_id=[instance_id])
|
instance_id=[instance_id])
|
||||||
result = result['reservationSet'][0]
|
result = result['reservationSet'][0]
|
||||||
@@ -215,10 +225,9 @@ class CloudTestCase(test.TestCase):
|
|||||||
db.service_destroy(self.context, comp2['id'])
|
db.service_destroy(self.context, comp2['id'])
|
||||||
|
|
||||||
def test_console_output(self):
|
def test_console_output(self):
|
||||||
image_id = FLAGS.default_image
|
|
||||||
instance_type = FLAGS.default_instance_type
|
instance_type = FLAGS.default_instance_type
|
||||||
max_count = 1
|
max_count = 1
|
||||||
kwargs = {'image_id': image_id,
|
kwargs = {'image_id': 'ami-1',
|
||||||
'instance_type': instance_type,
|
'instance_type': instance_type,
|
||||||
'max_count': max_count}
|
'max_count': max_count}
|
||||||
rv = self.cloud.run_instances(self.context, **kwargs)
|
rv = self.cloud.run_instances(self.context, **kwargs)
|
||||||
@@ -234,8 +243,7 @@ class CloudTestCase(test.TestCase):
|
|||||||
greenthread.sleep(0.3)
|
greenthread.sleep(0.3)
|
||||||
|
|
||||||
def test_ajax_console(self):
|
def test_ajax_console(self):
|
||||||
image_id = FLAGS.default_image
|
kwargs = {'image_id': 'ami-1'}
|
||||||
kwargs = {'image_id': image_id}
|
|
||||||
rv = self.cloud.run_instances(self.context, **kwargs)
|
rv = self.cloud.run_instances(self.context, **kwargs)
|
||||||
instance_id = rv['instancesSet'][0]['instanceId']
|
instance_id = rv['instancesSet'][0]['instanceId']
|
||||||
greenthread.sleep(0.3)
|
greenthread.sleep(0.3)
|
||||||
@@ -347,7 +355,7 @@ class CloudTestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_update_of_instance_display_fields(self):
|
def test_update_of_instance_display_fields(self):
|
||||||
inst = db.instance_create(self.context, {})
|
inst = db.instance_create(self.context, {})
|
||||||
ec2_id = cloud.id_to_ec2_id(inst['id'])
|
ec2_id = ec2utils.id_to_ec2_id(inst['id'])
|
||||||
self.cloud.update_instance(self.context, ec2_id,
|
self.cloud.update_instance(self.context, ec2_id,
|
||||||
display_name='c00l 1m4g3')
|
display_name='c00l 1m4g3')
|
||||||
inst = db.instance_get(self.context, inst['id'])
|
inst = db.instance_get(self.context, inst['id'])
|
||||||
@@ -365,7 +373,7 @@ class CloudTestCase(test.TestCase):
|
|||||||
def test_update_of_volume_display_fields(self):
|
def test_update_of_volume_display_fields(self):
|
||||||
vol = db.volume_create(self.context, {})
|
vol = db.volume_create(self.context, {})
|
||||||
self.cloud.update_volume(self.context,
|
self.cloud.update_volume(self.context,
|
||||||
cloud.id_to_ec2_id(vol['id'], 'vol-%08x'),
|
ec2utils.id_to_ec2_id(vol['id'], 'vol-%08x'),
|
||||||
display_name='c00l v0lum3')
|
display_name='c00l v0lum3')
|
||||||
vol = db.volume_get(self.context, vol['id'])
|
vol = db.volume_get(self.context, vol['id'])
|
||||||
self.assertEqual('c00l v0lum3', vol['display_name'])
|
self.assertEqual('c00l v0lum3', vol['display_name'])
|
||||||
@@ -374,7 +382,7 @@ class CloudTestCase(test.TestCase):
|
|||||||
def test_update_of_volume_wont_update_private_fields(self):
|
def test_update_of_volume_wont_update_private_fields(self):
|
||||||
vol = db.volume_create(self.context, {})
|
vol = db.volume_create(self.context, {})
|
||||||
self.cloud.update_volume(self.context,
|
self.cloud.update_volume(self.context,
|
||||||
cloud.id_to_ec2_id(vol['id'], 'vol-%08x'),
|
ec2utils.id_to_ec2_id(vol['id'], 'vol-%08x'),
|
||||||
mountpoint='/not/here')
|
mountpoint='/not/here')
|
||||||
vol = db.volume_get(self.context, vol['id'])
|
vol = db.volume_get(self.context, vol['id'])
|
||||||
self.assertEqual(None, vol['mountpoint'])
|
self.assertEqual(None, vol['mountpoint'])
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ from nova.compute import instance_types
|
|||||||
from nova.compute import manager as compute_manager
|
from nova.compute import manager as compute_manager
|
||||||
from nova.compute import power_state
|
from nova.compute import power_state
|
||||||
from nova.db.sqlalchemy import models
|
from nova.db.sqlalchemy import models
|
||||||
|
from nova.image import local
|
||||||
|
|
||||||
LOG = logging.getLogger('nova.tests.compute')
|
LOG = logging.getLogger('nova.tests.compute')
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
@@ -58,6 +58,11 @@ class ComputeTestCase(test.TestCase):
|
|||||||
self.project = self.manager.create_project('fake', 'fake', 'fake')
|
self.project = self.manager.create_project('fake', 'fake', 'fake')
|
||||||
self.context = context.RequestContext('fake', 'fake', False)
|
self.context = context.RequestContext('fake', 'fake', False)
|
||||||
|
|
||||||
|
def fake_show(meh, context, id):
|
||||||
|
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
|
||||||
|
|
||||||
|
self.stubs.Set(local.LocalImageService, 'show', fake_show)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.manager.delete_user(self.user)
|
self.manager.delete_user(self.user)
|
||||||
self.manager.delete_project(self.project)
|
self.manager.delete_project(self.project)
|
||||||
@@ -66,7 +71,7 @@ class ComputeTestCase(test.TestCase):
|
|||||||
def _create_instance(self, params={}):
|
def _create_instance(self, params={}):
|
||||||
"""Create a test instance"""
|
"""Create a test instance"""
|
||||||
inst = {}
|
inst = {}
|
||||||
inst['image_id'] = 'ami-test'
|
inst['image_id'] = 1
|
||||||
inst['reservation_id'] = 'r-fakeres'
|
inst['reservation_id'] = 'r-fakeres'
|
||||||
inst['launch_time'] = '10'
|
inst['launch_time'] = '10'
|
||||||
inst['user_id'] = self.user.id
|
inst['user_id'] = self.user.id
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class ConsoleTestCase(test.TestCase):
|
|||||||
inst = {}
|
inst = {}
|
||||||
#inst['host'] = self.host
|
#inst['host'] = self.host
|
||||||
#inst['name'] = 'instance-1234'
|
#inst['name'] = 'instance-1234'
|
||||||
inst['image_id'] = 'ami-test'
|
inst['image_id'] = 1
|
||||||
inst['reservation_id'] = 'r-fakeres'
|
inst['reservation_id'] = 'r-fakeres'
|
||||||
inst['launch_time'] = '10'
|
inst['launch_time'] = '10'
|
||||||
inst['user_id'] = self.user.id
|
inst['user_id'] = self.user.id
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ class DirectTestCase(test.TestCase):
|
|||||||
req.headers['X-OpenStack-User'] = 'user1'
|
req.headers['X-OpenStack-User'] = 'user1'
|
||||||
req.headers['X-OpenStack-Project'] = 'proj1'
|
req.headers['X-OpenStack-Project'] = 'proj1'
|
||||||
resp = req.get_response(self.auth_router)
|
resp = req.get_response(self.auth_router)
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
data = json.loads(resp.body)
|
data = json.loads(resp.body)
|
||||||
self.assertEqual(data['user'], 'user1')
|
self.assertEqual(data['user'], 'user1')
|
||||||
self.assertEqual(data['project'], 'proj1')
|
self.assertEqual(data['project'], 'proj1')
|
||||||
@@ -69,6 +70,7 @@ class DirectTestCase(test.TestCase):
|
|||||||
req.method = 'POST'
|
req.method = 'POST'
|
||||||
req.body = 'json=%s' % json.dumps({'data': 'foo'})
|
req.body = 'json=%s' % json.dumps({'data': 'foo'})
|
||||||
resp = req.get_response(self.router)
|
resp = req.get_response(self.router)
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
resp_parsed = json.loads(resp.body)
|
resp_parsed = json.loads(resp.body)
|
||||||
self.assertEqual(resp_parsed['data'], 'foo')
|
self.assertEqual(resp_parsed['data'], 'foo')
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ class DirectTestCase(test.TestCase):
|
|||||||
req.method = 'POST'
|
req.method = 'POST'
|
||||||
req.body = 'data=foo'
|
req.body = 'data=foo'
|
||||||
resp = req.get_response(self.router)
|
resp = req.get_response(self.router)
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
resp_parsed = json.loads(resp.body)
|
resp_parsed = json.loads(resp.body)
|
||||||
self.assertEqual(resp_parsed['data'], 'foo')
|
self.assertEqual(resp_parsed['data'], 'foo')
|
||||||
|
|
||||||
@@ -90,8 +93,7 @@ class DirectTestCase(test.TestCase):
|
|||||||
class DirectCloudTestCase(test_cloud.CloudTestCase):
|
class DirectCloudTestCase(test_cloud.CloudTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(DirectCloudTestCase, self).setUp()
|
super(DirectCloudTestCase, self).setUp()
|
||||||
compute_handle = compute.API(image_service=self.cloud.image_service,
|
compute_handle = compute.API(network_api=self.cloud.network_api,
|
||||||
network_api=self.cloud.network_api,
|
|
||||||
volume_api=self.cloud.volume_api)
|
volume_api=self.cloud.volume_api)
|
||||||
direct.register_service('compute', compute_handle)
|
direct.register_service('compute', compute_handle)
|
||||||
self.router = direct.JsonParamsMiddleware(direct.Router())
|
self.router = direct.JsonParamsMiddleware(direct.Router())
|
||||||
|
|||||||
@@ -14,10 +14,12 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import errno
|
||||||
import os
|
import os
|
||||||
|
import select
|
||||||
|
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova.utils import parse_mailmap, str_dict_replace
|
from nova.utils import parse_mailmap, str_dict_replace, synchronized
|
||||||
|
|
||||||
|
|
||||||
class ProjectTestCase(test.TestCase):
|
class ProjectTestCase(test.TestCase):
|
||||||
@@ -55,3 +57,47 @@ class ProjectTestCase(test.TestCase):
|
|||||||
'%r not listed in Authors' % missing)
|
'%r not listed in Authors' % missing)
|
||||||
finally:
|
finally:
|
||||||
tree.unlock()
|
tree.unlock()
|
||||||
|
|
||||||
|
|
||||||
|
class LockTestCase(test.TestCase):
|
||||||
|
def test_synchronized_wrapped_function_metadata(self):
|
||||||
|
@synchronized('whatever')
|
||||||
|
def foo():
|
||||||
|
"""Bar"""
|
||||||
|
pass
|
||||||
|
self.assertEquals(foo.__doc__, 'Bar', "Wrapped function's docstring "
|
||||||
|
"got lost")
|
||||||
|
self.assertEquals(foo.__name__, 'foo', "Wrapped function's name "
|
||||||
|
"got mangled")
|
||||||
|
|
||||||
|
def test_synchronized(self):
|
||||||
|
rpipe1, wpipe1 = os.pipe()
|
||||||
|
rpipe2, wpipe2 = os.pipe()
|
||||||
|
|
||||||
|
@synchronized('testlock')
|
||||||
|
def f(rpipe, wpipe):
|
||||||
|
try:
|
||||||
|
os.write(wpipe, "foo")
|
||||||
|
except OSError, e:
|
||||||
|
self.assertEquals(e.errno, errno.EPIPE)
|
||||||
|
return
|
||||||
|
|
||||||
|
rfds, _, __ = select.select([rpipe], [], [], 1)
|
||||||
|
self.assertEquals(len(rfds), 0, "The other process, which was"
|
||||||
|
" supposed to be locked, "
|
||||||
|
"wrote on its end of the "
|
||||||
|
"pipe")
|
||||||
|
os.close(rpipe)
|
||||||
|
|
||||||
|
pid = os.fork()
|
||||||
|
if pid > 0:
|
||||||
|
os.close(wpipe1)
|
||||||
|
os.close(rpipe2)
|
||||||
|
|
||||||
|
f(rpipe1, wpipe2)
|
||||||
|
else:
|
||||||
|
os.close(rpipe1)
|
||||||
|
os.close(wpipe2)
|
||||||
|
|
||||||
|
f(rpipe2, wpipe1)
|
||||||
|
os._exit(0)
|
||||||
|
|||||||
@@ -343,13 +343,13 @@ def lease_ip(private_ip):
|
|||||||
private_ip)
|
private_ip)
|
||||||
instance_ref = db.fixed_ip_get_instance(context.get_admin_context(),
|
instance_ref = db.fixed_ip_get_instance(context.get_admin_context(),
|
||||||
private_ip)
|
private_ip)
|
||||||
cmd = "%s add %s %s fake" % (binpath('nova-dhcpbridge'),
|
cmd = (binpath('nova-dhcpbridge'), 'add',
|
||||||
instance_ref['mac_address'],
|
instance_ref['mac_address'],
|
||||||
private_ip)
|
private_ip, 'fake')
|
||||||
env = {'DNSMASQ_INTERFACE': network_ref['bridge'],
|
env = {'DNSMASQ_INTERFACE': network_ref['bridge'],
|
||||||
'TESTING': '1',
|
'TESTING': '1',
|
||||||
'FLAGFILE': FLAGS.dhcpbridge_flagfile}
|
'FLAGFILE': FLAGS.dhcpbridge_flagfile}
|
||||||
(out, err) = utils.execute(cmd, addl_env=env)
|
(out, err) = utils.execute(*cmd, addl_env=env)
|
||||||
LOG.debug("ISSUE_IP: %s, %s ", out, err)
|
LOG.debug("ISSUE_IP: %s, %s ", out, err)
|
||||||
|
|
||||||
|
|
||||||
@@ -359,11 +359,11 @@ def release_ip(private_ip):
|
|||||||
private_ip)
|
private_ip)
|
||||||
instance_ref = db.fixed_ip_get_instance(context.get_admin_context(),
|
instance_ref = db.fixed_ip_get_instance(context.get_admin_context(),
|
||||||
private_ip)
|
private_ip)
|
||||||
cmd = "%s del %s %s fake" % (binpath('nova-dhcpbridge'),
|
cmd = (binpath('nova-dhcpbridge'), 'del',
|
||||||
instance_ref['mac_address'],
|
instance_ref['mac_address'],
|
||||||
private_ip)
|
private_ip, 'fake')
|
||||||
env = {'DNSMASQ_INTERFACE': network_ref['bridge'],
|
env = {'DNSMASQ_INTERFACE': network_ref['bridge'],
|
||||||
'TESTING': '1',
|
'TESTING': '1',
|
||||||
'FLAGFILE': FLAGS.dhcpbridge_flagfile}
|
'FLAGFILE': FLAGS.dhcpbridge_flagfile}
|
||||||
(out, err) = utils.execute(cmd, addl_env=env)
|
(out, err) = utils.execute(*cmd, addl_env=env)
|
||||||
LOG.debug("RELEASE_IP: %s, %s ", out, err)
|
LOG.debug("RELEASE_IP: %s, %s ", out, err)
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ class SimpleDriverTestCase(test.TestCase):
|
|||||||
def _create_instance(self, **kwargs):
|
def _create_instance(self, **kwargs):
|
||||||
"""Create a test instance"""
|
"""Create a test instance"""
|
||||||
inst = {}
|
inst = {}
|
||||||
inst['image_id'] = 'ami-test'
|
inst['image_id'] = 1
|
||||||
inst['reservation_id'] = 'r-fakeres'
|
inst['reservation_id'] = 'r-fakeres'
|
||||||
inst['user_id'] = self.user.id
|
inst['user_id'] = self.user.id
|
||||||
inst['project_id'] = self.project.id
|
inst['project_id'] = self.project.id
|
||||||
@@ -275,8 +275,6 @@ class SimpleDriverTestCase(test.TestCase):
|
|||||||
def _create_volume(self):
|
def _create_volume(self):
|
||||||
"""Create a test volume"""
|
"""Create a test volume"""
|
||||||
vol = {}
|
vol = {}
|
||||||
vol['image_id'] = 'ami-test'
|
|
||||||
vol['reservation_id'] = 'r-fakeres'
|
|
||||||
vol['size'] = 1
|
vol['size'] = 1
|
||||||
vol['availability_zone'] = 'test'
|
vol['availability_zone'] = 'test'
|
||||||
return db.volume_create(self.context, vol)['id']
|
return db.volume_create(self.context, vol)['id']
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import eventlet
|
||||||
import mox
|
import mox
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from xml.etree.ElementTree import fromstring as xml_to_tree
|
from xml.etree.ElementTree import fromstring as xml_to_tree
|
||||||
@@ -38,6 +40,70 @@ FLAGS = flags.FLAGS
|
|||||||
flags.DECLARE('instances_path', 'nova.compute.manager')
|
flags.DECLARE('instances_path', 'nova.compute.manager')
|
||||||
|
|
||||||
|
|
||||||
|
def _concurrency(wait, done, target):
|
||||||
|
wait.wait()
|
||||||
|
done.send()
|
||||||
|
|
||||||
|
|
||||||
|
class CacheConcurrencyTestCase(test.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(CacheConcurrencyTestCase, self).setUp()
|
||||||
|
|
||||||
|
def fake_exists(fname):
|
||||||
|
basedir = os.path.join(FLAGS.instances_path, '_base')
|
||||||
|
if fname == basedir:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def fake_execute(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.stubs.Set(os.path, 'exists', fake_exists)
|
||||||
|
self.stubs.Set(utils, 'execute', fake_execute)
|
||||||
|
|
||||||
|
def test_same_fname_concurrency(self):
|
||||||
|
"""Ensures that the same fname cache runs at a sequentially"""
|
||||||
|
conn = libvirt_conn.LibvirtConnection
|
||||||
|
wait1 = eventlet.event.Event()
|
||||||
|
done1 = eventlet.event.Event()
|
||||||
|
eventlet.spawn(conn._cache_image, _concurrency,
|
||||||
|
'target', 'fname', False, wait1, done1)
|
||||||
|
wait2 = eventlet.event.Event()
|
||||||
|
done2 = eventlet.event.Event()
|
||||||
|
eventlet.spawn(conn._cache_image, _concurrency,
|
||||||
|
'target', 'fname', False, wait2, done2)
|
||||||
|
wait2.send()
|
||||||
|
eventlet.sleep(0)
|
||||||
|
try:
|
||||||
|
self.assertFalse(done2.ready())
|
||||||
|
self.assertTrue('fname' in conn._image_sems)
|
||||||
|
finally:
|
||||||
|
wait1.send()
|
||||||
|
done1.wait()
|
||||||
|
eventlet.sleep(0)
|
||||||
|
self.assertTrue(done2.ready())
|
||||||
|
self.assertFalse('fname' in conn._image_sems)
|
||||||
|
|
||||||
|
def test_different_fname_concurrency(self):
|
||||||
|
"""Ensures that two different fname caches are concurrent"""
|
||||||
|
conn = libvirt_conn.LibvirtConnection
|
||||||
|
wait1 = eventlet.event.Event()
|
||||||
|
done1 = eventlet.event.Event()
|
||||||
|
eventlet.spawn(conn._cache_image, _concurrency,
|
||||||
|
'target', 'fname2', False, wait1, done1)
|
||||||
|
wait2 = eventlet.event.Event()
|
||||||
|
done2 = eventlet.event.Event()
|
||||||
|
eventlet.spawn(conn._cache_image, _concurrency,
|
||||||
|
'target', 'fname1', False, wait2, done2)
|
||||||
|
wait2.send()
|
||||||
|
eventlet.sleep(0)
|
||||||
|
try:
|
||||||
|
self.assertTrue(done2.ready())
|
||||||
|
finally:
|
||||||
|
wait1.send()
|
||||||
|
eventlet.sleep(0)
|
||||||
|
|
||||||
|
|
||||||
class LibvirtConnTestCase(test.TestCase):
|
class LibvirtConnTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(LibvirtConnTestCase, self).setUp()
|
super(LibvirtConnTestCase, self).setUp()
|
||||||
@@ -536,15 +602,16 @@ class IptablesFirewallTestCase(test.TestCase):
|
|||||||
instance_ref = db.instance_get(admin_ctxt, instance_ref['id'])
|
instance_ref = db.instance_get(admin_ctxt, instance_ref['id'])
|
||||||
|
|
||||||
# self.fw.add_instance(instance_ref)
|
# self.fw.add_instance(instance_ref)
|
||||||
def fake_iptables_execute(cmd, process_input=None):
|
def fake_iptables_execute(*cmd, **kwargs):
|
||||||
if cmd == 'sudo ip6tables-save -t filter':
|
process_input = kwargs.get('process_input', None)
|
||||||
|
if cmd == ('sudo', 'ip6tables-save', '-t', 'filter'):
|
||||||
return '\n'.join(self.in6_rules), None
|
return '\n'.join(self.in6_rules), None
|
||||||
if cmd == 'sudo iptables-save -t filter':
|
if cmd == ('sudo', 'iptables-save', '-t', 'filter'):
|
||||||
return '\n'.join(self.in_rules), None
|
return '\n'.join(self.in_rules), None
|
||||||
if cmd == 'sudo iptables-restore':
|
if cmd == ('sudo', 'iptables-restore'):
|
||||||
self.out_rules = process_input.split('\n')
|
self.out_rules = process_input.split('\n')
|
||||||
return '', ''
|
return '', ''
|
||||||
if cmd == 'sudo ip6tables-restore':
|
if cmd == ('sudo', 'ip6tables-restore'):
|
||||||
self.out6_rules = process_input.split('\n')
|
self.out6_rules = process_input.split('\n')
|
||||||
return '', ''
|
return '', ''
|
||||||
self.fw.execute = fake_iptables_execute
|
self.fw.execute = fake_iptables_execute
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class VolumeTestCase(test.TestCase):
|
|||||||
def test_run_attach_detach_volume(self):
|
def test_run_attach_detach_volume(self):
|
||||||
"""Make sure volume can be attached and detached from instance."""
|
"""Make sure volume can be attached and detached from instance."""
|
||||||
inst = {}
|
inst = {}
|
||||||
inst['image_id'] = 'ami-test'
|
inst['image_id'] = 1
|
||||||
inst['reservation_id'] = 'r-fakeres'
|
inst['reservation_id'] = 'r-fakeres'
|
||||||
inst['launch_time'] = '10'
|
inst['launch_time'] = '10'
|
||||||
inst['user_id'] = 'fake'
|
inst['user_id'] = 'fake'
|
||||||
|
|||||||
@@ -23,10 +23,14 @@ System-level utilities and helper functions.
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
|
import lockfile
|
||||||
|
import netaddr
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import socket
|
import socket
|
||||||
import string
|
import string
|
||||||
import struct
|
import struct
|
||||||
@@ -34,20 +38,20 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import types
|
import types
|
||||||
from xml.sax import saxutils
|
from xml.sax import saxutils
|
||||||
import re
|
|
||||||
import netaddr
|
|
||||||
|
|
||||||
from eventlet import event
|
from eventlet import event
|
||||||
from eventlet import greenthread
|
from eventlet import greenthread
|
||||||
from eventlet.green import subprocess
|
from eventlet.green import subprocess
|
||||||
|
None
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.exception import ProcessExecutionError
|
from nova.exception import ProcessExecutionError
|
||||||
|
from nova import flags
|
||||||
from nova import log as logging
|
from nova import log as logging
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("nova.utils")
|
LOG = logging.getLogger("nova.utils")
|
||||||
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
def import_class(import_str):
|
def import_class(import_str):
|
||||||
@@ -125,16 +129,24 @@ def fetchfile(url, target):
|
|||||||
# c.perform()
|
# c.perform()
|
||||||
# c.close()
|
# c.close()
|
||||||
# fp.close()
|
# fp.close()
|
||||||
execute("curl --fail %s -o %s" % (url, target))
|
execute("curl", "--fail", url, "-o", target)
|
||||||
|
|
||||||
|
|
||||||
def execute(cmd, process_input=None, addl_env=None, check_exit_code=True):
|
def execute(*cmd, **kwargs):
|
||||||
LOG.debug(_("Running cmd (subprocess): %s"), cmd)
|
process_input = kwargs.get('process_input', None)
|
||||||
|
addl_env = kwargs.get('addl_env', None)
|
||||||
|
check_exit_code = kwargs.get('check_exit_code', 0)
|
||||||
|
stdin = kwargs.get('stdin', subprocess.PIPE)
|
||||||
|
stdout = kwargs.get('stdout', subprocess.PIPE)
|
||||||
|
stderr = kwargs.get('stderr', subprocess.PIPE)
|
||||||
|
cmd = map(str, cmd)
|
||||||
|
|
||||||
|
LOG.debug(_("Running cmd (subprocess): %s"), ' '.join(cmd))
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
if addl_env:
|
if addl_env:
|
||||||
env.update(addl_env)
|
env.update(addl_env)
|
||||||
obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
obj = subprocess.Popen(cmd, stdin=stdin,
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
|
stdout=stdout, stderr=stderr, env=env)
|
||||||
result = None
|
result = None
|
||||||
if process_input != None:
|
if process_input != None:
|
||||||
result = obj.communicate(process_input)
|
result = obj.communicate(process_input)
|
||||||
@@ -143,12 +155,13 @@ def execute(cmd, process_input=None, addl_env=None, check_exit_code=True):
|
|||||||
obj.stdin.close()
|
obj.stdin.close()
|
||||||
if obj.returncode:
|
if obj.returncode:
|
||||||
LOG.debug(_("Result was %s") % obj.returncode)
|
LOG.debug(_("Result was %s") % obj.returncode)
|
||||||
if check_exit_code and obj.returncode != 0:
|
if type(check_exit_code) == types.IntType \
|
||||||
|
and obj.returncode != check_exit_code:
|
||||||
(stdout, stderr) = result
|
(stdout, stderr) = result
|
||||||
raise ProcessExecutionError(exit_code=obj.returncode,
|
raise ProcessExecutionError(exit_code=obj.returncode,
|
||||||
stdout=stdout,
|
stdout=stdout,
|
||||||
stderr=stderr,
|
stderr=stderr,
|
||||||
cmd=cmd)
|
cmd=' '.join(cmd))
|
||||||
# NOTE(termie): this appears to be necessary to let the subprocess call
|
# NOTE(termie): this appears to be necessary to let the subprocess call
|
||||||
# clean something up in between calls, without it two
|
# clean something up in between calls, without it two
|
||||||
# execute calls in a row hangs the second one
|
# execute calls in a row hangs the second one
|
||||||
@@ -158,7 +171,7 @@ def execute(cmd, process_input=None, addl_env=None, check_exit_code=True):
|
|||||||
|
|
||||||
def ssh_execute(ssh, cmd, process_input=None,
|
def ssh_execute(ssh, cmd, process_input=None,
|
||||||
addl_env=None, check_exit_code=True):
|
addl_env=None, check_exit_code=True):
|
||||||
LOG.debug(_("Running cmd (SSH): %s"), cmd)
|
LOG.debug(_("Running cmd (SSH): %s"), ' '.join(cmd))
|
||||||
if addl_env:
|
if addl_env:
|
||||||
raise exception.Error("Environment not supported over SSH")
|
raise exception.Error("Environment not supported over SSH")
|
||||||
|
|
||||||
@@ -187,7 +200,7 @@ def ssh_execute(ssh, cmd, process_input=None,
|
|||||||
raise exception.ProcessExecutionError(exit_code=exit_status,
|
raise exception.ProcessExecutionError(exit_code=exit_status,
|
||||||
stdout=stdout,
|
stdout=stdout,
|
||||||
stderr=stderr,
|
stderr=stderr,
|
||||||
cmd=cmd)
|
cmd=' '.join(cmd))
|
||||||
|
|
||||||
return (stdout, stderr)
|
return (stdout, stderr)
|
||||||
|
|
||||||
@@ -220,9 +233,9 @@ def debug(arg):
|
|||||||
return arg
|
return arg
|
||||||
|
|
||||||
|
|
||||||
def runthis(prompt, cmd, check_exit_code=True):
|
def runthis(prompt, *cmd, **kwargs):
|
||||||
LOG.debug(_("Running %s"), (cmd))
|
LOG.debug(_("Running %s"), (" ".join(cmd)))
|
||||||
rv, err = execute(cmd, check_exit_code=check_exit_code)
|
rv, err = execute(*cmd, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def generate_uid(topic, size=8):
|
def generate_uid(topic, size=8):
|
||||||
@@ -254,7 +267,7 @@ def last_octet(address):
|
|||||||
|
|
||||||
def get_my_linklocal(interface):
|
def get_my_linklocal(interface):
|
||||||
try:
|
try:
|
||||||
if_str = execute("ip -f inet6 -o addr show %s" % interface)
|
if_str = execute("ip", "-f", "inet6", "-o", "addr", "show", interface)
|
||||||
condition = "\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link"
|
condition = "\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link"
|
||||||
links = [re.search(condition, x) for x in if_str[0].split('\n')]
|
links = [re.search(condition, x) for x in if_str[0].split('\n')]
|
||||||
address = [w.group(1) for w in links if w is not None]
|
address = [w.group(1) for w in links if w is not None]
|
||||||
@@ -491,6 +504,18 @@ def loads(s):
|
|||||||
return json.loads(s)
|
return json.loads(s)
|
||||||
|
|
||||||
|
|
||||||
|
def synchronized(name):
|
||||||
|
def wrap(f):
|
||||||
|
@functools.wraps(f)
|
||||||
|
def inner(*args, **kwargs):
|
||||||
|
lock = lockfile.FileLock(os.path.join(FLAGS.lock_path,
|
||||||
|
'nova-%s.lock' % name))
|
||||||
|
with lock:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return inner
|
||||||
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
def ensure_b64_encoding(val):
|
def ensure_b64_encoding(val):
|
||||||
"""Safety method to ensure that values expected to be base64-encoded
|
"""Safety method to ensure that values expected to be base64-encoded
|
||||||
actually are. If they are, the value is returned unchanged. Otherwise,
|
actually are. If they are, the value is returned unchanged. Otherwise,
|
||||||
|
|||||||
131
nova/wsgi.py
131
nova/wsgi.py
@@ -36,6 +36,7 @@ import webob.exc
|
|||||||
|
|
||||||
from paste import deploy
|
from paste import deploy
|
||||||
|
|
||||||
|
from nova import exception
|
||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import log as logging
|
from nova import log as logging
|
||||||
from nova import utils
|
from nova import utils
|
||||||
@@ -82,6 +83,35 @@ class Server(object):
|
|||||||
log=WritableLogger(logger))
|
log=WritableLogger(logger))
|
||||||
|
|
||||||
|
|
||||||
|
class Request(webob.Request):
|
||||||
|
|
||||||
|
def best_match_content_type(self):
|
||||||
|
"""
|
||||||
|
Determine the most acceptable content-type based on the
|
||||||
|
query extension then the Accept header
|
||||||
|
"""
|
||||||
|
|
||||||
|
parts = self.path.rsplit(".", 1)
|
||||||
|
|
||||||
|
if len(parts) > 1:
|
||||||
|
format = parts[1]
|
||||||
|
if format in ["json", "xml"]:
|
||||||
|
return "application/{0}".format(parts[1])
|
||||||
|
|
||||||
|
ctypes = ["application/json", "application/xml"]
|
||||||
|
bm = self.accept.best_match(ctypes)
|
||||||
|
|
||||||
|
return bm or "application/json"
|
||||||
|
|
||||||
|
def get_content_type(self):
|
||||||
|
try:
|
||||||
|
ct = self.headers["Content-Type"]
|
||||||
|
assert ct in ("application/xml", "application/json")
|
||||||
|
return ct
|
||||||
|
except Exception:
|
||||||
|
raise webob.exc.HTTPBadRequest("Invalid content type")
|
||||||
|
|
||||||
|
|
||||||
class Application(object):
|
class Application(object):
|
||||||
"""Base WSGI application wrapper. Subclasses need to implement __call__."""
|
"""Base WSGI application wrapper. Subclasses need to implement __call__."""
|
||||||
|
|
||||||
@@ -113,7 +143,7 @@ class Application(object):
|
|||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
r"""Subclasses will probably want to implement __call__ like this:
|
r"""Subclasses will probably want to implement __call__ like this:
|
||||||
|
|
||||||
@webob.dec.wsgify
|
@webob.dec.wsgify(RequestClass=Request)
|
||||||
def __call__(self, req):
|
def __call__(self, req):
|
||||||
# Any of the following objects work as responses:
|
# Any of the following objects work as responses:
|
||||||
|
|
||||||
@@ -199,7 +229,7 @@ class Middleware(Application):
|
|||||||
"""Do whatever you'd like to the response."""
|
"""Do whatever you'd like to the response."""
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@webob.dec.wsgify
|
@webob.dec.wsgify(RequestClass=Request)
|
||||||
def __call__(self, req):
|
def __call__(self, req):
|
||||||
response = self.process_request(req)
|
response = self.process_request(req)
|
||||||
if response:
|
if response:
|
||||||
@@ -212,7 +242,7 @@ class Debug(Middleware):
|
|||||||
"""Helper class that can be inserted into any WSGI application chain
|
"""Helper class that can be inserted into any WSGI application chain
|
||||||
to get information about the request and response."""
|
to get information about the request and response."""
|
||||||
|
|
||||||
@webob.dec.wsgify
|
@webob.dec.wsgify(RequestClass=Request)
|
||||||
def __call__(self, req):
|
def __call__(self, req):
|
||||||
print ("*" * 40) + " REQUEST ENVIRON"
|
print ("*" * 40) + " REQUEST ENVIRON"
|
||||||
for key, value in req.environ.items():
|
for key, value in req.environ.items():
|
||||||
@@ -276,7 +306,7 @@ class Router(object):
|
|||||||
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
|
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
|
||||||
self.map)
|
self.map)
|
||||||
|
|
||||||
@webob.dec.wsgify
|
@webob.dec.wsgify(RequestClass=Request)
|
||||||
def __call__(self, req):
|
def __call__(self, req):
|
||||||
"""
|
"""
|
||||||
Route the incoming request to a controller based on self.map.
|
Route the incoming request to a controller based on self.map.
|
||||||
@@ -285,7 +315,7 @@ class Router(object):
|
|||||||
return self._router
|
return self._router
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@webob.dec.wsgify
|
@webob.dec.wsgify(RequestClass=Request)
|
||||||
def _dispatch(req):
|
def _dispatch(req):
|
||||||
"""
|
"""
|
||||||
Called by self._router after matching the incoming request to a route
|
Called by self._router after matching the incoming request to a route
|
||||||
@@ -304,11 +334,11 @@ class Controller(object):
|
|||||||
WSGI app that reads routing information supplied by RoutesMiddleware
|
WSGI app that reads routing information supplied by RoutesMiddleware
|
||||||
and calls the requested action method upon itself. All action methods
|
and calls the requested action method upon itself. All action methods
|
||||||
must, in addition to their normal parameters, accept a 'req' argument
|
must, in addition to their normal parameters, accept a 'req' argument
|
||||||
which is the incoming webob.Request. They raise a webob.exc exception,
|
which is the incoming wsgi.Request. They raise a webob.exc exception,
|
||||||
or return a dict which will be serialized by requested content type.
|
or return a dict which will be serialized by requested content type.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@webob.dec.wsgify
|
@webob.dec.wsgify(RequestClass=Request)
|
||||||
def __call__(self, req):
|
def __call__(self, req):
|
||||||
"""
|
"""
|
||||||
Call the method specified in req.environ by RoutesMiddleware.
|
Call the method specified in req.environ by RoutesMiddleware.
|
||||||
@@ -318,32 +348,45 @@ class Controller(object):
|
|||||||
method = getattr(self, action)
|
method = getattr(self, action)
|
||||||
del arg_dict['controller']
|
del arg_dict['controller']
|
||||||
del arg_dict['action']
|
del arg_dict['action']
|
||||||
|
if 'format' in arg_dict:
|
||||||
|
del arg_dict['format']
|
||||||
arg_dict['req'] = req
|
arg_dict['req'] = req
|
||||||
result = method(**arg_dict)
|
result = method(**arg_dict)
|
||||||
|
|
||||||
if type(result) is dict:
|
if type(result) is dict:
|
||||||
return self._serialize(result, req)
|
content_type = req.best_match_content_type()
|
||||||
|
body = self._serialize(result, content_type)
|
||||||
|
|
||||||
|
response = webob.Response()
|
||||||
|
response.headers["Content-Type"] = content_type
|
||||||
|
response.body = body
|
||||||
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _serialize(self, data, request):
|
def _serialize(self, data, content_type):
|
||||||
"""
|
"""
|
||||||
Serialize the given dict to the response type requested in request.
|
Serialize the given dict to the provided content_type.
|
||||||
Uses self._serialization_metadata if it exists, which is a dict mapping
|
Uses self._serialization_metadata if it exists, which is a dict mapping
|
||||||
MIME types to information needed to serialize to that type.
|
MIME types to information needed to serialize to that type.
|
||||||
"""
|
"""
|
||||||
_metadata = getattr(type(self), "_serialization_metadata", {})
|
_metadata = getattr(type(self), "_serialization_metadata", {})
|
||||||
serializer = Serializer(request.environ, _metadata)
|
serializer = Serializer(_metadata)
|
||||||
return serializer.to_content_type(data)
|
try:
|
||||||
|
return serializer.serialize(data, content_type)
|
||||||
|
except exception.InvalidContentType:
|
||||||
|
raise webob.exc.HTTPNotAcceptable()
|
||||||
|
|
||||||
def _deserialize(self, data, request):
|
def _deserialize(self, data, content_type):
|
||||||
"""
|
"""
|
||||||
Deserialize the request body to the response type requested in request.
|
Deserialize the request body to the specefied content type.
|
||||||
Uses self._serialization_metadata if it exists, which is a dict mapping
|
Uses self._serialization_metadata if it exists, which is a dict mapping
|
||||||
MIME types to information needed to serialize to that type.
|
MIME types to information needed to serialize to that type.
|
||||||
"""
|
"""
|
||||||
_metadata = getattr(type(self), "_serialization_metadata", {})
|
_metadata = getattr(type(self), "_serialization_metadata", {})
|
||||||
serializer = Serializer(request.environ, _metadata)
|
serializer = Serializer(_metadata)
|
||||||
return serializer.deserialize(data)
|
return serializer.deserialize(data, content_type)
|
||||||
|
|
||||||
|
|
||||||
class Serializer(object):
|
class Serializer(object):
|
||||||
@@ -351,50 +394,52 @@ class Serializer(object):
|
|||||||
Serializes and deserializes dictionaries to certain MIME types.
|
Serializes and deserializes dictionaries to certain MIME types.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, environ, metadata=None):
|
def __init__(self, metadata=None):
|
||||||
"""
|
"""
|
||||||
Create a serializer based on the given WSGI environment.
|
Create a serializer based on the given WSGI environment.
|
||||||
'metadata' is an optional dict mapping MIME types to information
|
'metadata' is an optional dict mapping MIME types to information
|
||||||
needed to serialize a dictionary to that type.
|
needed to serialize a dictionary to that type.
|
||||||
"""
|
"""
|
||||||
self.metadata = metadata or {}
|
self.metadata = metadata or {}
|
||||||
req = webob.Request.blank('', environ)
|
|
||||||
suffix = req.path_info.split('.')[-1].lower()
|
|
||||||
if suffix == 'json':
|
|
||||||
self.handler = self._to_json
|
|
||||||
elif suffix == 'xml':
|
|
||||||
self.handler = self._to_xml
|
|
||||||
elif 'application/json' in req.accept:
|
|
||||||
self.handler = self._to_json
|
|
||||||
elif 'application/xml' in req.accept:
|
|
||||||
self.handler = self._to_xml
|
|
||||||
else:
|
|
||||||
# This is the default
|
|
||||||
self.handler = self._to_json
|
|
||||||
|
|
||||||
def to_content_type(self, data):
|
def _get_serialize_handler(self, content_type):
|
||||||
|
handlers = {
|
||||||
|
"application/json": self._to_json,
|
||||||
|
"application/xml": self._to_xml,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return handlers[content_type]
|
||||||
|
except Exception:
|
||||||
|
raise exception.InvalidContentType()
|
||||||
|
|
||||||
|
def serialize(self, data, content_type):
|
||||||
"""
|
"""
|
||||||
Serialize a dictionary into a string.
|
Serialize a dictionary into a string of the specified content type.
|
||||||
|
|
||||||
The format of the string will be decided based on the Content Type
|
|
||||||
requested in self.environ: by Accept: header, or by URL suffix.
|
|
||||||
"""
|
"""
|
||||||
return self.handler(data)
|
return self._get_serialize_handler(content_type)(data)
|
||||||
|
|
||||||
def deserialize(self, datastring):
|
def deserialize(self, datastring, content_type):
|
||||||
"""
|
"""
|
||||||
Deserialize a string to a dictionary.
|
Deserialize a string to a dictionary.
|
||||||
|
|
||||||
The string must be in the format of a supported MIME type.
|
The string must be in the format of a supported MIME type.
|
||||||
"""
|
"""
|
||||||
datastring = datastring.strip()
|
return self.get_deserialize_handler(content_type)(datastring)
|
||||||
|
|
||||||
|
def get_deserialize_handler(self, content_type):
|
||||||
|
handlers = {
|
||||||
|
"application/json": self._from_json,
|
||||||
|
"application/xml": self._from_xml,
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
is_xml = (datastring[0] == '<')
|
return handlers[content_type]
|
||||||
if not is_xml:
|
except Exception:
|
||||||
|
raise exception.InvalidContentType()
|
||||||
|
|
||||||
|
def _from_json(self, datastring):
|
||||||
return utils.loads(datastring)
|
return utils.loads(datastring)
|
||||||
return self._from_xml(datastring)
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _from_xml(self, datastring):
|
def _from_xml(self, datastring):
|
||||||
xmldata = self.metadata.get('application/xml', {})
|
xmldata = self.metadata.get('application/xml', {})
|
||||||
|
|||||||
Reference in New Issue
Block a user