summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@yahoo.com>2010-08-17 17:58:52 -0700
committerVishvananda Ishaya <vishvananda@yahoo.com>2010-08-17 17:58:52 -0700
commitcdcbd516f62290697643eecc56550460bd48ff14 (patch)
tree798d4da90b8025b041a3f2b5efe86f3e33e8ef53
parent1cd448f907e132c451d6b27c64d16c17b7530952 (diff)
parent018ce9abbfb7047eff1e99379fba098a365e89eb (diff)
downloadnova-cdcbd516f62290697643eecc56550460bd48ff14.tar.gz
nova-cdcbd516f62290697643eecc56550460bd48ff14.tar.xz
nova-cdcbd516f62290697643eecc56550460bd48ff14.zip
merged trunk
-rwxr-xr-xbin/nova-rsapi4
-rw-r--r--nova/adminclient.py7
-rw-r--r--nova/auth/fakeldap.py1
-rw-r--r--nova/auth/ldapdriver.py1
-rw-r--r--nova/auth/manager.py12
-rw-r--r--nova/auth/rbac.py2
-rw-r--r--nova/auth/signer.py10
-rw-r--r--nova/cloudpipe/api.py3
-rw-r--r--nova/cloudpipe/pipelib.py2
-rw-r--r--nova/compute/disk.py4
-rw-r--r--nova/compute/model.py1
-rw-r--r--nova/compute/monitor.py35
-rw-r--r--nova/compute/service.py1
-rw-r--r--nova/crypto.py8
-rw-r--r--nova/endpoint/__init__.py32
-rw-r--r--nova/endpoint/admin.py4
-rwxr-xr-xnova/endpoint/api.py7
-rw-r--r--nova/endpoint/aws/__init__.py22
-rw-r--r--nova/endpoint/cloud.py3
-rw-r--r--nova/endpoint/images.py7
-rw-r--r--nova/endpoint/newapi.py51
-rw-r--r--nova/endpoint/rackspace.py183
-rw-r--r--nova/endpoint/rackspace/__init__.py83
-rw-r--r--nova/endpoint/rackspace/controllers/__init__.py5
-rw-r--r--nova/endpoint/rackspace/controllers/base.py9
-rw-r--r--nova/endpoint/rackspace/controllers/flavors.py1
-rw-r--r--nova/endpoint/rackspace/controllers/images.py1
-rw-r--r--nova/endpoint/rackspace/controllers/servers.py63
-rw-r--r--nova/endpoint/rackspace/controllers/sharedipgroups.py1
-rw-r--r--nova/exception.py8
-rw-r--r--nova/fakerabbit.py5
-rw-r--r--nova/flags.py40
-rw-r--r--nova/network/exception.py12
-rw-r--r--nova/network/linux_net.py6
-rw-r--r--nova/network/service.py28
-rw-r--r--nova/objectstore/bucket.py1
-rw-r--r--nova/objectstore/handler.py38
-rw-r--r--nova/objectstore/image.py1
-rw-r--r--nova/objectstore/stored.py4
-rw-r--r--nova/process.py3
-rw-r--r--nova/rpc.py5
-rw-r--r--nova/test.py5
-rw-r--r--nova/utils.py12
-rw-r--r--nova/validate.py1
-rw-r--r--nova/virt/fake.py19
-rw-r--r--nova/virt/images.py8
-rw-r--r--nova/virt/libvirt_conn.py20
-rw-r--r--nova/virt/xenapi.py39
-rw-r--r--nova/wsgi.py227
-rw-r--r--pylintrc10
-rw-r--r--run_tests.py9
-rwxr-xr-xrun_tests.sh60
-rw-r--r--tools/install_venv.py13
-rw-r--r--tools/pip-requires2
54 files changed, 700 insertions, 439 deletions
diff --git a/bin/nova-rsapi b/bin/nova-rsapi
index 026880d5a..e2722422e 100755
--- a/bin/nova-rsapi
+++ b/bin/nova-rsapi
@@ -24,11 +24,11 @@
from nova import flags
from nova import utils
from nova import wsgi
-from nova.endpoint import rackspace
+from nova.endpoint import newapi
FLAGS = flags.FLAGS
flags.DEFINE_integer('cc_port', 8773, 'cloud controller port')
if __name__ == '__main__':
utils.default_flagfile()
- wsgi.run_server(rackspace.API(), FLAGS.cc_port)
+ wsgi.run_server(newapi.APIVersionRouter(), FLAGS.cc_port)
diff --git a/nova/adminclient.py b/nova/adminclient.py
index 242298a75..0ca32b1e5 100644
--- a/nova/adminclient.py
+++ b/nova/adminclient.py
@@ -20,6 +20,7 @@ Nova User API client library.
"""
import base64
+
import boto
from boto.ec2.regioninfo import RegionInfo
@@ -57,6 +58,7 @@ class UserInfo(object):
elif name == 'secretkey':
self.secretkey = str(value)
+
class UserRole(object):
"""
Information about a Nova user's role, as parsed through SAX.
@@ -79,6 +81,7 @@ class UserRole(object):
else:
setattr(self, name, str(value))
+
class ProjectInfo(object):
"""
Information about a Nova project, as parsed through SAX
@@ -114,12 +117,14 @@ class ProjectInfo(object):
else:
setattr(self, name, str(value))
+
class ProjectMember(object):
"""
Information about a Nova project member, as parsed through SAX.
Fields include:
memberId
"""
+
def __init__(self, connection=None):
self.connection = connection
self.memberId = None
@@ -135,6 +140,7 @@ class ProjectMember(object):
self.memberId = value
else:
setattr(self, name, str(value))
+
class HostInfo(object):
"""
@@ -163,6 +169,7 @@ class HostInfo(object):
def endElement(self, name, value, connection):
setattr(self, name, value)
+
class NovaAdminClient(object):
def __init__(self, clc_ip='127.0.0.1', region='nova', access_key='admin',
secret_key='admin', **kwargs):
diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py
index b420924af..bc744fa01 100644
--- a/nova/auth/fakeldap.py
+++ b/nova/auth/fakeldap.py
@@ -219,7 +219,6 @@ class FakeLDAP(object):
raise NO_SUCH_OBJECT()
return objects
-
@property
def __redis_prefix(self):
return 'ldap:'
diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py
index 453fa196c..6bf7fcd1e 100644
--- a/nova/auth/ldapdriver.py
+++ b/nova/auth/ldapdriver.py
@@ -30,6 +30,7 @@ import sys
from nova import exception
from nova import flags
+
FLAGS = flags.FLAGS
flags.DEFINE_string('ldap_url', 'ldap://localhost',
'Point this at your ldap server')
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index 4a813c861..c16eb0c3c 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -38,7 +38,6 @@ from nova.network import vpn
FLAGS = flags.FLAGS
-
flags.DEFINE_list('allowed_roles',
['cloudadmin', 'itsec', 'sysadmin', 'netadmin', 'developer'],
'Allowed roles for project')
@@ -53,7 +52,6 @@ flags.DEFINE_list('superuser_roles', ['cloudadmin'],
flags.DEFINE_list('global_roles', ['cloudadmin', 'itsec'],
'Roles that apply to all projects')
-
flags.DEFINE_string('credentials_template',
utils.abspath('auth/novarc.template'),
'Template for creating users rc file')
@@ -68,15 +66,14 @@ flags.DEFINE_string('credential_cert_file', 'cert.pem',
'Filename of certificate in credentials zip')
flags.DEFINE_string('credential_rc_file', 'novarc',
'Filename of rc in credentials zip')
-
flags.DEFINE_string('credential_cert_subject',
'/C=US/ST=California/L=MountainView/O=AnsoLabs/'
'OU=NovaDev/CN=%s-%s',
'Subject for certificate for users')
-
flags.DEFINE_string('auth_driver', 'nova.auth.ldapdriver.FakeLdapDriver',
'Driver that auth manager uses')
+
class AuthBase(object):
"""Base class for objects relating to auth
@@ -84,6 +81,7 @@ class AuthBase(object):
an id member. They may optionally contain methods that delegate to
AuthManager, but should not implement logic themselves.
"""
+
@classmethod
def safe_id(cls, obj):
"""Safe get object id
@@ -101,6 +99,7 @@ class AuthBase(object):
class User(AuthBase):
"""Object representing a user"""
+
def __init__(self, id, name, access, secret, admin):
AuthBase.__init__(self)
self.id = id
@@ -162,6 +161,7 @@ class KeyPair(AuthBase):
Even though this object is named KeyPair, only the public key and
fingerprint is stored. The user's private key is not saved.
"""
+
def __init__(self, id, name, owner_id, public_key, fingerprint):
AuthBase.__init__(self)
self.id = id
@@ -180,6 +180,7 @@ class KeyPair(AuthBase):
class Project(AuthBase):
"""Represents a Project returned from the datastore"""
+
def __init__(self, id, name, project_manager_id, description, member_ids):
AuthBase.__init__(self)
self.id = id
@@ -233,7 +234,6 @@ class Project(AuthBase):
self.member_ids)
-
class AuthManager(object):
"""Manager Singleton for dealing with Users, Projects, and Keypairs
@@ -245,7 +245,9 @@ class AuthManager(object):
AuthManager also manages associated data related to Auth objects that
need to be more accessible, such as vpn ips and ports.
"""
+
_instance = None
+
def __new__(cls, *args, **kwargs):
"""Returns the AuthManager singleton"""
if not cls._instance:
diff --git a/nova/auth/rbac.py b/nova/auth/rbac.py
index 7fab9419f..1446e4e27 100644
--- a/nova/auth/rbac.py
+++ b/nova/auth/rbac.py
@@ -32,6 +32,7 @@ def allow(*roles):
return wrapped_f
return wrap
+
def deny(*roles):
def wrap(f):
def wrapped_f(self, context, *args, **kwargs):
@@ -44,6 +45,7 @@ def deny(*roles):
return wrapped_f
return wrap
+
def __matches_role(context, role):
if role == 'all':
return True
diff --git a/nova/auth/signer.py b/nova/auth/signer.py
index 634f22f0d..8334806d2 100644
--- a/nova/auth/signer.py
+++ b/nova/auth/signer.py
@@ -48,11 +48,15 @@ import hashlib
import hmac
import logging
import urllib
-import boto # NOTE(vish): for new boto
-import boto.utils # NOTE(vish): for old boto
+
+# NOTE(vish): for new boto
+import boto
+# NOTE(vish): for old boto
+import boto.utils
from nova.exception import Error
+
class Signer(object):
""" hacked up code from boto/connection.py """
@@ -77,7 +81,6 @@ class Signer(object):
return self._calc_signature_2(params, verb, server_string, path)
raise Error('Unknown Signature Version: %s' % self.SignatureVersion)
-
def _get_utf8_value(self, value):
if not isinstance(value, str) and not isinstance(value, unicode):
value = str(value)
@@ -133,5 +136,6 @@ class Signer(object):
logging.debug('base64 encoded digest: %s' % b64)
return b64
+
if __name__ == '__main__':
print Signer('foo').generate({"SignatureMethod": 'HmacSHA256', 'SignatureVersion': '2'}, "get", "server", "/foo")
diff --git a/nova/cloudpipe/api.py b/nova/cloudpipe/api.py
index 0bffe9aa3..56aa89834 100644
--- a/nova/cloudpipe/api.py
+++ b/nova/cloudpipe/api.py
@@ -21,9 +21,10 @@ Tornado REST API Request Handlers for CloudPipe
"""
import logging
-import tornado.web
import urllib
+import tornado.web
+
from nova import crypto
from nova.auth import manager
diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py
index 5b0ed3471..2867bcb21 100644
--- a/nova/cloudpipe/pipelib.py
+++ b/nova/cloudpipe/pipelib.py
@@ -36,11 +36,11 @@ from nova.endpoint import api
FLAGS = flags.FLAGS
-
flags.DEFINE_string('boot_script_template',
utils.abspath('cloudpipe/bootscript.sh'),
'Template for script to run on cloudpipe instance boot')
+
class CloudPipe(object):
def __init__(self, cloud_controller):
self.controller = cloud_controller
diff --git a/nova/compute/disk.py b/nova/compute/disk.py
index 1ffcca685..c340c5a79 100644
--- a/nova/compute/disk.py
+++ b/nova/compute/disk.py
@@ -24,6 +24,7 @@ Includes injection of SSH PGP keys into authorized_keys file.
import logging
import os
import tempfile
+
from twisted.internet import defer
from nova import exception
@@ -84,6 +85,7 @@ def partition(infile, outfile, local_bytes=0, local_type='ext2', execute=None):
yield execute('dd if=%s of=%s bs=%d seek=%d conv=notrunc,fsync'
% (infile, outfile, sector_size, primary_first))
+
@defer.inlineCallbacks
def inject_data(image, key=None, net=None, partition=None, execute=None):
"""Injects a ssh key and optionally net data into a disk image.
@@ -137,6 +139,7 @@ def inject_data(image, key=None, net=None, partition=None, execute=None):
# remove loopback
yield execute('sudo losetup -d %s' % device)
+
@defer.inlineCallbacks
def _inject_key_into_fs(key, fs, execute=None):
sshdir = os.path.join(os.path.join(fs, 'root'), '.ssh')
@@ -146,6 +149,7 @@ def _inject_key_into_fs(key, fs, execute=None):
keyfile = os.path.join(sshdir, 'authorized_keys')
yield execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n')
+
@defer.inlineCallbacks
def _inject_net_into_fs(net, fs, execute=None):
netfile = os.path.join(os.path.join(os.path.join(
diff --git a/nova/compute/model.py b/nova/compute/model.py
index 54d816a9c..baa41c3e0 100644
--- a/nova/compute/model.py
+++ b/nova/compute/model.py
@@ -231,6 +231,7 @@ class Daemon():
for x in cls.associated_to("host", hostname):
yield x
+
class SessionToken():
"""This is a short-lived auth token that is passed through web requests"""
diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py
index 19e1a483d..268864900 100644
--- a/nova/compute/monitor.py
+++ b/nova/compute/monitor.py
@@ -24,14 +24,15 @@ Instance Monitoring:
in the object store.
"""
-import boto
-import boto.s3
import datetime
import logging
import os
-import rrdtool
import sys
import time
+
+import boto
+import boto.s3
+import rrdtool
from twisted.internet import defer
from twisted.internet import task
from twisted.application import service
@@ -41,13 +42,12 @@ from nova.virt import connection as virt_connection
FLAGS = flags.FLAGS
-flags.DEFINE_integer(
- 'monitoring_instances_delay', 5, 'Sleep time between updates')
-flags.DEFINE_integer(
- 'monitoring_instances_step', 300, 'Interval of RRD updates')
-flags.DEFINE_string(
- 'monitoring_rrd_path', '/var/nova/monitor/instances',
- 'Location of RRD files')
+flags.DEFINE_integer('monitoring_instances_delay', 5,
+ 'Sleep time between updates')
+flags.DEFINE_integer('monitoring_instances_step', 300,
+ 'Interval of RRD updates')
+flags.DEFINE_string('monitoring_rrd_path', '/var/nova/monitor/instances',
+ 'Location of RRD files')
RRD_VALUES = {
@@ -61,7 +61,7 @@ RRD_VALUES = {
'RRA:MAX:0.5:6:800',
'RRA:MAX:0.5:24:800',
'RRA:MAX:0.5:288:800',
- ],
+ ],
'net': [
'DS:rx:COUNTER:600:0:1250000',
'DS:tx:COUNTER:600:0:1250000',
@@ -73,7 +73,7 @@ RRD_VALUES = {
'RRA:MAX:0.5:6:800',
'RRA:MAX:0.5:24:800',
'RRA:MAX:0.5:288:800',
- ],
+ ],
'disk': [
'DS:rd:COUNTER:600:U:U',
'DS:wr:COUNTER:600:U:U',
@@ -85,12 +85,13 @@ RRD_VALUES = {
'RRA:MAX:0.5:6:800',
'RRA:MAX:0.5:24:800',
'RRA:MAX:0.5:444:800',
- ]
-}
+ ]
+ }
utcnow = datetime.datetime.utcnow
+
def update_rrd(instance, name, data):
"""
Updates the specified RRD file.
@@ -106,6 +107,7 @@ def update_rrd(instance, name, data):
'%d:%s' % (timestamp, data)
)
+
def init_rrd(instance, name):
"""
Initializes the specified RRD file.
@@ -124,6 +126,7 @@ def init_rrd(instance, name):
'--start', '0',
*RRD_VALUES[name]
)
+
def graph_cpu(instance, duration):
"""
@@ -148,6 +151,7 @@ def graph_cpu(instance, duration):
store_graph(instance.instance_id, filename)
+
def graph_net(instance, duration):
"""
Creates a graph of network usage for the specified instance and duration.
@@ -174,6 +178,7 @@ def graph_net(instance, duration):
)
store_graph(instance.instance_id, filename)
+
def graph_disk(instance, duration):
"""
@@ -202,6 +207,7 @@ def graph_disk(instance, duration):
store_graph(instance.instance_id, filename)
+
def store_graph(instance_id, filename):
"""
Transmits the specified graph file to internal object store on cloud
@@ -387,6 +393,7 @@ class InstanceMonitor(object, service.Service):
"""
Monitors the running instances of the current machine.
"""
+
def __init__(self):
"""
Initialize the monitoring loop.
diff --git a/nova/compute/service.py b/nova/compute/service.py
index b80ef3740..13507a1bb 100644
--- a/nova/compute/service.py
+++ b/nova/compute/service.py
@@ -29,6 +29,7 @@ import json
import logging
import os
import sys
+
from twisted.internet import defer
from twisted.internet import task
diff --git a/nova/crypto.py b/nova/crypto.py
index cc84f5e45..b05548ea1 100644
--- a/nova/crypto.py
+++ b/nova/crypto.py
@@ -24,7 +24,6 @@ SSH keypairs and x509 certificates.
import base64
import hashlib
import logging
-import M2Crypto
import os
import shutil
import struct
@@ -32,6 +31,8 @@ import tempfile
import time
import utils
+import M2Crypto
+
from nova import exception
from nova import flags
@@ -42,11 +43,13 @@ flags.DEFINE_string('keys_path', utils.abspath('../keys'), 'Where we keep our ke
flags.DEFINE_string('ca_path', utils.abspath('../CA'), 'Where we keep our root CA')
flags.DEFINE_boolean('use_intermediate_ca', False, 'Should we use intermediate CAs for each project?')
+
def ca_path(project_id):
if project_id:
return "%s/INTER/%s/cacert.pem" % (FLAGS.ca_path, project_id)
return "%s/cacert.pem" % (FLAGS.ca_path)
+
def fetch_ca(project_id=None, chain=True):
if not FLAGS.use_intermediate_ca:
project_id = None
@@ -60,6 +63,7 @@ def fetch_ca(project_id=None, chain=True):
buffer += cafile.read()
return buffer
+
def generate_key_pair(bits=1024):
# what is the magic 65537?
@@ -109,6 +113,7 @@ def generate_x509_cert(subject, bits=1024):
shutil.rmtree(tmpdir)
return (private_key, csr)
+
def sign_csr(csr_text, intermediate=None):
if not FLAGS.use_intermediate_ca:
intermediate = None
@@ -122,6 +127,7 @@ def sign_csr(csr_text, intermediate=None):
os.chdir(start)
return _sign_csr(csr_text, user_ca)
+
def _sign_csr(csr_text, ca_folder):
tmpfolder = tempfile.mkdtemp()
csrfile = open("%s/inbound.csr" % (tmpfolder), "w")
diff --git a/nova/endpoint/__init__.py b/nova/endpoint/__init__.py
index 753685149..e69de29bb 100644
--- a/nova/endpoint/__init__.py
+++ b/nova/endpoint/__init__.py
@@ -1,32 +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.
-
-"""
-:mod:`nova.endpoint` -- Main NOVA Api endpoints
-=====================================================
-
-.. automodule:: nova.endpoint
- :platform: Unix
- :synopsis: REST APIs for all nova functions
-.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
-.. moduleauthor:: Devin Carlen <devin.carlen@gmail.com>
-.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
-.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
-.. moduleauthor:: Manish Singh <yosh@gimp.org>
-.. moduleauthor:: Andy Smith <andy@anarkystic.com>
-"""
diff --git a/nova/endpoint/admin.py b/nova/endpoint/admin.py
index 4f4824fca..d6f622755 100644
--- a/nova/endpoint/admin.py
+++ b/nova/endpoint/admin.py
@@ -37,6 +37,7 @@ def user_dict(user, base64_file=None):
else:
return {}
+
def project_dict(project):
"""Convert the project object to a result dict"""
if project:
@@ -47,6 +48,7 @@ def project_dict(project):
else:
return {}
+
def host_dict(host):
"""Convert a host model object to a result dict"""
if host:
@@ -54,6 +56,7 @@ def host_dict(host):
else:
return {}
+
def admin_only(target):
"""Decorator for admin-only API calls"""
def wrapper(*args, **kwargs):
@@ -66,6 +69,7 @@ def admin_only(target):
return wrapper
+
class AdminController(object):
"""
API Controller for users, hosts, nodes, and workers.
diff --git a/nova/endpoint/api.py b/nova/endpoint/api.py
index 78a18b9ea..40be00bb7 100755
--- a/nova/endpoint/api.py
+++ b/nova/endpoint/api.py
@@ -25,12 +25,13 @@ import logging
import multiprocessing
import random
import re
-import tornado.web
-from twisted.internet import defer
import urllib
# TODO(termie): replace minidom with etree
from xml.dom import minidom
+import tornado.web
+from twisted.internet import defer
+
from nova import crypto
from nova import exception
from nova import flags
@@ -43,6 +44,7 @@ from nova.endpoint import cloud
FLAGS = flags.FLAGS
flags.DEFINE_integer('cc_port', 8773, 'cloud controller port')
+
_log = logging.getLogger("api")
_log.setLevel(logging.DEBUG)
@@ -227,6 +229,7 @@ class MetadataRequestHandler(tornado.web.RequestHandler):
self.print_data(data)
self.finish()
+
class APIRequestHandler(tornado.web.RequestHandler):
def get(self, controller_name):
self.execute(controller_name)
diff --git a/nova/endpoint/aws/__init__.py b/nova/endpoint/aws/__init__.py
new file mode 100644
index 000000000..55cbb8fd3
--- /dev/null
+++ b/nova/endpoint/aws/__init__.py
@@ -0,0 +1,22 @@
+import routes
+import webob.dec
+
+from nova import wsgi
+
+# TODO(gundlach): temp
+class API(wsgi.Router):
+ """WSGI entry point for all AWS API requests."""
+
+ def __init__(self):
+ mapper = routes.Mapper()
+
+ mapper.connect(None, "{all:.*}", controller=self.dummy)
+
+ super(API, self).__init__(mapper)
+
+ @webob.dec.wsgify
+ def dummy(self, req):
+ #TODO(gundlach)
+ msg = "dummy response -- please hook up __init__() to cloud.py instead"
+ return repr({ 'dummy': msg,
+ 'kwargs': repr(req.environ['wsgiorg.routing_args'][1]) })
diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py
index 832103579..3bc03e0b1 100644
--- a/nova/endpoint/cloud.py
+++ b/nova/endpoint/cloud.py
@@ -26,6 +26,7 @@ import base64
import logging
import os
import time
+
from twisted.internet import defer
from nova import datastore
@@ -45,7 +46,6 @@ from nova.volume import service
FLAGS = flags.FLAGS
-
flags.DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on')
@@ -363,7 +363,6 @@ class CloudController(object):
'status': volume['attach_status'],
'volumeId': volume_id})
-
@rbac.allow('projectmanager', 'sysadmin')
def detach_volume(self, context, volume_id, **kwargs):
volume = self._get_volume(context, volume_id)
diff --git a/nova/endpoint/images.py b/nova/endpoint/images.py
index fe7cb5d11..2a88d66af 100644
--- a/nova/endpoint/images.py
+++ b/nova/endpoint/images.py
@@ -21,10 +21,11 @@ Proxy AMI-related calls from the cloud controller, to the running
objectstore daemon.
"""
-import boto.s3.connection
import json
import urllib
+import boto.s3.connection
+
from nova import flags
from nova import utils
from nova.auth import manager
@@ -32,6 +33,7 @@ from nova.auth import manager
FLAGS = flags.FLAGS
+
def modify(context, image_id, operation):
conn(context).make_request(
method='POST',
@@ -53,6 +55,7 @@ def register(context, image_location):
return image_id
+
def list(context, filter_list=[]):
""" return a list of all images that a user can see
@@ -68,6 +71,7 @@ def list(context, filter_list=[]):
return [i for i in result if i['imageId'] in filter_list]
return result
+
def deregister(context, image_id):
""" unregister an image """
conn(context).make_request(
@@ -75,6 +79,7 @@ def deregister(context, image_id):
bucket='_images',
query_args=qs({'image_id': image_id}))
+
def conn(context):
access = manager.AuthManager().get_access_key(context.user,
context.project)
diff --git a/nova/endpoint/newapi.py b/nova/endpoint/newapi.py
new file mode 100644
index 000000000..9aae933af
--- /dev/null
+++ b/nova/endpoint/newapi.py
@@ -0,0 +1,51 @@
+# 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.
+
+"""
+:mod:`nova.endpoint` -- Main NOVA Api endpoints
+=====================================================
+
+.. automodule:: nova.endpoint
+ :platform: Unix
+ :synopsis: REST APIs for all nova functions
+.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
+.. moduleauthor:: Devin Carlen <devin.carlen@gmail.com>
+.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
+.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
+.. moduleauthor:: Manish Singh <yosh@gimp.org>
+.. moduleauthor:: Andy Smith <andy@anarkystic.com>
+"""
+
+from nova import wsgi
+import routes
+from nova.endpoint import rackspace
+from nova.endpoint import aws
+
+class APIVersionRouter(wsgi.Router):
+ """Routes top-level requests to the appropriate API."""
+
+ def __init__(self):
+ mapper = routes.Mapper()
+
+ rsapi = rackspace.API()
+ mapper.connect(None, "/v1.0/{path_info:.*}", controller=rsapi)
+
+ mapper.connect(None, "/ec2/{path_info:.*}", controller=aws.API())
+
+ super(APIVersionRouter, self).__init__(mapper)
+
diff --git a/nova/endpoint/rackspace.py b/nova/endpoint/rackspace.py
deleted file mode 100644
index 75b828e91..000000000
--- a/nova/endpoint/rackspace.py
+++ /dev/null
@@ -1,183 +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.
-
-"""
-Rackspace API Endpoint
-"""
-
-import json
-import time
-
-import webob.dec
-import webob.exc
-
-from nova import flags
-from nova import rpc
-from nova import utils
-from nova import wsgi
-from nova.auth import manager
-from nova.compute import model as compute
-from nova.network import model as network
-
-
-FLAGS = flags.FLAGS
-flags.DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on')
-
-
-class API(wsgi.Middleware):
- """Entry point for all requests."""
-
- def __init__(self):
- super(API, self).__init__(Router(webob.exc.HTTPNotFound()))
-
- def __call__(self, environ, start_response):
- context = {}
- if "HTTP_X_AUTH_TOKEN" in environ:
- context['user'] = manager.AuthManager().get_user_from_access_key(
- environ['HTTP_X_AUTH_TOKEN'])
- if context['user']:
- context['project'] = manager.AuthManager().get_project(
- context['user'].name)
- if "user" not in context:
- return webob.exc.HTTPForbidden()(environ, start_response)
- environ['nova.context'] = context
- return self.application(environ, start_response)
-
-
-class Router(wsgi.Router):
- """Route requests to the next WSGI application."""
-
- def _build_map(self):
- """Build routing map for authentication and cloud."""
- self._connect("/v1.0", controller=AuthenticationAPI())
- cloud = CloudServerAPI()
- self._connect("/servers", controller=cloud.launch_server,
- conditions={"method": ["POST"]})
- self._connect("/servers/{server_id}", controller=cloud.delete_server,
- conditions={'method': ["DELETE"]})
- self._connect("/servers", controller=cloud)
-
-
-class AuthenticationAPI(wsgi.Application):
- """Handle all authorization requests through WSGI applications."""
-
- @webob.dec.wsgify
- def __call__(self, req): # pylint: disable-msg=W0221
- # TODO(todd): make a actual session with a unique token
- # just pass the auth key back through for now
- res = webob.Response()
- res.status = '204 No Content'
- res.headers.add('X-Server-Management-Url', req.host_url)
- res.headers.add('X-Storage-Url', req.host_url)
- res.headers.add('X-CDN-Managment-Url', req.host_url)
- res.headers.add('X-Auth-Token', req.headers['X-Auth-Key'])
- return res
-
-
-class CloudServerAPI(wsgi.Application):
- """Handle all server requests through WSGI applications."""
-
- def __init__(self):
- super(CloudServerAPI, self).__init__()
- self.instdir = compute.InstanceDirectory()
- self.network = network.PublicNetworkController()
-
- @webob.dec.wsgify
- def __call__(self, req): # pylint: disable-msg=W0221
- value = {"servers": []}
- for inst in self.instdir.all:
- value["servers"].append(self.instance_details(inst))
- return json.dumps(value)
-
- def instance_details(self, inst): # pylint: disable-msg=R0201
- """Build the data structure to represent details for an instance."""
- return {
- "id": inst.get("instance_id", None),
- "imageId": inst.get("image_id", None),
- "flavorId": inst.get("instacne_type", None),
- "hostId": inst.get("node_name", None),
- "status": inst.get("state", "pending"),
- "addresses": {
- "public": [network.get_public_ip_for_instance(
- inst.get("instance_id", None))],
- "private": [inst.get("private_dns_name", None)]},
-
- # implemented only by Rackspace, not AWS
- "name": inst.get("name", "Not-Specified"),
-
- # not supported
- "progress": "Not-Supported",
- "metadata": {
- "Server Label": "Not-Supported",
- "Image Version": "Not-Supported"}}
-
- @webob.dec.wsgify
- def launch_server(self, req):
- """Launch a new instance."""
- data = json.loads(req.body)
- inst = self.build_server_instance(data, req.environ['nova.context'])
- rpc.cast(
- FLAGS.compute_topic, {
- "method": "run_instance",
- "args": {"instance_id": inst.instance_id}})
-
- return json.dumps({"server": self.instance_details(inst)})
-
- def build_server_instance(self, env, context):
- """Build instance data structure and save it to the data store."""
- reservation = utils.generate_uid('r')
- ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
- inst = self.instdir.new()
- inst['name'] = env['server']['name']
- inst['image_id'] = env['server']['imageId']
- inst['instance_type'] = env['server']['flavorId']
- inst['user_id'] = context['user'].id
- inst['project_id'] = context['project'].id
- inst['reservation_id'] = reservation
- inst['launch_time'] = ltime
- inst['mac_address'] = utils.generate_mac()
- address = self.network.allocate_ip(
- inst['user_id'],
- inst['project_id'],
- mac=inst['mac_address'])
- inst['private_dns_name'] = str(address)
- inst['bridge_name'] = network.BridgedNetwork.get_network_for_project(
- inst['user_id'],
- inst['project_id'],
- 'default')['bridge_name']
- # key_data, key_name, ami_launch_index
- # TODO(todd): key data or root password
- inst.save()
- return inst
-
- @webob.dec.wsgify
- @wsgi.route_args
- def delete_server(self, req, route_args): # pylint: disable-msg=R0201
- """Delete an instance."""
- owner_hostname = None
- instance = compute.Instance.lookup(route_args['server_id'])
- if instance:
- owner_hostname = instance["node_name"]
- if not owner_hostname:
- return webob.exc.HTTPNotFound("Did not find image, or it was "
- "not in a running state.")
- rpc_transport = "%s:%s" % (FLAGS.compute_topic, owner_hostname)
- rpc.cast(rpc_transport,
- {"method": "reboot_instance",
- "args": {"instance_id": route_args['server_id']}})
- req.status = "202 Accepted"
diff --git a/nova/endpoint/rackspace/__init__.py b/nova/endpoint/rackspace/__init__.py
new file mode 100644
index 000000000..ac53ee10b
--- /dev/null
+++ b/nova/endpoint/rackspace/__init__.py
@@ -0,0 +1,83 @@
+# 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.
+
+"""
+Rackspace API Endpoint
+"""
+
+import json
+import time
+
+import webob.dec
+import webob.exc
+import routes
+
+from nova import flags
+from nova import wsgi
+from nova.auth import manager
+from nova.endpoint.rackspace import controllers
+
+
+class API(wsgi.Middleware):
+ """WSGI entry point for all Rackspace API requests."""
+
+ def __init__(self):
+ app = AuthMiddleware(APIRouter())
+ super(API, self).__init__(app)
+
+
+class AuthMiddleware(wsgi.Middleware):
+ """Authorize the rackspace API request or return an HTTP Forbidden."""
+
+ #TODO(gundlach): isn't this the old Nova API's auth? Should it be replaced
+ #with correct RS API auth?
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ context = {}
+ if "HTTP_X_AUTH_TOKEN" in req.environ:
+ context['user'] = manager.AuthManager().get_user_from_access_key(
+ req.environ['HTTP_X_AUTH_TOKEN'])
+ if context['user']:
+ context['project'] = manager.AuthManager().get_project(
+ context['user'].name)
+ if "user" not in context:
+ return webob.exc.HTTPForbidden()
+ req.environ['nova.context'] = context
+ return self.application
+
+
+class APIRouter(wsgi.Router):
+ """
+ Routes requests on the Rackspace API to the appropriate controller
+ and method.
+ """
+
+ def __init__(self):
+ mapper = routes.Mapper()
+
+ mapper.resource("server", "servers",
+ controller=controllers.ServersController())
+ mapper.resource("image", "images",
+ controller=controllers.ImagesController())
+ mapper.resource("flavor", "flavors",
+ controller=controllers.FlavorsController())
+ mapper.resource("sharedipgroup", "sharedipgroups",
+ controller=controllers.SharedIpGroupsController())
+
+ super(APIRouter, self).__init__(mapper)
diff --git a/nova/endpoint/rackspace/controllers/__init__.py b/nova/endpoint/rackspace/controllers/__init__.py
new file mode 100644
index 000000000..052b6f365
--- /dev/null
+++ b/nova/endpoint/rackspace/controllers/__init__.py
@@ -0,0 +1,5 @@
+from nova.endpoint.rackspace.controllers.images import ImagesController
+from nova.endpoint.rackspace.controllers.flavors import FlavorsController
+from nova.endpoint.rackspace.controllers.servers import ServersController
+from nova.endpoint.rackspace.controllers.sharedipgroups import \
+ SharedIpGroupsController
diff --git a/nova/endpoint/rackspace/controllers/base.py b/nova/endpoint/rackspace/controllers/base.py
new file mode 100644
index 000000000..8cd44f62e
--- /dev/null
+++ b/nova/endpoint/rackspace/controllers/base.py
@@ -0,0 +1,9 @@
+from nova import wsgi
+
+class BaseController(wsgi.Controller):
+ @classmethod
+ def render(cls, instance):
+ if isinstance(instance, list):
+ return { cls.entity_name : cls.render(instance) }
+ else:
+ return { "TODO": "TODO" }
diff --git a/nova/endpoint/rackspace/controllers/flavors.py b/nova/endpoint/rackspace/controllers/flavors.py
new file mode 100644
index 000000000..f256cc852
--- /dev/null
+++ b/nova/endpoint/rackspace/controllers/flavors.py
@@ -0,0 +1 @@
+class FlavorsController(object): pass
diff --git a/nova/endpoint/rackspace/controllers/images.py b/nova/endpoint/rackspace/controllers/images.py
new file mode 100644
index 000000000..ae2a08849
--- /dev/null
+++ b/nova/endpoint/rackspace/controllers/images.py
@@ -0,0 +1 @@
+class ImagesController(object): pass
diff --git a/nova/endpoint/rackspace/controllers/servers.py b/nova/endpoint/rackspace/controllers/servers.py
new file mode 100644
index 000000000..2f8e662d6
--- /dev/null
+++ b/nova/endpoint/rackspace/controllers/servers.py
@@ -0,0 +1,63 @@
+from nova import rpc
+from nova.compute import model as compute
+from nova.endpoint.rackspace.controllers.base import BaseController
+
+class ServersController(BaseController):
+ entity_name = 'servers'
+
+ def index(self, **kwargs):
+ return [instance_details(inst) for inst in compute.InstanceDirectory().all]
+
+ def show(self, **kwargs):
+ instance_id = kwargs['id']
+ return compute.InstanceDirectory().get(instance_id)
+
+ def delete(self, **kwargs):
+ instance_id = kwargs['id']
+ instance = compute.InstanceDirectory().get(instance_id)
+ if not instance:
+ raise ServerNotFound("The requested server was not found")
+ instance.destroy()
+ return True
+
+ def create(self, **kwargs):
+ inst = self.build_server_instance(kwargs['server'])
+ rpc.cast(
+ FLAGS.compute_topic, {
+ "method": "run_instance",
+ "args": {"instance_id": inst.instance_id}})
+
+ def update(self, **kwargs):
+ instance_id = kwargs['id']
+ instance = compute.InstanceDirectory().get(instance_id)
+ if not instance:
+ raise ServerNotFound("The requested server was not found")
+ instance.update(kwargs['server'])
+ instance.save()
+
+ def build_server_instance(self, env):
+ """Build instance data structure and save it to the data store."""
+ reservation = utils.generate_uid('r')
+ ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
+ inst = self.instdir.new()
+ inst['name'] = env['server']['name']
+ inst['image_id'] = env['server']['imageId']
+ inst['instance_type'] = env['server']['flavorId']
+ inst['user_id'] = env['user']['id']
+ inst['project_id'] = env['project']['id']
+ inst['reservation_id'] = reservation
+ inst['launch_time'] = ltime
+ inst['mac_address'] = utils.generate_mac()
+ address = self.network.allocate_ip(
+ inst['user_id'],
+ inst['project_id'],
+ mac=inst['mac_address'])
+ inst['private_dns_name'] = str(address)
+ inst['bridge_name'] = network.BridgedNetwork.get_network_for_project(
+ inst['user_id'],
+ inst['project_id'],
+ 'default')['bridge_name']
+ # key_data, key_name, ami_launch_index
+ # TODO(todd): key data or root password
+ inst.save()
+ return inst
diff --git a/nova/endpoint/rackspace/controllers/sharedipgroups.py b/nova/endpoint/rackspace/controllers/sharedipgroups.py
new file mode 100644
index 000000000..9d346d623
--- /dev/null
+++ b/nova/endpoint/rackspace/controllers/sharedipgroups.py
@@ -0,0 +1 @@
+class SharedIpGroupsController(object): pass
diff --git a/nova/exception.py b/nova/exception.py
index 52497a19e..29bcb17f8 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -25,31 +25,39 @@ import logging
import sys
import traceback
+
class Error(Exception):
def __init__(self, message=None):
super(Error, self).__init__(message)
+
class ApiError(Error):
def __init__(self, message='Unknown', code='Unknown'):
self.message = message
self.code = code
super(ApiError, self).__init__('%s: %s'% (code, message))
+
class NotFound(Error):
pass
+
class Duplicate(Error):
pass
+
class NotAuthorized(Error):
pass
+
class NotEmpty(Error):
pass
+
class Invalid(Error):
pass
+
def wrap_exception(f):
def _wrap(*args, **kw):
try:
diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py
index 689194513..068025249 100644
--- a/nova/fakerabbit.py
+++ b/nova/fakerabbit.py
@@ -16,12 +16,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-""" Based a bit on the carrot.backeds.queue backend... but a lot better """
+"""Based a bit on the carrot.backeds.queue backend... but a lot better."""
-from carrot.backends import base
import logging
import Queue as queue
+from carrot.backends import base
+
class Message(base.BaseMessage):
pass
diff --git a/nova/flags.py b/nova/flags.py
index b3bdd088f..e3feb252d 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -175,29 +175,25 @@ DEFINE_string('network_topic', 'network', 'the topic network nodes listen on')
DEFINE_bool('verbose', False, 'show debug output')
DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit')
-DEFINE_bool('fake_network', False, 'should we use fake network devices and addresses')
+DEFINE_bool('fake_network', False,
+ 'should we use fake network devices and addresses')
DEFINE_string('rabbit_host', 'localhost', 'rabbit host')
DEFINE_integer('rabbit_port', 5672, 'rabbit port')
DEFINE_string('rabbit_userid', 'guest', 'rabbit userid')
DEFINE_string('rabbit_password', 'guest', 'rabbit password')
DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host')
DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to')
-DEFINE_string('ec2_url',
- 'http://127.0.0.1:8773/services/Cloud',
- 'Url to ec2 api server')
-
-DEFINE_string('default_image',
- 'ami-11111',
- 'default image to use, testing only')
-DEFINE_string('default_kernel',
- 'aki-11111',
- 'default kernel to use, testing only')
-DEFINE_string('default_ramdisk',
- 'ari-11111',
- 'default ramdisk to use, testing only')
-DEFINE_string('default_instance_type',
- 'm1.small',
- 'default instance type to use, testing only')
+DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud',
+ 'Url to ec2 api server')
+
+DEFINE_string('default_image', 'ami-11111',
+ 'default image to use, testing only')
+DEFINE_string('default_kernel', 'aki-11111',
+ 'default kernel to use, testing only')
+DEFINE_string('default_ramdisk', 'ari-11111',
+ 'default ramdisk to use, testing only')
+DEFINE_string('default_instance_type', 'm1.small',
+ 'default instance type to use, testing only')
DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', 'AMI for cloudpipe vpn server')
DEFINE_string('vpn_key_suffix',
@@ -207,10 +203,8 @@ DEFINE_string('vpn_key_suffix',
DEFINE_integer('auth_token_ttl', 3600, 'Seconds for auth tokens to linger')
# UNUSED
-DEFINE_string('node_availability_zone',
- 'nova',
- 'availability zone of this node')
-DEFINE_string('node_name',
- socket.gethostname(),
- 'name of this node')
+DEFINE_string('node_availability_zone', 'nova',
+ 'availability zone of this node')
+DEFINE_string('node_name', socket.gethostname(),
+ 'name of this node')
diff --git a/nova/network/exception.py b/nova/network/exception.py
index 8d7aa1498..2a3f5ec14 100644
--- a/nova/network/exception.py
+++ b/nova/network/exception.py
@@ -20,29 +20,29 @@
Exceptions for network errors.
"""
-from nova.exception import Error
+from nova import exception
-class NoMoreAddresses(Error):
+class NoMoreAddresses(exception.Error):
"""No More Addresses are available in the network"""
pass
-class AddressNotAllocated(Error):
+class AddressNotAllocated(exception.Error):
"""The specified address has not been allocated"""
pass
-class AddressAlreadyAssociated(Error):
+class AddressAlreadyAssociated(exception.Error):
"""The specified address has already been associated"""
pass
-class AddressNotAssociated(Error):
+class AddressNotAssociated(exception.Error):
"""The specified address is not associated"""
pass
-class NotValidNetworkSize(Error):
+class NotValidNetworkSize(exception.Error):
"""The network size is not valid"""
pass
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 73b9500d2..48d71f11e 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -18,17 +18,17 @@ Implements vlans, bridges, and iptables rules using linux utilities.
"""
import logging
-import signal
import os
+import signal
-# todo(ja): does the definition of network_path belong here?
+# TODO(ja): does the definition of network_path belong here?
from nova import flags
from nova import models
from nova import utils
-FLAGS = flags.FLAGS
+FLAGS = flags.FLAGS
flags.DEFINE_string('dhcpbridge_flagfile',
'/etc/nova/nova-dhcpbridge.conf',
'location of flagfile for dhcpbridge')
diff --git a/nova/network/service.py b/nova/network/service.py
index 2b931f342..8ddc4bc84 100644
--- a/nova/network/service.py
+++ b/nova/network/service.py
@@ -25,17 +25,17 @@ import logging
import IPy
from sqlalchemy.orm import exc
+from nova import exception
from nova import flags
from nova import models
from nova import service
from nova import utils
from nova.auth import manager
-from nova.exception import NotFound
-from nova.network import exception
+from nova.network import exception as network_exception
from nova.network import linux_net
-FLAGS = flags.FLAGS
+FLAGS = flags.FLAGS
flags.DEFINE_string('network_type',
'flat',
'Service Class for Networking')
@@ -45,15 +45,15 @@ flags.DEFINE_list('flat_network_ips',
['192.168.0.2', '192.168.0.3', '192.168.0.4'],
'Available ips for simple network')
flags.DEFINE_string('flat_network_network', '192.168.0.0',
- 'Network for simple network')
+ 'Network for simple network')
flags.DEFINE_string('flat_network_netmask', '255.255.255.0',
- 'Netmask for simple network')
+ 'Netmask for simple network')
flags.DEFINE_string('flat_network_gateway', '192.168.0.1',
- 'Broadcast for simple network')
+ 'Broadcast for simple network')
flags.DEFINE_string('flat_network_broadcast', '192.168.0.255',
- 'Broadcast for simple network')
+ 'Broadcast for simple network')
flags.DEFINE_string('flat_network_dns', '8.8.4.4',
- 'Dns for simple network')
+ 'Dns for simple network')
flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks')
flags.DEFINE_integer('vlan_end', 4093, 'Last VLAN for private networks')
@@ -76,7 +76,7 @@ def type_to_class(network_type):
return FlatNetworkService
elif network_type == 'vlan':
return VlanNetworkService
- raise NotFound("Couldn't find %s network type" % network_type)
+ raise exception.NotFound("Couldn't find %s network type" % network_type)
def setup_compute_network(project_id):
@@ -129,7 +129,7 @@ class BaseNetworkService(service.Service):
try:
fixed_ip = query.first()
except exc.NoResultFound:
- raise exception.NoMoreAddresses()
+ raise network_exception.NoMoreAddresses()
# FIXME will this set backreference?
fixed_ip.instance_id = instance_id
fixed_ip.allocated = True
@@ -168,7 +168,7 @@ class BaseNetworkService(service.Service):
try:
elastic_ip = query.first()
except exc.NoResultFound:
- raise exception.NoMoreAddresses()
+ raise network_exception.NoMoreAddresses()
elastic_ip.project_id = project_id
session.add(elastic_ip)
try:
@@ -233,7 +233,7 @@ class VlanNetworkService(BaseNetworkService):
if is_vpn:
fixed_ip = models.FixedIp.find_by_ip_str(network.vpn_private_ip_str)
if fixed_ip.allocated:
- raise exception.AddressAlreadyAllocated()
+ raise network_exception.AddressAlreadyAllocated()
# FIXME will this set backreference?
fixed_ip.instance_id = instance_id
fixed_ip.allocated = True
@@ -263,7 +263,7 @@ class VlanNetworkService(BaseNetworkService):
"""Called by bridge when ip is leased"""
fixed_ip = models.FixedIp.find_by_ip_str(fixed_ip_str)
if not fixed_ip.allocated:
- raise exception.AddressNotAllocated(fixed_ip_str)
+ raise network_exception.AddressNotAllocated(fixed_ip_str)
logging.debug("Leasing IP %s", fixed_ip_str)
fixed_ip.leased = True
fixed_ip.save()
@@ -325,7 +325,7 @@ class VlanNetworkService(BaseNetworkService):
try:
network_index = query.first()
except exc.NoResultFound:
- raise exception.NoMoreNetworks()
+ raise network_exception.NoMoreNetworks()
network_index.network = network
session.add(network_index)
try:
diff --git a/nova/objectstore/bucket.py b/nova/objectstore/bucket.py
index b42a96233..c2b412dd7 100644
--- a/nova/objectstore/bucket.py
+++ b/nova/objectstore/bucket.py
@@ -36,6 +36,7 @@ FLAGS = flags.FLAGS
flags.DEFINE_string('buckets_path', utils.abspath('../buckets'),
'path to s3 buckets')
+
class Bucket(object):
def __init__(self, name):
self.name = name
diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py
index dfe1918e3..035e342ca 100644
--- a/nova/objectstore/handler.py
+++ b/nova/objectstore/handler.py
@@ -38,17 +38,19 @@ S3 client with this module::
"""
import datetime
-import logging
import json
+import logging
import multiprocessing
import os
-from tornado import escape
import urllib
-from twisted.application import internet, service
-from twisted.web.resource import Resource
-from twisted.web import server, static, error
-
+from tornado import escape
+from twisted.application import internet
+from twisted.application import service
+from twisted.web import error
+from twisted.web import resource
+from twisted.web import server
+from twisted.web import static
from nova import exception
from nova import flags
@@ -60,6 +62,7 @@ from nova.objectstore import image
FLAGS = flags.FLAGS
+
def render_xml(request, value):
assert isinstance(value, dict) and len(value) == 1
request.setHeader("Content-Type", "application/xml; charset=UTF-8")
@@ -72,11 +75,13 @@ def render_xml(request, value):
request.write('</' + escape.utf8(name) + '>')
request.finish()
+
def finish(request, content=None):
if content:
request.write(content)
request.finish()
+
def _render_parts(value, write_cb):
if isinstance(value, basestring):
write_cb(escape.xhtml_escape(value))
@@ -95,11 +100,13 @@ def _render_parts(value, write_cb):
else:
raise Exception("Unknown S3 value type %r", value)
+
def get_argument(request, key, default_value):
if key in request.args:
return request.args[key][0]
return default_value
+
def get_context(request):
try:
# Authorization Header format: 'AWS <access>:<secret>'
@@ -120,13 +127,14 @@ def get_context(request):
logging.debug("Authentication Failure: %s" % ex)
raise exception.NotAuthorized
-class ErrorHandlingResource(Resource):
+
+class ErrorHandlingResource(resource.Resource):
"""Maps exceptions to 404 / 401 codes. Won't work for exceptions thrown after NOT_DONE_YET is returned."""
# TODO(unassigned) (calling-all-twisted-experts): This needs to be plugged in to the right place in twisted...
# This doesn't look like it's the right place (consider exceptions in getChild; or after NOT_DONE_YET is returned
def render(self, request):
try:
- return Resource.render(self, request)
+ return resource.Resource.render(self, request)
except exception.NotFound:
request.setResponseCode(404)
return ''
@@ -134,6 +142,7 @@ class ErrorHandlingResource(Resource):
request.setResponseCode(403)
return ''
+
class S3(ErrorHandlingResource):
"""Implementation of an S3-like storage server based on local files."""
def getChild(self, name, request):
@@ -154,9 +163,10 @@ class S3(ErrorHandlingResource):
}})
return server.NOT_DONE_YET
+
class BucketResource(ErrorHandlingResource):
def __init__(self, name):
- Resource.__init__(self)
+ resource.Resource.__init__(self)
self.name = name
def getChild(self, name, request):
@@ -206,7 +216,7 @@ class BucketResource(ErrorHandlingResource):
class ObjectResource(ErrorHandlingResource):
def __init__(self, bucket, name):
- Resource.__init__(self)
+ resource.Resource.__init__(self)
self.bucket = bucket
self.name = name
@@ -245,17 +255,19 @@ class ObjectResource(ErrorHandlingResource):
request.setResponseCode(204)
return ''
+
class ImageResource(ErrorHandlingResource):
isLeaf = True
def __init__(self, name):
- Resource.__init__(self)
+ resource.Resource.__init__(self)
self.img = image.Image(name)
def render_GET(self, request):
return static.File(self.img.image_path, defaultType='application/octet-stream').render_GET(request)
-class ImagesResource(Resource):
+
+class ImagesResource(resource.Resource):
def getChild(self, name, request):
if name == '':
return self
@@ -339,11 +351,13 @@ class ImagesResource(Resource):
request.setResponseCode(204)
return ''
+
def get_site():
root = S3()
site = server.Site(root)
return site
+
def get_application():
factory = get_site()
application = service.Application("objectstore")
diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py
index 861eb364f..fb780a0ec 100644
--- a/nova/objectstore/image.py
+++ b/nova/objectstore/image.py
@@ -42,6 +42,7 @@ FLAGS = flags.FLAGS
flags.DEFINE_string('images_path', utils.abspath('../images'),
'path to decrypted images')
+
class Image(object):
def __init__(self, image_id):
self.image_id = image_id
diff --git a/nova/objectstore/stored.py b/nova/objectstore/stored.py
index 81c047b22..9829194cb 100644
--- a/nova/objectstore/stored.py
+++ b/nova/objectstore/stored.py
@@ -23,7 +23,7 @@ Properties of an object stored within a bucket.
import os
import nova.crypto
-from nova.exception import NotFound, NotAuthorized
+from nova import exception
class Object(object):
@@ -33,7 +33,7 @@ class Object(object):
self.key = key
self.path = bucket._object_path(key)
if not os.path.isfile(self.path):
- raise NotFound
+ raise exception.NotFound
def __repr__(self):
return "<Object %s/%s>" % (self.bucket, self.key)
diff --git a/nova/process.py b/nova/process.py
index 2dc56372f..86f29e2c4 100644
--- a/nova/process.py
+++ b/nova/process.py
@@ -23,6 +23,7 @@ Process pool, still buggy right now.
import logging
import multiprocessing
import StringIO
+
from twisted.internet import defer
from twisted.internet import error
from twisted.internet import process
@@ -205,6 +206,7 @@ class ProcessPool(object):
self._pool.release()
return rv
+
class SharedPool(object):
_instance = None
def __init__(self):
@@ -213,5 +215,6 @@ class SharedPool(object):
def __getattr__(self, key):
return getattr(self._instance, key)
+
def simple_execute(cmd, **kwargs):
return SharedPool().simple_execute(cmd, **kwargs)
diff --git a/nova/rpc.py b/nova/rpc.py
index 4ac546c2a..824a66b5b 100644
--- a/nova/rpc.py
+++ b/nova/rpc.py
@@ -21,12 +21,13 @@ AMQP-based RPC. Queues have consumers and publishers.
No fan-out support yet.
"""
-from carrot import connection as carrot_connection
-from carrot import messaging
import json
import logging
import sys
import uuid
+
+from carrot import connection as carrot_connection
+from carrot import messaging
from twisted.internet import defer
from twisted.internet import task
diff --git a/nova/test.py b/nova/test.py
index 9cb826253..4eb5c1c53 100644
--- a/nova/test.py
+++ b/nova/test.py
@@ -22,11 +22,11 @@ Allows overriding of flags for use of fakes,
and some black magic for inline callbacks.
"""
-import mox
-import stubout
import sys
import time
+import mox
+import stubout
from tornado import ioloop
from twisted.internet import defer
from twisted.trial import unittest
@@ -97,7 +97,6 @@ class TrialTestCase(unittest.TestCase):
setattr(FLAGS, k, v)
-
class BaseTestCase(TrialTestCase):
# TODO(jaypipes): Can this be moved into the TrialTestCase class?
"""Base test case class for all unit tests."""
diff --git a/nova/utils.py b/nova/utils.py
index 63db080f1..e826f9b71 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -20,7 +20,7 @@
System-level utilities and helper functions.
"""
-from datetime import datetime, timedelta
+import datetime
import inspect
import logging
import os
@@ -32,9 +32,11 @@ import sys
from nova import exception
from nova import flags
+
FLAGS = flags.FLAGS
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
+
def import_class(import_str):
"""Returns a class from a string including module and class"""
mod_str, _sep, class_str = import_str.rpartition('.')
@@ -44,6 +46,7 @@ def import_class(import_str):
except (ImportError, ValueError, AttributeError):
raise exception.NotFound('Class %s cannot be found' % class_str)
+
def fetchfile(url, target):
logging.debug("Fetching %s" % url)
# c = pycurl.Curl()
@@ -55,6 +58,7 @@ def fetchfile(url, target):
# fp.close()
execute("curl %s -o %s" % (url, target))
+
def execute(cmd, input=None, addl_env=None):
env = os.environ.copy()
if addl_env:
@@ -129,10 +133,12 @@ def get_my_ip():
logging.warn("Couldn't get IP, using 127.0.0.1 %s", ex)
return "127.0.0.1"
+
def isotime(at=None):
if not at:
- at = datetime.utcnow()
+ at = datetime.datetime.utcnow()
return at.strftime(TIME_FORMAT)
+
def parse_isotime(timestr):
- return datetime.strptime(timestr, TIME_FORMAT)
+ return datetime.datetime.strptime(timestr, TIME_FORMAT)
diff --git a/nova/validate.py b/nova/validate.py
index a69306fad..21f4ed286 100644
--- a/nova/validate.py
+++ b/nova/validate.py
@@ -57,6 +57,7 @@ def rangetest(**argchecks): # validate ranges for both+defaults
return onCall
return onDecorator
+
def typetest(**argchecks):
def onDecorator(func):
import sys
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index d7416d250..f7ee34695 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -24,6 +24,8 @@ This module also documents the semantics of real hypervisor connections.
import logging
+from twisted.internet import defer
+
from nova.compute import power_state
@@ -89,10 +91,13 @@ class FakeConnection(object):
This function should use the data there to guide the creation of
the new instance.
- Once this function successfully completes, the instance should be
+ The work will be done asynchronously. This function returns a
+ Deferred that allows the caller to detect when it is complete.
+
+ Once this successfully completes, the instance should be
running (power_state.RUNNING).
- If this function fails, any partial instance should be completely
+ If this fails, any partial instance should be completely
cleaned up, and the virtualization platform should be in the state
that it was before this call began.
"""
@@ -100,6 +105,7 @@ class FakeConnection(object):
fake_instance = FakeInstance()
self.instances[instance.id] = fake_instance
fake_instance._state = power_state.RUNNING
+ return defer.succeed(None)
def reboot(self, instance):
"""
@@ -107,8 +113,11 @@ class FakeConnection(object):
The given parameter is an instance of nova.compute.service.Instance,
and so the instance is being specified as instance.name.
+
+ The work will be done asynchronously. This function returns a
+ Deferred that allows the caller to detect when it is complete.
"""
- pass
+ return defer.succeed(None)
def destroy(self, instance):
"""
@@ -116,8 +125,12 @@ class FakeConnection(object):
The given parameter is an instance of nova.compute.service.Instance,
and so the instance is being specified as instance.name.
+
+ The work will be done asynchronously. This function returns a
+ Deferred that allows the caller to detect when it is complete.
"""
del self.instances[instance.name]
+ return defer.succeed(None)
def get_info(self, instance_id):
"""
diff --git a/nova/virt/images.py b/nova/virt/images.py
index 1e23c48b9..a3ca72bdd 100644
--- a/nova/virt/images.py
+++ b/nova/virt/images.py
@@ -27,11 +27,11 @@ import urlparse
from nova import flags
from nova import process
-from nova.auth import signer
from nova.auth import manager
+from nova.auth import signer
-FLAGS = flags.FLAGS
+FLAGS = flags.FLAGS
flags.DEFINE_bool('use_s3', True,
'whether to get images from s3 or use local copy')
@@ -43,6 +43,7 @@ def fetch(image, path, user, project):
f = _fetch_local_image
return f(image, path, user, project)
+
def _fetch_s3_image(image, path, user, project):
url = image_url(image)
@@ -66,13 +67,16 @@ def _fetch_s3_image(image, path, user, project):
cmd += ['-o', path]
return process.SharedPool().execute(executable=cmd[0], args=cmd[1:])
+
def _fetch_local_image(image, path, user, project):
source = _image_path('%s/image' % image)
return process.simple_execute('cp %s %s' % (source, path))
+
def _image_path(path):
return os.path.join(FLAGS.images_path, path)
+
def image_url(image):
return "http://%s:%s/_images/%s/image" % (FLAGS.s3_host, FLAGS.s3_port,
image)
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index c00421ab8..c4eb3f272 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -42,6 +42,7 @@ from nova.virt import images
libvirt = None
libxml2 = None
+
FLAGS = flags.FLAGS
flags.DEFINE_string('libvirt_xml_template',
utils.abspath('virt/libvirt.qemu.xml.template'),
@@ -57,7 +58,9 @@ flags.DEFINE_string('libvirt_type',
'Libvirt domain type (valid options are: kvm, qemu, uml)')
flags.DEFINE_string('libvirt_uri',
'',
- 'Override the default libvirt URI (which is dependent on libvirt_type)')
+ 'Override the default libvirt URI (which is dependent'
+ ' on libvirt_type)')
+
def get_connection(read_only):
# These are loaded late so that there's no need to install these
@@ -70,6 +73,7 @@ def get_connection(read_only):
libxml2 = __import__('libxml2')
return LibvirtConnection(read_only)
+
class LibvirtConnection(object):
def __init__(self, read_only):
self.libvirt_uri, template_file = self.get_uri_and_template()
@@ -78,14 +82,12 @@ class LibvirtConnection(object):
self._wrapped_conn = None
self.read_only = read_only
-
@property
def _conn(self):
if not self._wrapped_conn:
self._wrapped_conn = self._connect(self.libvirt_uri, self.read_only)
return self._wrapped_conn
-
def get_uri_and_template(self):
if FLAGS.libvirt_type == 'uml':
uri = FLAGS.libvirt_uri or 'uml:///system'
@@ -95,7 +97,6 @@ class LibvirtConnection(object):
template_file = FLAGS.libvirt_xml_template
return uri, template_file
-
def _connect(self, uri, read_only):
auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT],
'root',
@@ -106,13 +107,10 @@ class LibvirtConnection(object):
else:
return libvirt.openAuth(uri, auth, 0)
-
-
def list_instances(self):
return [self._conn.lookupByID(x).name()
for x in self._conn.listDomainsID()]
-
def destroy(self, instance):
try:
virt_dom = self._conn.lookupByName(instance.name)
@@ -141,14 +139,12 @@ class LibvirtConnection(object):
timer.start(interval=0.5, now=True)
return d
-
def _cleanup(self, instance):
target = os.path.join(FLAGS.instances_path, instance.name)
logging.info("Deleting instance files at %s", target)
if os.path.exists(target):
shutil.rmtree(target)
-
@defer.inlineCallbacks
@exception.wrap_exception
def reboot(self, instance):
@@ -174,7 +170,6 @@ class LibvirtConnection(object):
timer.start(interval=0.5, now=True)
yield d
-
@defer.inlineCallbacks
@exception.wrap_exception
def spawn(self, instance):
@@ -204,7 +199,6 @@ class LibvirtConnection(object):
timer.start(interval=0.5, now=True)
yield local_d
-
@defer.inlineCallbacks
def _create_image(self, inst, libvirt_xml):
# syntactic nicety
@@ -286,7 +280,6 @@ class LibvirtConnection(object):
'num_cpu': num_cpu,
'cpu_time': cpu_time}
-
def get_disks(self, instance_name):
"""
Note that this function takes an instance name, not an Instance, so
@@ -329,7 +322,6 @@ class LibvirtConnection(object):
return disks
-
def get_interfaces(self, instance_name):
"""
Note that this function takes an instance name, not an Instance, so
@@ -372,7 +364,6 @@ class LibvirtConnection(object):
return interfaces
-
def block_stats(self, instance_name, disk):
"""
Note that this function takes an instance name, not an Instance, so
@@ -381,7 +372,6 @@ class LibvirtConnection(object):
domain = self._conn.lookupByName(instance_name)
return domain.blockStats(disk)
-
def interface_stats(self, instance_name, interface):
"""
Note that this function takes an instance name, not an Instance, so
diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py
index 9fe15644f..2f5994983 100644
--- a/nova/virt/xenapi.py
+++ b/nova/virt/xenapi.py
@@ -33,16 +33,29 @@ from nova.virt import images
XenAPI = None
+
FLAGS = flags.FLAGS
flags.DEFINE_string('xenapi_connection_url',
None,
- 'URL for connection to XenServer/Xen Cloud Platform. Required if connection_type=xenapi.')
+ 'URL for connection to XenServer/Xen Cloud Platform.'
+ ' Required if connection_type=xenapi.')
flags.DEFINE_string('xenapi_connection_username',
'root',
- 'Username for connection to XenServer/Xen Cloud Platform. Used only if connection_type=xenapi.')
+ 'Username for connection to XenServer/Xen Cloud Platform.'
+ ' Used only if connection_type=xenapi.')
flags.DEFINE_string('xenapi_connection_password',
None,
- 'Password for connection to XenServer/Xen Cloud Platform. Used only if connection_type=xenapi.')
+ 'Password for connection to XenServer/Xen Cloud Platform.'
+ ' Used only if connection_type=xenapi.')
+
+
+XENAPI_POWER_STATE = {
+ 'Halted' : power_state.SHUTDOWN,
+ 'Running' : power_state.RUNNING,
+ 'Paused' : power_state.PAUSED,
+ 'Suspended': power_state.SHUTDOWN, # FIXME
+ 'Crashed' : power_state.CRASHED
+}
def get_connection(_):
@@ -62,7 +75,6 @@ def get_connection(_):
class XenAPIConnection(object):
-
def __init__(self, url, user, pw):
self._conn = XenAPI.Session(url)
self._conn.login_with_password(user, pw)
@@ -107,7 +119,6 @@ class XenAPIConnection(object):
yield self._create_vif(vm_ref, network_ref, mac_address)
yield self._conn.xenapi.VM.start(vm_ref, False, False)
-
def create_vm(self, instance, kernel, ramdisk):
mem = str(long(instance.datamodel['memory_kb']) * 1024)
vcpus = str(instance.datamodel['vcpus'])
@@ -145,7 +156,6 @@ class XenAPIConnection(object):
logging.debug('Created VM %s as %s.', instance.name, vm_ref)
return vm_ref
-
def create_vbd(self, vm_ref, vdi_ref, userdevice, bootable):
vbd_rec = {}
vbd_rec['VM'] = vm_ref
@@ -166,7 +176,6 @@ class XenAPIConnection(object):
vdi_ref)
return vbd_ref
-
def _create_vif(self, vm_ref, network_ref, mac_address):
vif_rec = {}
vif_rec['device'] = '0'
@@ -184,7 +193,6 @@ class XenAPIConnection(object):
vm_ref, network_ref)
return vif_ref
-
def _find_network_with_bridge(self, bridge):
expr = 'field "bridge" = "%s"' % bridge
networks = self._conn.xenapi.network.get_all_records_where(expr)
@@ -195,7 +203,6 @@ class XenAPIConnection(object):
else:
raise Exception('Found no network for bridge %s' % bridge)
-
def fetch_image(self, image, user, project, use_sr):
"""use_sr: True to put the image as a VDI in an SR, False to place
it on dom0's filesystem. The former is for VM disks, the latter for
@@ -213,7 +220,6 @@ class XenAPIConnection(object):
args['add_partition'] = 'true'
return self._call_plugin('objectstore', fn, args)
-
def reboot(self, instance):
vm = self.lookup(instance.name)
if vm is None:
@@ -231,7 +237,7 @@ class XenAPIConnection(object):
if vm is None:
raise Exception('instance not present %s' % instance_id)
rec = self._conn.xenapi.VM.get_record(vm)
- return {'state': power_state_from_xenapi[rec['power_state']],
+ return {'state': XENAPI_POWER_STATE[rec['power_state']],
'max_mem': long(rec['memory_static_max']) >> 10,
'mem': long(rec['memory_dynamic_max']) >> 10,
'num_cpu': rec['VCPUs_max'],
@@ -247,26 +253,15 @@ class XenAPIConnection(object):
else:
return vms[0]
-
def _call_plugin(self, plugin, fn, args):
return _unwrap_plugin_exceptions(
self._conn.xenapi.host.call_plugin,
self._get_xenapi_host(), plugin, fn, args)
-
def _get_xenapi_host(self):
return self._conn.xenapi.session.get_this_host(self._conn.handle)
-power_state_from_xenapi = {
- 'Halted' : power_state.SHUTDOWN,
- 'Running' : power_state.RUNNING,
- 'Paused' : power_state.PAUSED,
- 'Suspended': power_state.SHUTDOWN, # FIXME
- 'Crashed' : power_state.CRASHED
-}
-
-
def _unwrap_plugin_exceptions(func, *args, **kwargs):
try:
return func(*args, **kwargs)
diff --git a/nova/wsgi.py b/nova/wsgi.py
index 4fd6e59e3..a0a175dc7 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -29,6 +29,8 @@ import eventlet.wsgi
eventlet.patcher.monkey_patch(all=False, socket=True)
import routes
import routes.middleware
+import webob.dec
+import webob.exc
logging.getLogger("routes.middleware").addHandler(logging.StreamHandler())
@@ -41,6 +43,8 @@ def run_server(application, port):
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):
@@ -79,95 +83,192 @@ class Application(object):
raise NotImplementedError("You must implement __call__")
-class Middleware(Application): # pylint: disable-msg=W0223
- """Base WSGI middleware wrapper. These classes require an
- application to be initialized that will be called next."""
+class Middleware(Application): # pylint: disable=W0223
+ """
+ Base WSGI middleware wrapper. These classes require an application to be
+ initialized that will be called next. By default the middleware will
+ simply call its wrapped app, or you can override __call__ to customize its
+ behavior.
+ """
- def __init__(self, application): # pylint: disable-msg=W0231
+ def __init__(self, application): # pylint: disable=W0231
self.application = application
+ @webob.dec.wsgify
+ def __call__(self, req):
+ """Override to implement middleware behavior."""
+ return self.application
+
class Debug(Middleware):
- """Helper class that can be insertd into any WSGI application chain
+ """Helper class that can be inserted into any WSGI application chain
to get information about the request and response."""
- def __call__(self, environ, start_response):
- for key, value in environ.items():
+ @webob.dec.wsgify
+ def __call__(self, req):
+ print ("*" * 40) + " REQUEST ENVIRON"
+ for key, value in req.environ.items():
print key, "=", value
print
- wrapper = debug_start_response(start_response)
- return debug_print_body(self.application(environ, wrapper))
-
+ resp = req.get_response(self.application)
-def debug_start_response(start_response):
- """Wrap the start_response to capture when called."""
-
- def wrapper(status, headers, exc_info=None):
- """Print out all headers when start_response is called."""
- print status
- for (key, value) in headers:
+ print ("*" * 40) + " RESPONSE HEADERS"
+ for (key, value) in resp.headers:
print key, "=", value
print
- start_response(status, headers, exc_info)
-
- return wrapper
-
-def debug_print_body(body):
- """Print the body of the response as it is sent back."""
+ resp.app_iter = self.print_generator(resp.app_iter)
- class Wrapper(object):
- """Iterate through all the body parts and print before returning."""
+ return resp
- def __iter__(self):
- for part in body:
- sys.stdout.write(part)
- sys.stdout.flush()
- yield part
- print
+ @staticmethod
+ def print_generator(app_iter):
+ """
+ Iterator that prints the contents of a wrapper string iterator
+ when iterated.
+ """
+ print ("*" * 40) + "BODY"
+ for part in app_iter:
+ sys.stdout.write(part)
+ sys.stdout.flush()
+ yield part
+ print
- return Wrapper()
+class Router(object):
+ """
+ WSGI middleware that maps incoming requests to WSGI apps.
+ """
-class ParsedRoutes(Middleware):
- """Processed parsed routes from routes.middleware.RoutesMiddleware
- and call either the controller if found or the default application
- otherwise."""
+ def __init__(self, mapper):
+ """
+ Create a router for the given routes.Mapper.
- def __call__(self, environ, start_response):
- if environ['routes.route'] is None:
- return self.application(environ, start_response)
- app = environ['wsgiorg.routing_args'][1]['controller']
- return app(environ, start_response)
+ Each route in `mapper` must specify a 'controller', which is a
+ WSGI app to call. You'll probably want to specify an 'action' as
+ well and have your controller be a wsgi.Controller, who will route
+ the request to the action method.
+ Examples:
+ mapper = routes.Mapper()
+ sc = ServerController()
-class Router(Middleware): # pylint: disable-msg=R0921
- """Wrapper to help setup routes.middleware.RoutesMiddleware."""
+ # Explicit mapping of one route to a controller+action
+ mapper.connect(None, "/svrlist", controller=sc, action="list")
- def __init__(self, application):
- self.map = routes.Mapper()
- self._build_map()
- application = ParsedRoutes(application)
- application = routes.middleware.RoutesMiddleware(application, self.map)
- super(Router, self).__init__(application)
+ # Actions are all implicitly defined
+ mapper.resource("server", "servers", controller=sc)
- def __call__(self, environ, start_response):
- return self.application(environ, start_response)
+ # Pointing to an arbitrary WSGI app. You can specify the
+ # {path_info:.*} parameter so the target app can be handed just that
+ # section of the URL.
+ mapper.connect(None, "/v1.0/{path_info:.*}", controller=BlogApp())
+ """
+ self.map = mapper
+ self._router = routes.middleware.RoutesMiddleware(self._dispatch,
+ self.map)
- def _build_map(self):
- """Method to create new connections for the routing map."""
- raise NotImplementedError("You must implement _build_map")
+ @webob.dec.wsgify
+ def __call__(self, req):
+ """
+ Route the incoming request to a controller based on self.map.
+ If no match, return a 404.
+ """
+ return self._router
- def _connect(self, *args, **kwargs):
- """Wrapper for the map.connect method."""
- self.map.connect(*args, **kwargs)
+ @webob.dec.wsgify
+ def _dispatch(self, req):
+ """
+ Called by self._router after matching the incoming request to a route
+ and putting the information into req.environ. Either returns 404
+ or the routed WSGI app's response.
+ """
+ match = req.environ['wsgiorg.routing_args'][1]
+ if not match:
+ return webob.exc.HTTPNotFound()
+ app = match['controller']
+ return app
+
+
+class Controller(object):
+ """
+ WSGI app that reads routing information supplied by RoutesMiddleware
+ and calls the requested action method upon itself. All action methods
+ must, in addition to their normal parameters, accept a 'req' argument
+ which is the incoming webob.Request.
+ """
+ @webob.dec.wsgify
+ def __call__(self, req):
+ """
+ Call the method specified in req.environ by RoutesMiddleware.
+ """
+ arg_dict = req.environ['wsgiorg.routing_args'][1]
+ action = arg_dict['action']
+ method = getattr(self, action)
+ del arg_dict['controller']
+ del arg_dict['action']
+ arg_dict['req'] = req
+ return method(**arg_dict)
-def route_args(application):
- """Decorator to make grabbing routing args more convenient."""
+class Serializer(object):
+ """
+ Serializes a dictionary to a Content Type specified by a WSGI environment.
+ """
- def wrapper(self, req):
- """Call application with req and parsed routing args from."""
- return application(self, req, req.environ['wsgiorg.routing_args'][1])
+ def __init__(self, environ, metadata=None):
+ """
+ Create a serializer based on the given WSGI environment.
+ 'metadata' is an optional dict mapping MIME types to information
+ needed to serialize a dictionary to that type.
+ """
+ self.environ = environ
+ self.metadata = metadata or {}
- return wrapper
+ def to_content_type(self, data):
+ """
+ Serialize a dictionary into a string. The format of the string
+ will be decided based on the Content Type requested in self.environ:
+ by Accept: header, or by URL suffix.
+ """
+ mimetype = 'application/xml'
+ # TODO(gundlach): determine mimetype from request
+
+ if mimetype == 'application/json':
+ import json
+ return json.dumps(data)
+ elif mimetype == 'application/xml':
+ metadata = self.metadata.get('application/xml', {})
+ # We expect data to contain a single key which is the XML root.
+ root_key = data.keys()[0]
+ from xml.dom import minidom
+ doc = minidom.Document()
+ node = self._to_xml_node(doc, metadata, root_key, data[root_key])
+ return node.toprettyxml(indent=' ')
+ else:
+ return repr(data)
+
+ def _to_xml_node(self, doc, metadata, nodename, data):
+ result = doc.createElement(nodename)
+ if type(data) is list:
+ singular = metadata.get('plurals', {}).get(nodename, None)
+ if singular is None:
+ if nodename.endswith('s'):
+ singular = nodename[:-1]
+ else:
+ singular = 'item'
+ for item in data:
+ node = self._to_xml_node(doc, metadata, singular, item)
+ result.appendChild(node)
+ elif type(data) is dict:
+ attrs = metadata.get('attributes', {}).get(nodename, {})
+ for k,v in data.items():
+ if k in attrs:
+ result.setAttribute(k, str(v))
+ else:
+ node = self._to_xml_node(doc, metadata, k, v)
+ result.appendChild(node)
+ else: # atom
+ node = doc.createTextNode(str(data))
+ result.appendChild(node)
+ return result
diff --git a/pylintrc b/pylintrc
index 53d02d6b2..6c799c7ea 100644
--- a/pylintrc
+++ b/pylintrc
@@ -1,5 +1,9 @@
[Messages Control]
-disable-msg=C0103
+disable=C0103
+# TODOs in code comments are fine...
+disable=W0511
+# *args and **kwargs are fine
+disable=W0142
[Basic]
# Variables can be 1 to 31 characters long, with
@@ -10,10 +14,6 @@ variable-rgx=[a-z_][a-z0-9_]{0,30}$
# and be lowecased with underscores
method-rgx=[a-z_][a-z0-9_]{2,50}$
-[MESSAGES CONTROL]
-# TODOs in code comments are fine...
-disable-msg=W0511
-
[Design]
max-public-methods=100
min-public-methods=0
diff --git a/run_tests.py b/run_tests.py
index d90ac8175..77aa9088a 100644
--- a/run_tests.py
+++ b/run_tests.py
@@ -38,11 +38,11 @@ Due to our use of multiprocessing it we frequently get some ignorable
'Interrupted system call' exceptions after test completion.
"""
+
import __main__
import os
import sys
-
from twisted.scripts import trial as trial_script
from nova import datastore
@@ -65,13 +65,12 @@ from nova.tests.volume_unittest import *
FLAGS = flags.FLAGS
-
flags.DEFINE_bool('flush_db', True,
'Flush the database before running fake tests')
-
flags.DEFINE_string('tests_stderr', 'run_tests.err.log',
- 'Path to where to pipe STDERR during test runs. '
- 'Default = "run_tests.err.log"')
+ 'Path to where to pipe STDERR during test runs.'
+ ' Default = "run_tests.err.log"')
+
if __name__ == '__main__':
OptionsClass = twistd.WrapTwistedOptions(trial_script.Options)
diff --git a/run_tests.sh b/run_tests.sh
index 85d7c8834..6ea40d95e 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -1,12 +1,66 @@
-#!/bin/bash
+#!/bin/bash
+
+function usage {
+ echo "Usage: $0 [OPTION]..."
+ echo "Run Nova's test suite(s)"
+ echo ""
+ echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
+ echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
+ echo " -h, --help Print this usage message"
+ echo ""
+ echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
+ echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
+ echo " prefer to run tests NOT in a virtual environment, simply pass the -N option."
+ exit
+}
+
+function process_options {
+ array=$1
+ elements=${#array[@]}
+ for (( x=0;x<$elements;x++)); do
+ process_option ${array[${x}]}
+ done
+}
+
+function process_option {
+ option=$1
+ case $option in
+ -h|--help) usage;;
+ -V|--virtual-env) let always_venv=1; let never_venv=0;;
+ -N|--no-virtual-env) let always_venv=0; let never_venv=1;;
+ esac
+}
venv=.nova-venv
with_venv=tools/with_venv.sh
+always_venv=0
+never_venv=0
+options=("$@")
+
+process_options $options
+
+if [ $never_venv -eq 1 ]; then
+ # Just run the test suites in current environment
+ python run_tests.py
+ exit
+fi
if [ -e ${venv} ]; then
${with_venv} python run_tests.py $@
else
- echo "No virtual environment found...creating one"
- python tools/install_venv.py
+ if [ $always_venv -eq 1 ]; then
+ # Automatically install the virtualenv
+ python tools/install_venv.py
+ else
+ echo -e "No virtual environment found...create one? (Y/n) \c"
+ read use_ve
+ if [ "x$use_ve" = "xY" ]; then
+ # Install the virtualenv and run the test suite in it
+ python tools/install_venv.py
+ else
+ python run_tests.py
+ exit
+ fi
+ fi
${with_venv} python run_tests.py $@
fi
diff --git a/tools/install_venv.py b/tools/install_venv.py
index e1a270638..4e775eb33 100644
--- a/tools/install_venv.py
+++ b/tools/install_venv.py
@@ -38,15 +38,16 @@ def die(message, *args):
def run_command(cmd, redirect_output=True, error_ok=False):
- """Runs a command in an out-of-process shell, returning the
- output of that command
+ """
+ Runs a command in an out-of-process shell, returning the
+ output of that command. Working directory is ROOT.
"""
if redirect_output:
stdout = subprocess.PIPE
else:
stdout = None
- proc = subprocess.Popen(cmd, stdout=stdout)
+ proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout)
output = proc.communicate()[0]
if not error_ok and proc.returncode != 0:
die('Command "%s" failed.\n%s', ' '.join(cmd), output)
@@ -94,6 +95,12 @@ def install_dependencies(venv=VENV):
redirect_output=False)
+ # Tell the virtual env how to "import nova"
+ pathfile=os.path.join(venv, "lib", "python2.6", "site-packages", "nova.pth")
+ f=open(pathfile, 'w')
+ f.write("%s\n" % ROOT)
+
+
def print_help():
help = """
Nova development environment setup is complete.
diff --git a/tools/pip-requires b/tools/pip-requires
index c173d6221..28af7bcb9 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -11,7 +11,9 @@ lockfile==0.8
python-daemon==1.5.5
python-gflags==1.3
redis==2.0.0
+routes==1.12.3
tornado==1.0
+webob==0.9.8
wsgiref==0.1.2
zope.interface==3.6.1
mox==0.5.0