summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJosh Kearney <josh@jk0.org>2011-03-24 15:56:53 -0500
committerJosh Kearney <josh@jk0.org>2011-03-24 15:56:53 -0500
commit4dd38d7f729e09d0b3718c21ae716d11b8a5faee (patch)
tree969f389ec8eb8a3cd1a0ab6dbe000cd6033f1379
parenta07854e38eda33fda9bc3523d8dd85caae594ea0 (diff)
parent823df3b0ee7e7eb35e5864bfa235e686819df13e (diff)
downloadnova-4dd38d7f729e09d0b3718c21ae716d11b8a5faee.tar.gz
nova-4dd38d7f729e09d0b3718c21ae716d11b8a5faee.tar.xz
nova-4dd38d7f729e09d0b3718c21ae716d11b8a5faee.zip
Merged trunk
-rwxr-xr-xbin/nova-direct-api35
-rwxr-xr-xbin/stack14
-rw-r--r--nova/api/direct.py50
-rw-r--r--nova/api/ec2/cloud.py22
-rw-r--r--nova/compute/api.py9
-rw-r--r--nova/tests/test_direct.py27
-rw-r--r--nova/volume/api.py3
7 files changed, 137 insertions, 23 deletions
diff --git a/bin/nova-direct-api b/bin/nova-direct-api
index a2c9f1557..83ec72722 100755
--- a/bin/nova-direct-api
+++ b/bin/nova-direct-api
@@ -34,12 +34,14 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
gettext.install('nova', unicode=1)
+from nova import compute
from nova import flags
from nova import log as logging
+from nova import network
from nova import utils
+from nova import volume
from nova import wsgi
from nova.api import direct
-from nova.compute import api as compute_api
FLAGS = flags.FLAGS
@@ -50,13 +52,42 @@ flags.DEFINE_flag(flags.HelpshortFlag())
flags.DEFINE_flag(flags.HelpXMLFlag())
+# An example of an API that only exposes read-only methods.
+# In this case we're just limiting which methods are exposed.
+class ReadOnlyCompute(direct.Limited):
+ """Read-only Compute API."""
+
+ _allowed = ['get', 'get_all', 'get_console_output']
+
+
+# An example of an API that provides a backwards compatibility layer.
+# In this case we're overwriting the implementation to ensure
+# compatibility with an older version. In reality we would want the
+# "description=None" to be part of the actual API so that code
+# like this isn't even necessary, but this example shows what one can
+# do if that isn't the situation.
+class VolumeVersionOne(direct.Limited):
+ _allowed = ['create', 'delete', 'update', 'get']
+
+ def create(self, context, size, name):
+ self.proxy.create(context, size, name, description=None)
+
+
if __name__ == '__main__':
utils.default_flagfile()
FLAGS(sys.argv)
logging.setup()
- direct.register_service('compute', compute_api.API())
+ direct.register_service('compute', compute.API())
+ direct.register_service('volume', volume.API())
+ direct.register_service('network', network.API())
direct.register_service('reflect', direct.Reflection())
+
+ # Here is how we could expose the code in the examples above.
+ #direct.register_service('compute-readonly',
+ # ReadOnlyCompute(compute.API()))
+ #direct.register_service('volume-v1', VolumeVersionOne(volume.API()))
+
router = direct.Router()
with_json = direct.JsonParamsMiddleware(router)
with_req = direct.PostParamsMiddleware(with_json)
diff --git a/bin/stack b/bin/stack
index 25caca06f..d84a82e27 100755
--- a/bin/stack
+++ b/bin/stack
@@ -59,11 +59,21 @@ USAGE = """usage: stack [options] <controller> <method> [arg1=value arg2=value]
def format_help(d):
"""Format help text, keys are labels and values are descriptions."""
+ MAX_INDENT = 30
indent = max([len(k) for k in d])
+ if indent > MAX_INDENT:
+ indent = MAX_INDENT - 6
+
out = []
for k, v in d.iteritems():
- t = textwrap.TextWrapper(initial_indent=' %s ' % k.ljust(indent),
- subsequent_indent=' ' * (indent + 6))
+ if (len(k) + 6) > MAX_INDENT:
+ out.extend([' %s' % k])
+ initial_indent = ' ' * (indent + 6)
+ else:
+ initial_indent = ' %s ' % k.ljust(indent)
+ subsequent_indent = ' ' * (indent + 6)
+ t = textwrap.TextWrapper(initial_indent=initial_indent,
+ subsequent_indent=subsequent_indent)
out.extend(t.wrap(v))
return out
diff --git a/nova/api/direct.py b/nova/api/direct.py
index dfca250e0..e5f33cee4 100644
--- a/nova/api/direct.py
+++ b/nova/api/direct.py
@@ -38,6 +38,7 @@ import routes
import webob
from nova import context
+from nova import exception
from nova import flags
from nova import utils
from nova import wsgi
@@ -205,10 +206,53 @@ class ServiceWrapper(wsgi.Controller):
# NOTE(vish): make sure we have no unicode keys for py2.6.
params = dict([(str(k), v) for (k, v) in params.iteritems()])
result = method(context, **params)
- if type(result) is dict or type(result) is list:
- return self._serialize(result, req.best_match_content_type())
- else:
+ if result is None or type(result) is str or type(result) is unicode:
return result
+ try:
+ return self._serialize(result, req.best_match_content_type())
+ except:
+ raise exception.Error("returned non-serializable type: %s"
+ % result)
+
+
+class Limited(object):
+ __notdoc = """Limit the available methods on a given object.
+
+ (Not a docstring so that the docstring can be conditionally overriden.)
+
+ Useful when defining a public API that only exposes a subset of an
+ internal API.
+
+ Expected usage of this class is to define a subclass that lists the allowed
+ methods in the 'allowed' variable.
+
+ Additionally where appropriate methods can be added or overwritten, for
+ example to provide backwards compatibility.
+
+ The wrapping approach has been chosen so that the wrapped API can maintain
+ its own internal consistency, for example if it calls "self.create" it
+ should get its own create method rather than anything we do here.
+
+ """
+
+ _allowed = None
+
+ def __init__(self, proxy):
+ self._proxy = proxy
+ if not self.__doc__:
+ self.__doc__ = proxy.__doc__
+ if not self._allowed:
+ self._allowed = []
+
+ def __getattr__(self, key):
+ """Only return methods that are named in self._allowed."""
+ if key not in self._allowed:
+ raise AttributeError()
+ return getattr(self._proxy, key)
+
+ def __dir__(self):
+ """Only return methods that are named in self._allowed."""
+ return [x for x in dir(self._proxy) if x in self._allowed]
class Proxy(object):
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 2afcea77c..0da642318 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -541,7 +541,7 @@ class CloudController(object):
volumes = []
for ec2_id in volume_id:
internal_id = ec2utils.ec2_id_to_id(ec2_id)
- volume = self.volume_api.get(context, internal_id)
+ volume = self.volume_api.get(context, volume_id=internal_id)
volumes.append(volume)
else:
volumes = self.volume_api.get_all(context)
@@ -585,9 +585,11 @@ class CloudController(object):
def create_volume(self, context, size, **kwargs):
LOG.audit(_("Create volume of %s GB"), size, context=context)
- volume = self.volume_api.create(context, size,
- kwargs.get('display_name'),
- kwargs.get('display_description'))
+ volume = self.volume_api.create(
+ context,
+ size=size,
+ name=kwargs.get('display_name'),
+ description=kwargs.get('display_description'))
# TODO(vish): Instance should be None at db layer instead of
# trying to lazy load, but for now we turn it into
# a dict to avoid an error.
@@ -606,7 +608,9 @@ class CloudController(object):
if field in kwargs:
changes[field] = kwargs[field]
if changes:
- self.volume_api.update(context, volume_id, kwargs)
+ self.volume_api.update(context,
+ volume_id=volume_id,
+ fields=changes)
return True
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
@@ -619,7 +623,7 @@ class CloudController(object):
instance_id=instance_id,
volume_id=volume_id,
device=device)
- volume = self.volume_api.get(context, volume_id)
+ volume = self.volume_api.get(context, volume_id=volume_id)
return {'attachTime': volume['attach_time'],
'device': volume['mountpoint'],
'instanceId': ec2utils.id_to_ec2_id(instance_id),
@@ -630,7 +634,7 @@ class CloudController(object):
def detach_volume(self, context, volume_id, **kwargs):
volume_id = ec2utils.ec2_id_to_id(volume_id)
LOG.audit(_("Detach volume %s"), volume_id, context=context)
- volume = self.volume_api.get(context, volume_id)
+ volume = self.volume_api.get(context, volume_id=volume_id)
instance = self.compute_api.detach_volume(context, volume_id=volume_id)
return {'attachTime': volume['attach_time'],
'device': volume['mountpoint'],
@@ -768,7 +772,7 @@ class CloudController(object):
def release_address(self, context, public_ip, **kwargs):
LOG.audit(_("Release address %s"), public_ip, context=context)
- self.network_api.release_floating_ip(context, public_ip)
+ self.network_api.release_floating_ip(context, address=public_ip)
return {'releaseResponse': ["Address released."]}
def associate_address(self, context, instance_id, public_ip, **kwargs):
@@ -782,7 +786,7 @@ class CloudController(object):
def disassociate_address(self, context, public_ip, **kwargs):
LOG.audit(_("Disassociate address %s"), public_ip, context=context)
- self.network_api.disassociate_floating_ip(context, public_ip)
+ self.network_api.disassociate_floating_ip(context, address=public_ip)
return {'disassociateResponse': ["Address disassociated."]}
def run_instances(self, context, **kwargs):
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 309847156..f4aab97de 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -636,7 +636,7 @@ class API(base.Base):
if not re.match("^/dev/[a-z]d[a-z]+$", device):
raise exception.ApiError(_("Invalid device specified: %s. "
"Example device: /dev/vdb") % device)
- self.volume_api.check_attach(context, volume_id)
+ self.volume_api.check_attach(context, volume_id=volume_id)
instance = self.get(context, instance_id)
host = instance['host']
rpc.cast(context,
@@ -650,7 +650,7 @@ class API(base.Base):
instance = self.db.volume_get_instance(context.elevated(), volume_id)
if not instance:
raise exception.ApiError(_("Volume isn't attached to anything!"))
- self.volume_api.check_detach(context, volume_id)
+ self.volume_api.check_detach(context, volume_id=volume_id)
host = instance['host']
rpc.cast(context,
self.db.queue_get_for(context, FLAGS.compute_topic, host),
@@ -661,5 +661,6 @@ class API(base.Base):
def associate_floating_ip(self, context, instance_id, address):
instance = self.get(context, instance_id)
- self.network_api.associate_floating_ip(context, address,
- instance['fixed_ip'])
+ self.network_api.associate_floating_ip(context,
+ floating_ip=address,
+ fixed_ip=instance['fixed_ip'])
diff --git a/nova/tests/test_direct.py b/nova/tests/test_direct.py
index 80e4d2e1f..588a24b35 100644
--- a/nova/tests/test_direct.py
+++ b/nova/tests/test_direct.py
@@ -25,12 +25,18 @@ import webob
from nova import compute
from nova import context
from nova import exception
+from nova import network
from nova import test
+from nova import volume
from nova import utils
from nova.api import direct
from nova.tests import test_cloud
+class ArbitraryObject(object):
+ pass
+
+
class FakeService(object):
def echo(self, context, data):
return {'data': data}
@@ -39,6 +45,9 @@ class FakeService(object):
return {'user': context.user_id,
'project': context.project_id}
+ def invalid_return(self, context):
+ return ArbitraryObject()
+
class DirectTestCase(test.TestCase):
def setUp(self):
@@ -84,6 +93,12 @@ class DirectTestCase(test.TestCase):
resp_parsed = json.loads(resp.body)
self.assertEqual(resp_parsed['data'], 'foo')
+ def test_invalid(self):
+ req = webob.Request.blank('/fake/invalid_return')
+ req.environ['openstack.context'] = self.context
+ req.method = 'POST'
+ self.assertRaises(exception.Error, req.get_response, self.router)
+
def test_proxy(self):
proxy = direct.Proxy(self.router)
rv = proxy.fake.echo(self.context, data='baz')
@@ -93,12 +108,20 @@ class DirectTestCase(test.TestCase):
class DirectCloudTestCase(test_cloud.CloudTestCase):
def setUp(self):
super(DirectCloudTestCase, self).setUp()
- compute_handle = compute.API(network_api=self.cloud.network_api,
- volume_api=self.cloud.volume_api)
+ compute_handle = compute.API(image_service=self.cloud.image_service)
+ volume_handle = volume.API()
+ network_handle = network.API()
direct.register_service('compute', compute_handle)
+ direct.register_service('volume', volume_handle)
+ direct.register_service('network', network_handle)
+
self.router = direct.JsonParamsMiddleware(direct.Router())
proxy = direct.Proxy(self.router)
self.cloud.compute_api = proxy.compute
+ self.cloud.volume_api = proxy.volume
+ self.cloud.network_api = proxy.network
+ compute_handle.volume_api = proxy.volume
+ compute_handle.network_api = proxy.network
def tearDown(self):
super(DirectCloudTestCase, self).tearDown()
diff --git a/nova/volume/api.py b/nova/volume/api.py
index 2f4494845..4b4bb9dc5 100644
--- a/nova/volume/api.py
+++ b/nova/volume/api.py
@@ -82,7 +82,8 @@ class API(base.Base):
self.db.volume_update(context, volume_id, fields)
def get(self, context, volume_id):
- return self.db.volume_get(context, volume_id)
+ rv = self.db.volume_get(context, volume_id)
+ return dict(rv.iteritems())
def get_all(self, context):
if context.is_admin: