From e3c7f34bbcc09cbb169d9316c50da6d908ac623c Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Thu, 2 Sep 2010 15:34:33 -0500 Subject: Servers API remodeling and serialization handling --- nova/api/rackspace/base.py | 8 +--- nova/api/rackspace/servers.py | 89 ++++++++++++++++++++++++++++++------------- 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/nova/api/rackspace/base.py b/nova/api/rackspace/base.py index dd2c6543c..5e5bd6f54 100644 --- a/nova/api/rackspace/base.py +++ b/nova/api/rackspace/base.py @@ -21,10 +21,4 @@ from nova import wsgi class Controller(wsgi.Controller): """TODO(eday): Base controller for all rackspace controllers. What is this for? Is this just Rackspace specific? """ - - @classmethod - def render(cls, instance): - if isinstance(instance, list): - return {cls.entity_name: cls.render(instance)} - else: - return {"TODO": "TODO"} + pass diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 25d1fe9c8..940b7061f 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -18,56 +18,76 @@ from nova import rpc from nova.compute import model as compute from nova.api.rackspace import base +from webob import exc +from nova import flags + +FLAGS = flags.FLAGS class Controller(base.Controller): - entity_name = 'servers' - def index(self, **kwargs): - instances = [] - for inst in compute.InstanceDirectory().all: - instances.append(instance_details(inst)) + _serialization_metadata = { + 'application/xml': { + "plurals": "servers", + "attributes": { + "server": [ "id", "imageId", "flavorId", "hostId", "status", + "progress", "addresses", "metadata", "progress" ] + } + } + } + + def __init__(self): + self.instdir = compute.InstanceDirectory() + + def index(self, req): + return [_entity_inst(instance_details(inst)) for inst in instdir.all] - def show(self, **kwargs): - instance_id = kwargs['id'] - return compute.InstanceDirectory().get(instance_id) + def show(self, req, id): + inst = self.instdir.get(id) + if inst: + return _entity_inst(inst) + raise exc.HTTPNotFound() - def delete(self, **kwargs): - instance_id = kwargs['id'] - instance = compute.InstanceDirectory().get(instance_id) + def delete(self, req, id): + instance = self.instdir.get(id) if not instance: - raise ServerNotFound("The requested server was not found") + return exc.HTTPNotFound() instance.destroy() - return True + return exc.HTTPAccepted() - def create(self, **kwargs): - inst = self.build_server_instance(kwargs['server']) + def create(self, req): + inst = self._build_server_instance(req) rpc.cast( FLAGS.compute_topic, { "method": "run_instance", "args": {"instance_id": inst.instance_id}}) + return _entity_inst(inst) - def update(self, **kwargs): - instance_id = kwargs['id'] - instance = compute.InstanceDirectory().get(instance_id) + def update(self, req, id): + instance = self.instdir.get(instance_id) if not instance: - raise ServerNotFound("The requested server was not found") + return exc.HTTPNotFound() instance.update(kwargs['server']) instance.save() + return exc.HTTPNoContent() - def build_server_instance(self, env): + def _build_server_instance(self, req): """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() + + # TODO(dietz) Do we need any of these? + inst['project_id'] = env['project']['id'] + inst['reservation_id'] = reservation + reservation = utils.generate_uid('r') + address = self.network.allocate_ip( inst['user_id'], inst['project_id'], @@ -77,7 +97,24 @@ class Controller(base.Controller): 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 + return _entity_inst(inst) + + def _entity_inst(self, inst): + """ Maps everything to Rackspace-like attributes for return""" + + translated_keys = dict(metadata={}, status=state_description, + id=instance_id, imageId=image_id, flavorId=instance_type,) + + for k,v in translated_keys.iteritems(): + inst[k] = inst[v] + + filtered_keys = ['instance_id', 'state_description', 'state', + 'reservation_id', 'project_id', 'launch_time', + 'bridge_name', 'mac_address', 'user_id'] + + for key in filtered_keys:: + del inst[key] + + return dict(server=inst) -- cgit From 9df460a5cb2de96133028949ad12ac7c16dbd7fc Mon Sep 17 00:00:00 2001 From: andy Date: Wed, 8 Sep 2010 19:57:29 +0200 Subject: Remove tornado-related code from almost everything. Left it in api where it is still being used pending gundlach's changes. --- nova/rpc.py | 16 +--------------- nova/test.py | 7 ++++--- nova/tests/access_unittest.py | 2 +- nova/tests/auth_unittest.py | 2 +- nova/tests/cloud_unittest.py | 14 ++++++++------ nova/tests/objectstore_unittest.py | 2 +- nova/tests/rpc_unittest.py | 4 ++-- 7 files changed, 18 insertions(+), 29 deletions(-) diff --git a/nova/rpc.py b/nova/rpc.py index 84a9b5590..d83dd7a7c 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -81,21 +81,6 @@ class Consumer(messaging.Consumer): self.failed_connection = False super(Consumer, self).__init__(*args, **kwargs) - # TODO(termie): it would be nice to give these some way of automatically - # cleaning up after themselves - def attach_to_tornado(self, io_inst=None): - """Attach a callback to tornado that fires 10 times a second""" - from tornado import ioloop - if io_inst is None: - io_inst = ioloop.IOLoop.instance() - - injected = ioloop.PeriodicCallback( - lambda: self.fetch(enable_callbacks=True), 100, io_loop=io_inst) - injected.start() - return injected - - attachToTornado = attach_to_tornado - def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): """Wraps the parent fetch with some logic for failed connections""" # TODO(vish): the logic for failed connections and logging should be @@ -123,6 +108,7 @@ class Consumer(messaging.Consumer): """Attach a callback to twisted that fires 10 times a second""" loop = task.LoopingCall(self.fetch, enable_callbacks=True) loop.start(interval=0.1) + return loop class Publisher(messaging.Publisher): diff --git a/nova/test.py b/nova/test.py index c392c8a84..5380d5743 100644 --- a/nova/test.py +++ b/nova/test.py @@ -62,6 +62,7 @@ class TrialTestCase(unittest.TestCase): self.mox = mox.Mox() self.stubs = stubout.StubOutForTesting() self.flag_overrides = {} + self.injected = [] def tearDown(self): # pylint: disable-msg=C0103 """Runs after each test method to finalize/tear down test environment""" @@ -72,6 +73,9 @@ class TrialTestCase(unittest.TestCase): self.stubs.SmartUnsetAll() self.mox.VerifyAll() + for x in self.injected: + x.stop() + if FLAGS.fake_rabbit: fakerabbit.reset_all() @@ -99,7 +103,6 @@ class BaseTestCase(TrialTestCase): super(BaseTestCase, self).setUp() # TODO(termie): we could possibly keep a more global registry of # the injected listeners... this is fine for now though - self.injected = [] self.ioloop = ioloop.IOLoop.instance() self._waiting = None @@ -109,8 +112,6 @@ class BaseTestCase(TrialTestCase): def tearDown(self):# pylint: disable-msg=C0103 """Runs after each test method to finalize/tear down test environment""" super(BaseTestCase, self).tearDown() - for x in self.injected: - x.stop() if FLAGS.fake_rabbit: fakerabbit.reset_all() diff --git a/nova/tests/access_unittest.py b/nova/tests/access_unittest.py index fa0a090a0..4cdf42091 100644 --- a/nova/tests/access_unittest.py +++ b/nova/tests/access_unittest.py @@ -30,7 +30,7 @@ FLAGS = flags.FLAGS class Context(object): pass -class AccessTestCase(test.BaseTestCase): +class AccessTestCase(test.TrialTestCase): def setUp(self): super(AccessTestCase, self).setUp() FLAGS.connection_type = 'fake' diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py index 0b404bfdc..b219d0e3c 100644 --- a/nova/tests/auth_unittest.py +++ b/nova/tests/auth_unittest.py @@ -31,7 +31,7 @@ from nova.endpoint import cloud FLAGS = flags.FLAGS -class AuthTestCase(test.BaseTestCase): +class AuthTestCase(test.TrialTestCase): flush_db = False def setUp(self): super(AuthTestCase, self).setUp() diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 900ff5a97..0add53937 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -19,7 +19,6 @@ import logging import StringIO import time -from tornado import ioloop from twisted.internet import defer import unittest from xml.etree import ElementTree @@ -36,7 +35,7 @@ from nova.endpoint import cloud FLAGS = flags.FLAGS -class CloudTestCase(test.BaseTestCase): +class CloudTestCase(test.TrialTestCase): def setUp(self): super(CloudTestCase, self).setUp() self.flags(connection_type='fake', @@ -51,18 +50,21 @@ class CloudTestCase(test.BaseTestCase): # set up a service self.compute = service.ComputeService() self.compute_consumer = rpc.AdapterConsumer(connection=self.conn, - topic=FLAGS.compute_topic, - proxy=self.compute) - self.injected.append(self.compute_consumer.attach_to_tornado(self.ioloop)) + topic=FLAGS.compute_topic, + proxy=self.compute) + self.injected.append(self.compute_consumer.attach_to_twisted()) try: manager.AuthManager().create_user('admin', 'admin', 'admin') except: pass admin = manager.AuthManager().get_user('admin') project = manager.AuthManager().create_project('proj', 'admin', 'proj') - self.context = api.APIRequestContext(handler=None,project=project,user=admin) + self.context = api.APIRequestContext(handler=None, + project=project, + user=admin) def tearDown(self): + super(CloudTestCase, self).tearDown() manager.AuthManager().delete_project('proj') manager.AuthManager().delete_user('admin') diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py index dece4b5d5..b5970d405 100644 --- a/nova/tests/objectstore_unittest.py +++ b/nova/tests/objectstore_unittest.py @@ -53,7 +53,7 @@ os.makedirs(os.path.join(OSS_TEMPDIR, 'images')) os.makedirs(os.path.join(OSS_TEMPDIR, 'buckets')) -class ObjectStoreTestCase(test.BaseTestCase): +class ObjectStoreTestCase(test.TrialTestCase): """Test objectstore API directly.""" def setUp(self): # pylint: disable-msg=C0103 diff --git a/nova/tests/rpc_unittest.py b/nova/tests/rpc_unittest.py index e12a28fbc..e11967987 100644 --- a/nova/tests/rpc_unittest.py +++ b/nova/tests/rpc_unittest.py @@ -30,7 +30,7 @@ from nova import test FLAGS = flags.FLAGS -class RpcTestCase(test.BaseTestCase): +class RpcTestCase(test.TrialTestCase): """Test cases for rpc""" def setUp(self): # pylint: disable-msg=C0103 super(RpcTestCase, self).setUp() @@ -40,7 +40,7 @@ class RpcTestCase(test.BaseTestCase): topic='test', proxy=self.receiver) - self.injected.append(self.consumer.attach_to_tornado(self.ioloop)) + self.injected.append(self.consumer.attach_to_twisted()) def test_call_succeed(self): """Get a value through rpc call""" -- cgit From bd550806950bcfdcd32172a896f04bc3b1a76392 Mon Sep 17 00:00:00 2001 From: andy Date: Wed, 8 Sep 2010 20:06:27 +0200 Subject: Missed an instance of attach_to_tornado. Kind of crappy because testing didn't catch it, the test code certainly appears to be testing those features, however. --- nova/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/rpc.py b/nova/rpc.py index d83dd7a7c..7a89ca8d0 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -264,7 +264,7 @@ def call(topic, msg): return d.callback(data['result']) consumer.register_callback(deferred_receive) - injected = consumer.attach_to_tornado() + injected = consumer.attach_to_twisted() # clean up after the injected listened and return x d.addCallback(lambda x: injected.stop() and x or x) -- cgit From 387671f9bc0299116ffbab7acfc47127afb989aa Mon Sep 17 00:00:00 2001 From: andy Date: Wed, 8 Sep 2010 22:43:54 +0200 Subject: Tests turn things into inlineCallbacks. This is to duplicate the old behavior of BaseTestCase and to generally prevent the bad situation in that tests appear to be passing when in fact they haven't run because @defer.inlineCallbacks was forgotten. --- nova/rpc.py | 1 - nova/test.py | 51 ++++++++++++++++++++++++++++++++++++++++---- nova/tests/cloud_unittest.py | 2 +- nova/tests/rpc_unittest.py | 3 +-- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/nova/rpc.py b/nova/rpc.py index 7a89ca8d0..210d3cccf 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -250,7 +250,6 @@ def call(topic, msg): msg_id = uuid.uuid4().hex msg.update({'_msg_id': msg_id}) LOG.debug("MSG_ID is %s" % (msg_id)) - conn = Connection.instance() d = defer.Deferred() consumer = DirectConsumer(connection=conn, msg_id=msg_id) diff --git a/nova/test.py b/nova/test.py index 5380d5743..1f4b33272 100644 --- a/nova/test.py +++ b/nova/test.py @@ -33,6 +33,7 @@ from twisted.trial import unittest from nova import fakerabbit from nova import flags +from nova import rpc FLAGS = flags.FLAGS @@ -63,22 +64,28 @@ class TrialTestCase(unittest.TestCase): self.stubs = stubout.StubOutForTesting() self.flag_overrides = {} self.injected = [] + self._monkeyPatchAttach() def tearDown(self): # pylint: disable-msg=C0103 """Runs after each test method to finalize/tear down test environment""" - super(TrialTestCase, self).tearDown() self.reset_flags() self.mox.UnsetStubs() self.stubs.UnsetAll() self.stubs.SmartUnsetAll() self.mox.VerifyAll() - + + rpc.Consumer.attach_to_twisted = self.originalAttach for x in self.injected: - x.stop() + try: + x.stop() + except AssertionError: + pass if FLAGS.fake_rabbit: fakerabbit.reset_all() + super(TrialTestCase, self).tearDown() + def flags(self, **kw): """Override flag variables for a test""" for k, v in kw.iteritems(): @@ -94,10 +101,46 @@ class TrialTestCase(unittest.TestCase): for k, v in self.flag_overrides.iteritems(): setattr(FLAGS, k, v) + def run(self, result=None): + test_method = getattr(self, self._testMethodName) + setattr(self, + self._testMethodName, + self._maybeInlineCallbacks(test_method, result)) + rv = super(TrialTestCase, self).run(result) + setattr(self, self._testMethodName, test_method) + return rv + + def _maybeInlineCallbacks(self, func, result): + def _wrapped(): + g = func() + if isinstance(g, defer.Deferred): + return g + if not hasattr(g, 'send'): + return defer.succeed(g) + + inlined = defer.inlineCallbacks(func) + d = inlined() + return d + _wrapped.func_name = func.func_name + return _wrapped + + def _monkeyPatchAttach(self): + self.originalAttach = rpc.Consumer.attach_to_twisted + def _wrapped(innerSelf): + rv = self.originalAttach(innerSelf) + self.injected.append(rv) + return rv + + _wrapped.func_name = self.originalAttach.func_name + rpc.Consumer.attach_to_twisted = _wrapped + class BaseTestCase(TrialTestCase): # TODO(jaypipes): Can this be moved into the TrialTestCase class? - """Base test case class for all unit tests.""" + """Base test case class for all unit tests. + + DEPRECATED: This is being removed once Tornado is gone, use TrialTestCase. + """ def setUp(self): # pylint: disable-msg=C0103 """Run before each test method to initialize test environment""" super(BaseTestCase, self).setUp() diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 0add53937..893717fe4 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -52,7 +52,7 @@ class CloudTestCase(test.TrialTestCase): self.compute_consumer = rpc.AdapterConsumer(connection=self.conn, topic=FLAGS.compute_topic, proxy=self.compute) - self.injected.append(self.compute_consumer.attach_to_twisted()) + self.compute_consumer.attach_to_twisted() try: manager.AuthManager().create_user('admin', 'admin', 'admin') diff --git a/nova/tests/rpc_unittest.py b/nova/tests/rpc_unittest.py index e11967987..1bb2405c7 100644 --- a/nova/tests/rpc_unittest.py +++ b/nova/tests/rpc_unittest.py @@ -39,8 +39,7 @@ class RpcTestCase(test.TrialTestCase): self.consumer = rpc.AdapterConsumer(connection=self.conn, topic='test', proxy=self.receiver) - - self.injected.append(self.consumer.attach_to_twisted()) + self.consumer.attach_to_twisted() def test_call_succeed(self): """Get a value through rpc call""" -- cgit From 0936150221713d775a5cad4a2e978980c32b21c1 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Mon, 13 Sep 2010 23:56:32 -0700 Subject: now we can run files - thanks vish --- bin/nova-manage | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/nova-manage b/bin/nova-manage index 6e5266767..26f75faf7 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -156,6 +156,11 @@ class ShellCommands(object): readline.parse_and_bind("tab:complete") code.interact() + def script(self, path): + """Runs the script from the specifed path with flags set properly. + arguments: path""" + exec(compile(open(path).read(), path, 'exec'), locals(), globals()) + class RoleCommands(object): """Class for managing roles.""" -- cgit From 6186634c0f8d6a44323fe1f7b2530528a539c64c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Sep 2010 11:12:04 -0700 Subject: bpython is amazing --- bin/nova-manage | 51 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 26f75faf7..d23818348 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -135,15 +135,48 @@ class VpnCommands(object): class ShellCommands(object): - def run(self): - "Runs a Python interactive interpreter. Tries to use IPython, if it's available." - try: - import IPython - # Explicitly pass an empty list as arguments, because otherwise IPython - # would use sys.argv from this script. - shell = IPython.Shell.IPShell(argv=[]) - shell.mainloop() - except ImportError: + def bpython(self): + """Runs a bpython shell. + + Falls back to Ipython/python shell if unavailable""" + self.run('bpython') + + def ipython(self): + """Runs an Ipython shell. + + Falls back to Python shell if unavailable""" + self.run('ipython') + + def python(self): + """Runs an python shell. + + Falls back to Python shell if unavailable""" + self.run('python') + + def run(self, shell=None): + """Runs a Python interactive interpreter. + + args: [shell=bpython]""" + if not shell: + shell = 'bpython' + + if shell == 'bpython': + try: + import bpython + bpython.embed() + except ImportError: + shell = 'ipython' + if shell == 'ipython': + try: + import IPython + # Explicitly pass an empty list as arguments, because otherwise IPython + # would use sys.argv from this script. + shell = IPython.Shell.IPShell(argv=[]) + shell.mainloop() + except ImportError: + shell = 'python' + + if shell == 'python': import code try: # Try activating rlcompleter, because it's handy. import readline -- cgit From b68b73d08155483d19f4088baa6a4ffe73ef5f1d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Sep 2010 14:36:06 -0700 Subject: typo s/an/a --- bin/nova-manage | 2 +- nova/tests/rpc_unittest.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 4ae2f20dc..c5c7322bf 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -149,7 +149,7 @@ class ShellCommands(object): self.run('ipython') def python(self): - """Runs an python shell. + """Runs a python shell. Falls back to Python shell if unavailable""" self.run('python') diff --git a/nova/tests/rpc_unittest.py b/nova/tests/rpc_unittest.py index e12a28fbc..f4d7b4b28 100644 --- a/nova/tests/rpc_unittest.py +++ b/nova/tests/rpc_unittest.py @@ -67,6 +67,17 @@ class RpcTestCase(test.BaseTestCase): except rpc.RemoteError as exc: self.assertEqual(int(exc.value), value) + def test_pass_object(self): + """Test that we can pass objects through rpc""" + class x(): + pass + obj = x() + x.foo = 'bar' + x.baz = 100 + + result = yield rpc.call('test', {"method": "echo", + "args": {"value": obj}}) + print result class TestReceiver(object): """Simple Proxy class so the consumer has methods to call -- cgit From d642716db42b229e879f6f4673f166beb8d55faa Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 21 Sep 2010 17:12:53 -0500 Subject: Added server index and detail differentiation --- nova/api/rackspace/__init__.py | 3 ++- nova/api/rackspace/servers.py | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index ac5365310..5f4020837 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -140,7 +140,8 @@ class APIRouter(wsgi.Router): def __init__(self): mapper = routes.Mapper() - mapper.resource("server", "servers", controller=servers.Controller()) + mapper.resource("server", "servers", controller=servers.Controller() + collection={'detail': 'GET'}) mapper.resource("image", "images", controller=images.Controller(), collection={'detail': 'GET'}) mapper.resource("flavor", "flavors", controller=flavors.Controller(), diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 5f685dbd4..3ba5af8cf 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -31,8 +31,9 @@ class Controller(base.Controller): 'application/xml': { "plurals": "servers", "attributes": { - "server": [ "id", "imageId", "flavorId", "hostId", "status", - "progress", "addresses", "metadata", "progress" ] + "server": [ "id", "imageId", "name", "flavorId", "hostId", + "status", "progress", "addresses", "metadata", + "progress" ] } } } @@ -41,7 +42,11 @@ class Controller(base.Controller): self.instdir = compute.InstanceDirectory() def index(self, req): - return [_entity_inst(instance_details(inst)) for inst in instdir.all] + allowed_keys = [ 'id', 'name'] + return [_entity_inst(inst, allowed_keys) for inst in instdir.all] + + def detail(self, req): + return [_entity_inst(inst) for inst in instdir.all] def show(self, req, id): inst = self.instdir.get(id) @@ -103,7 +108,7 @@ class Controller(base.Controller): inst.save() return _entity_inst(inst) - def _entity_inst(self, inst): + def _entity_inst(self, inst, allowed_keys=None): """ Maps everything to Rackspace-like attributes for return""" translated_keys = dict(metadata={}, status=state_description, @@ -119,4 +124,9 @@ class Controller(base.Controller): for key in filtered_keys:: del inst[key] + if allowed_keys: + for key in inst.keys(): + if key not in allowed_keys: + del inst[key] + return dict(server=inst) -- cgit From be214c0ecece6d9cffced02f397ba9ce42be6d9f Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 23 Sep 2010 16:45:30 -0500 Subject: whatever --- nova/tests/api/rackspace/servers.py | 13 +++++++++++-- nova/tests/api/rackspace/test_helper.py | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index 6d628e78a..2cfb8d45f 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -16,19 +16,25 @@ # under the License. import unittest - +import stubout from nova.api.rackspace import servers +import nova.api.rackspace from nova.tests.api.test_helper import * +from nova.tests.api.rackspace import test_helper class ServersTest(unittest.TestCase): def setUp(self): self.stubs = stubout.StubOutForTesting() + test_helper.FakeAuthManager.auth_data = {} + test_helper.FakeAuthDatabase.data = {} + test_helper.stub_out_auth(self.stubs) def tearDown(self): self.stubs.UnsetAll() def test_get_server_list(self): - pass + req = webob.Request.blank('/v1.0/servers') + req.get_response(nova.api.API()) def test_create_instance(self): pass @@ -56,3 +62,6 @@ class ServersTest(unittest.TestCase): def test_delete_server_instance(self): pass + +if __name__ == "__main__": + unittest.main() diff --git a/nova/tests/api/rackspace/test_helper.py b/nova/tests/api/rackspace/test_helper.py index be14e2de8..1fb2a19cc 100644 --- a/nova/tests/api/rackspace/test_helper.py +++ b/nova/tests/api/rackspace/test_helper.py @@ -3,6 +3,7 @@ import webob.dec import datetime from nova.wsgi import Router from nova import auth +import nova.api.rackspace.auth class Context(object): pass @@ -24,6 +25,27 @@ def fake_auth_init(self): self.auth = FakeAuthManager() self.host = 'foo' +def stub_out_auth(stubs): + def fake_auth_init(self, app): + self.application = app + + def fake_rate_init(self, app): + super(nova.api.rackspace.RateLimitingMiddleware, self).__init__(app) + self.application = app + + @webob.dec.wsgify + def fake_wsgi(self, req): + return self.application + + stubs.Set(nova.api.rackspace.AuthMiddleware, + '__init__', fake_auth_init) + stubs.Set(nova.api.rackspace.RateLimitingMiddleware, + '__init__', fake_rate_init) + stubs.Set(nova.api.rackspace.AuthMiddleware, + '__call__', fake_wsgi) + stubs.Set(nova.api.rackspace.RateLimitingMiddleware, + '__call__', fake_wsgi) + class FakeAuthDatabase(object): data = {} -- cgit From e8bac42e5fb7a4bdefaf50db210777516c049166 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 23 Sep 2010 18:00:27 -0700 Subject: Add multi region support for adminclient --- nova/adminclient.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/nova/adminclient.py b/nova/adminclient.py index 0ca32b1e5..77a09e652 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -20,8 +20,8 @@ Nova User API client library. """ import base64 - import boto +import httplib from boto.ec2.regioninfo import RegionInfo @@ -68,13 +68,13 @@ class UserRole(object): def __init__(self, connection=None): self.connection = connection self.role = None - + def __repr__(self): return 'UserRole:%s' % self.role def startElement(self, name, attrs, connection): return None - + def endElement(self, name, value, connection): if name == 'role': self.role = value @@ -128,20 +128,20 @@ class ProjectMember(object): def __init__(self, connection=None): self.connection = connection self.memberId = None - + def __repr__(self): return 'ProjectMember:%s' % self.memberId def startElement(self, name, attrs, connection): return None - + def endElement(self, name, value, connection): if name == 'member': self.memberId = value else: setattr(self, name, str(value)) - + class HostInfo(object): """ Information about a Nova Host, as parsed through SAX: @@ -171,17 +171,20 @@ class HostInfo(object): class NovaAdminClient(object): - def __init__(self, clc_ip='127.0.0.1', region='nova', access_key='admin', - secret_key='admin', **kwargs): - self.clc_ip = clc_ip + def __init__(self, clc_url='http://127.0.0.1:8773', region='nova', + access_key='admin', secret_key='admin', **kwargs): + parts = httplib.urlsplit(clc_url) + is_secure = parts.scheme == 'https' + ip, port = parts.netloc.split(':') + self.region = region self.access = access_key self.secret = secret_key self.apiconn = boto.connect_ec2(aws_access_key_id=access_key, aws_secret_access_key=secret_key, - is_secure=False, - region=RegionInfo(None, region, clc_ip), - port=8773, + is_secure=is_secure, + region=RegionInfo(None, region, ip), + port=port, path='/services/Admin', **kwargs) self.apiconn.APIVersion = 'nova' @@ -289,7 +292,7 @@ class NovaAdminClient(object): if project.projectname != None: return project - + def create_project(self, projectname, manager_user, description=None, member_users=None): """ @@ -322,7 +325,7 @@ class NovaAdminClient(object): Adds a user to a project. """ return self.modify_project_member(user, project, operation='add') - + def remove_project_member(self, user, project): """ Removes a user from a project. -- cgit From ea33870fd0dee4e96b4f2aa18cdad02b66cd4f57 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 24 Sep 2010 10:19:28 +0200 Subject: nova-api-new is no more. Don't attempt to install it. --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 1767b00f4..523a5c990 100644 --- a/setup.py +++ b/setup.py @@ -54,5 +54,4 @@ setup(name='nova', 'bin/nova-manage', 'bin/nova-network', 'bin/nova-objectstore', - 'bin/nova-api-new', 'bin/nova-volume']) -- cgit From 9abb45043c11125ecee36e44f939817bd03d70c4 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 24 Sep 2010 10:21:10 +0200 Subject: Install nova-scheduler. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 523a5c990..d420d3559 100644 --- a/setup.py +++ b/setup.py @@ -54,4 +54,5 @@ setup(name='nova', 'bin/nova-manage', 'bin/nova-network', 'bin/nova-objectstore', + 'bin/nova-scheduler', 'bin/nova-volume']) -- cgit From aa0af4b0a43f3eff3db1b1868a82ba65c5710f87 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Fri, 24 Sep 2010 17:42:55 -0700 Subject: Finished making admin client work for multi-region --- nova/adminclient.py | 59 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/nova/adminclient.py b/nova/adminclient.py index 77a09e652..8db5d9158 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -25,6 +25,12 @@ import httplib from boto.ec2.regioninfo import RegionInfo +DEFAULT_CLC_URL='http://127.0.0.1:8773' +DEFAULT_REGION='nova' +DEFAULT_ACCESS_KEY='admin' +DEFAULT_SECRET_KEY='admin' + + class UserInfo(object): """ Information about a Nova user, as parsed through SAX @@ -171,38 +177,57 @@ class HostInfo(object): class NovaAdminClient(object): - def __init__(self, clc_url='http://127.0.0.1:8773', region='nova', - access_key='admin', secret_key='admin', **kwargs): - parts = httplib.urlsplit(clc_url) - is_secure = parts.scheme == 'https' - ip, port = parts.netloc.split(':') + def __init__(self, clc_url=DEFAULT_CLC_URL, region=DEFAULT_REGION, + access_key=DEFAULT_ACCESS_KEY, secret_key=DEFAULT_SECRET_KEY, + **kwargs): + parts = self.split_clc_url(clc_url) + self.clc_url = clc_url self.region = region self.access = access_key self.secret = secret_key self.apiconn = boto.connect_ec2(aws_access_key_id=access_key, aws_secret_access_key=secret_key, - is_secure=is_secure, - region=RegionInfo(None, region, ip), - port=port, + is_secure=parts['is_secure'], + region=RegionInfo(None, + region, + parts['ip']), + port=parts['port'], path='/services/Admin', **kwargs) self.apiconn.APIVersion = 'nova' - def connection_for(self, username, project, **kwargs): + def connection_for(self, username, project, clc_url=None, region=None, + **kwargs): """ Returns a boto ec2 connection for the given username. """ + if not clc_url: + clc_url = self.clc_url + if not region: + region = self.region + parts = self.split_clc_url(clc_url) user = self.get_user(username) access_key = '%s:%s' % (user.accesskey, project) - return boto.connect_ec2( - aws_access_key_id=access_key, - aws_secret_access_key=user.secretkey, - is_secure=False, - region=RegionInfo(None, self.region, self.clc_ip), - port=8773, - path='/services/Cloud', - **kwargs) + return boto.connect_ec2(aws_access_key_id=access_key, + aws_secret_access_key=user.secretkey, + is_secure=parts['is_secure'], + region=RegionInfo(None, + self.region, + parts['ip']), + port=parts['port'], + path='/services/Cloud', + **kwargs) + + def split_clc_url(self, clc_url): + """ + Splits a cloud controller endpoint url. + """ + import logging; logging.debug('clc_url = %s', clc_url) + parts = httplib.urlsplit(clc_url) + is_secure = parts.scheme == 'https' + ip, port = parts.netloc.split(':') + return {'ip': ip, 'port': int(port), 'is_secure': is_secure} def get_users(self): """ grabs the list of all users """ -- cgit From 307b16447a16e438d78b8149418c0ef728c5300e Mon Sep 17 00:00:00 2001 From: Cerberus Date: Sat, 25 Sep 2010 13:00:19 -0500 Subject: Minor changes to be committed so trunk can be merged in --- nova/api/rackspace/servers.py | 26 +++++++++++++++++++++----- nova/tests/api/rackspace/servers.py | 5 +++-- nova/tests/api/rackspace/test_helper.py | 5 +++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 08b6768f9..9243f3ae6 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -22,11 +22,14 @@ from nova import rpc from nova import utils from nova import compute from nova.api.rackspace import base +from nova.api.rackspace import _id_translator from webob import exc from nova import flags FLAGS = flags.FLAGS +class ServersContext(object): pass + class Controller(base.Controller): _serialization_metadata = { 'application/xml': { @@ -39,12 +42,16 @@ class Controller(base.Controller): } } - def __init__(self): - self.instdir = compute.InstanceDirectory() + def __init__(self, db_driver=None): + self.context = ServersContext() + if not db_driver: + db_driver = FLAGS.db_driver + self.db = utils.import_object(db_driver) def index(self, req): - allowed_keys = [ 'id', 'name'] - return [_entity_inst(inst, allowed_keys) for inst in instdir.all] + unfiltered = [ 'id', 'name'] + instance_list = self.instance_get_all(self.context) + return [_entity_inst(inst, unfiltered) for inst in instance_list] def detail(self, req): return [_entity_inst(inst) for inst in instdir.all] @@ -79,12 +86,21 @@ class Controller(base.Controller): instance.save() return exc.HTTPNoContent() + def _id_translator(self): + service = nova.image.service.ImageService.load() + return _id_translator.RackspaceAPIIdTranslator( + "image", self.service.__class__.__name__) + def _build_server_instance(self, req): """Build instance data structure and save it to the data store.""" ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) inst = {} + + image_id = env['server']['imageId'] + opaque_id = self._id_translator.from_rs_id(image_id) + inst['name'] = env['server']['name'] - inst['image_id'] = env['server']['imageId'] + inst['image_id'] = opaque_id inst['instance_type'] = env['server']['flavorId'] inst['user_id'] = env['user']['id'] diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index 2cfb8d45f..6addc4614 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -33,8 +33,9 @@ class ServersTest(unittest.TestCase): self.stubs.UnsetAll() def test_get_server_list(self): - req = webob.Request.blank('/v1.0/servers') - req.get_response(nova.api.API()) + req = webob.Request.blank('/v1.0/servers/') + res = req.get_response(nova.api.API()) + print res def test_create_instance(self): pass diff --git a/nova/tests/api/rackspace/test_helper.py b/nova/tests/api/rackspace/test_helper.py index 1fb2a19cc..784f3e466 100644 --- a/nova/tests/api/rackspace/test_helper.py +++ b/nova/tests/api/rackspace/test_helper.py @@ -1,3 +1,4 @@ +from nova import utils import webob import webob.dec import datetime @@ -36,6 +37,9 @@ def stub_out_auth(stubs): @webob.dec.wsgify def fake_wsgi(self, req): return self.application + + def get_my_ip(): + return '127.0.0.1' stubs.Set(nova.api.rackspace.AuthMiddleware, '__init__', fake_auth_init) @@ -45,6 +49,7 @@ def stub_out_auth(stubs): '__call__', fake_wsgi) stubs.Set(nova.api.rackspace.RateLimitingMiddleware, '__call__', fake_wsgi) + stubs.Set(nova.utils, 'get_my_ip', get_my_ip) class FakeAuthDatabase(object): data = {} -- cgit From e627748aec6a4747e22975d6cd59c8f20bc00c70 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Sat, 25 Sep 2010 13:35:23 -0500 Subject: Modification of test stubbing to match new domain requirements for the router, and removal of the unnecessary rackspace base controller --- nova/api/rackspace/base.py | 24 ------------------------ nova/api/rackspace/flavors.py | 3 ++- nova/api/rackspace/images.py | 3 ++- nova/api/rackspace/servers.py | 29 ++++++++++++++++------------- nova/api/rackspace/sharedipgroups.py | 4 +++- nova/tests/api/rackspace/auth.py | 4 ++-- nova/tests/api/rackspace/servers.py | 4 +++- nova/tests/api/rackspace/test_helper.py | 30 +++++++++++++++++++----------- 8 files changed, 47 insertions(+), 54 deletions(-) delete mode 100644 nova/api/rackspace/base.py diff --git a/nova/api/rackspace/base.py b/nova/api/rackspace/base.py deleted file mode 100644 index 5e5bd6f54..000000000 --- a/nova/api/rackspace/base.py +++ /dev/null @@ -1,24 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from nova import wsgi - - -class Controller(wsgi.Controller): - """TODO(eday): Base controller for all rackspace controllers. What is this - for? Is this just Rackspace specific? """ - pass diff --git a/nova/api/rackspace/flavors.py b/nova/api/rackspace/flavors.py index 60b35c939..024011a71 100644 --- a/nova/api/rackspace/flavors.py +++ b/nova/api/rackspace/flavors.py @@ -17,9 +17,10 @@ from nova.api.rackspace import base from nova.compute import instance_types +from nova import wsgi from webob import exc -class Controller(base.Controller): +class Controller(wsgi.Controller): """Flavor controller for the Rackspace API.""" _serialization_metadata = { diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 2f3e928b9..9aaec52e2 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -16,11 +16,12 @@ # under the License. import nova.image.service +from nova import wsgi from nova.api.rackspace import base from nova.api.rackspace import _id_translator from webob import exc -class Controller(base.Controller): +class Controller(wsgi.Controller): _serialization_metadata = { 'application/xml': { diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 9243f3ae6..d94295861 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -21,16 +21,14 @@ from nova import flags from nova import rpc from nova import utils from nova import compute +from nova import flags from nova.api.rackspace import base from nova.api.rackspace import _id_translator from webob import exc -from nova import flags FLAGS = flags.FLAGS -class ServersContext(object): pass - -class Controller(base.Controller): +class Controller(wsgi.Controller): _serialization_metadata = { 'application/xml': { "plurals": "servers", @@ -43,27 +41,30 @@ class Controller(base.Controller): } def __init__(self, db_driver=None): - self.context = ServersContext() if not db_driver: db_driver = FLAGS.db_driver self.db = utils.import_object(db_driver) def index(self, req): unfiltered = [ 'id', 'name'] - instance_list = self.instance_get_all(self.context) - return [_entity_inst(inst, unfiltered) for inst in instance_list] + instance_list = self.db.instance_get_all(None) + res = [self._entity_inst(inst, unfiltered) for inst in \ + instance_list] + return self._entity_list(res) def detail(self, req): - return [_entity_inst(inst) for inst in instdir.all] + res = [self._entity_inst(inst) for inst in \ + self.db.instance_get_all(None)] + return self._entity_list(res) def show(self, req, id): - inst = self.instdir.get(id) + inst = self.db.instance_get(None, id) if inst: - return _entity_inst(inst) + return self._entity_inst(inst) raise exc.HTTPNotFound() def delete(self, req, id): - instance = self.instdir.get(id) + instance = self.db.instance_get(None, id) if not instance: return exc.HTTPNotFound() @@ -79,7 +80,7 @@ class Controller(base.Controller): return _entity_inst(inst) def update(self, req, id): - instance = self.instdir.get(instance_id) + instance = self.db.instance_get(None, id) if not instance: return exc.HTTPNotFound() instance.update(kwargs['server']) @@ -107,7 +108,6 @@ class Controller(base.Controller): inst['launch_time'] = ltime inst['mac_address'] = utils.generate_mac() - # TODO(dietz) Do we need any of these? inst['project_id'] = env['project']['id'] inst['reservation_id'] = reservation reservation = utils.generate_uid('r') @@ -125,6 +125,9 @@ class Controller(base.Controller): inst.save() return _entity_inst(inst) + def _entity_list(self, entities): + return dict(servers=entities) + def _entity_inst(self, inst, allowed_keys=None): """ Maps everything to Rackspace-like attributes for return""" diff --git a/nova/api/rackspace/sharedipgroups.py b/nova/api/rackspace/sharedipgroups.py index 986f11434..4d2d0ede1 100644 --- a/nova/api/rackspace/sharedipgroups.py +++ b/nova/api/rackspace/sharedipgroups.py @@ -15,4 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -class Controller(object): pass +from nova import wsgi + +class Controller(wsgi.Controller): pass diff --git a/nova/tests/api/rackspace/auth.py b/nova/tests/api/rackspace/auth.py index 429c22ad2..a6e10970f 100644 --- a/nova/tests/api/rackspace/auth.py +++ b/nova/tests/api/rackspace/auth.py @@ -15,8 +15,8 @@ class Test(unittest.TestCase): '__init__', test_helper.fake_auth_init) test_helper.FakeAuthManager.auth_data = {} test_helper.FakeAuthDatabase.data = {} - self.stubs.Set(nova.api.rackspace, 'RateLimitingMiddleware', - test_helper.FakeRateLimiter) + test_helper.stub_out_rate_limiting(self.stubs) + test_helper.stub_for_testing(self.stubs) def tearDown(self): self.stubs.UnsetAll() diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index 6addc4614..e50581632 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -27,13 +27,15 @@ class ServersTest(unittest.TestCase): self.stubs = stubout.StubOutForTesting() test_helper.FakeAuthManager.auth_data = {} test_helper.FakeAuthDatabase.data = {} + test_helper.stub_for_testing(self.stubs) + test_helper.stub_out_rate_limiting(self.stubs) test_helper.stub_out_auth(self.stubs) def tearDown(self): self.stubs.UnsetAll() def test_get_server_list(self): - req = webob.Request.blank('/v1.0/servers/') + req = webob.Request.blank('/v1.0/servers') res = req.get_response(nova.api.API()) print res diff --git a/nova/tests/api/rackspace/test_helper.py b/nova/tests/api/rackspace/test_helper.py index 784f3e466..971eaf20a 100644 --- a/nova/tests/api/rackspace/test_helper.py +++ b/nova/tests/api/rackspace/test_helper.py @@ -5,6 +5,9 @@ import datetime from nova.wsgi import Router from nova import auth import nova.api.rackspace.auth +from nova import flags + +FLAGS = flags.FLAGS class Context(object): pass @@ -26,30 +29,35 @@ def fake_auth_init(self): self.auth = FakeAuthManager() self.host = 'foo' +@webob.dec.wsgify +def fake_wsgi(self, req): + return self.application + def stub_out_auth(stubs): def fake_auth_init(self, app): self.application = app + + stubs.Set(nova.api.rackspace.AuthMiddleware, + '__init__', fake_auth_init) + stubs.Set(nova.api.rackspace.AuthMiddleware, + '__call__', fake_wsgi) +def stub_out_rate_limiting(stubs): def fake_rate_init(self, app): super(nova.api.rackspace.RateLimitingMiddleware, self).__init__(app) self.application = app - @webob.dec.wsgify - def fake_wsgi(self, req): - return self.application - - def get_my_ip(): - return '127.0.0.1' - - stubs.Set(nova.api.rackspace.AuthMiddleware, - '__init__', fake_auth_init) stubs.Set(nova.api.rackspace.RateLimitingMiddleware, '__init__', fake_rate_init) - stubs.Set(nova.api.rackspace.AuthMiddleware, - '__call__', fake_wsgi) + stubs.Set(nova.api.rackspace.RateLimitingMiddleware, '__call__', fake_wsgi) + +def stub_for_testing(stubs): + def get_my_ip(): + return '127.0.0.1' stubs.Set(nova.utils, 'get_my_ip', get_my_ip) + FLAGS.FAKE_subdomain = 'rs' class FakeAuthDatabase(object): data = {} -- cgit From 9f9f80a6282131ea944b6e3669527ea0c8c4705d Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Sun, 26 Sep 2010 14:45:27 -0700 Subject: Removed extra logging from debugging --- nova/adminclient.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/adminclient.py b/nova/adminclient.py index 8db5d9158..fc9fcfde0 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -223,7 +223,6 @@ class NovaAdminClient(object): """ Splits a cloud controller endpoint url. """ - import logging; logging.debug('clc_url = %s', clc_url) parts = httplib.urlsplit(clc_url) is_secure = parts.scheme == 'https' ip, port = parts.netloc.split(':') -- cgit From 1c978e8414b5841c4caf856c80f385026600f54e Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Mon, 27 Sep 2010 12:50:20 -0400 Subject: Support content type detection in serializer --- nova/api/rackspace/__init__.py | 2 +- nova/api/rackspace/servers.py | 5 ++--- nova/tests/api/wsgi_test.py | 33 ++++++++++++++++++++++++++++++--- nova/wsgi.py | 21 ++++++++++++++------- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index 5f4020837..736486733 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -140,7 +140,7 @@ class APIRouter(wsgi.Router): def __init__(self): mapper = routes.Mapper() - mapper.resource("server", "servers", controller=servers.Controller() + mapper.resource("server", "servers", controller=servers.Controller(), collection={'detail': 'GET'}) mapper.resource("image", "images", controller=images.Controller(), collection={'detail': 'GET'}) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 3ba5af8cf..761ce2895 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -29,7 +29,6 @@ FLAGS = flags.FLAGS class Controller(base.Controller): _serialization_metadata = { 'application/xml': { - "plurals": "servers", "attributes": { "server": [ "id", "imageId", "name", "flavorId", "hostId", "status", "progress", "addresses", "metadata", @@ -39,7 +38,7 @@ class Controller(base.Controller): } def __init__(self): - self.instdir = compute.InstanceDirectory() + self.instdir = None # TODO(cerberus): compute doesn't exist. compute.InstanceDirectory() def index(self, req): allowed_keys = [ 'id', 'name'] @@ -121,7 +120,7 @@ class Controller(base.Controller): 'reservation_id', 'project_id', 'launch_time', 'bridge_name', 'mac_address', 'user_id'] - for key in filtered_keys:: + for key in filtered_keys: del inst[key] if allowed_keys: diff --git a/nova/tests/api/wsgi_test.py b/nova/tests/api/wsgi_test.py index 786dc1bce..145b1bfee 100644 --- a/nova/tests/api/wsgi_test.py +++ b/nova/tests/api/wsgi_test.py @@ -91,6 +91,33 @@ class Test(unittest.TestCase): result = webob.Request.blank('/test/123').get_response(Router()) self.assertNotEqual(result.body, "123") - def test_serializer(self): - # TODO(eday): Placeholder for serializer testing. - pass + +class SerializerTest(unittest.TestCase): + + def match(self, url, accept, expect): + input_dict = dict(servers=dict(a=(2,3))) + expected_xml = '(2,3)' + expected_json = '{"servers":{"a":[2,3]}}' + req = webob.Request.blank(url, headers=dict(Accept=accept)) + result = wsgi.Serializer(req.environ).to_content_type(input_dict) + result = result.replace('\n', '').replace(' ', '') + if expect == 'xml': + self.assertEqual(result, expected_xml) + elif expect == 'json': + self.assertEqual(result, expected_json) + else: + raise "Bad expect value" + + def test_basic(self): + self.match('/servers/4.json', None, expect='json') + self.match('/servers/4', 'application/json', expect='json') + self.match('/servers/4', 'application/xml', expect='xml') + self.match('/servers/4.xml', None, expect='xml') + + def test_defaults_to_json(self): + self.match('/servers/4', None, expect='json') + self.match('/servers/4', 'text/html', expect='json') + + def test_suffix_takes_precedence_over_accept_header(self): + self.match('/servers/4.xml', 'application/json', expect='xml') + self.match('/servers/4.xml.', 'application/json', expect='json') diff --git a/nova/wsgi.py b/nova/wsgi.py index 8a4e2a9f4..ac319db40 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -29,6 +29,7 @@ import eventlet.wsgi eventlet.patcher.monkey_patch(all=False, socket=True) import routes import routes.middleware +import webob import webob.dec import webob.exc @@ -239,11 +240,19 @@ class Serializer(object): '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 {} - self._methods = { - 'application/json': self._to_json, - 'application/xml': self._to_xml} + req = webob.Request(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: + self.handler = self._to_json # default def to_content_type(self, data): """ @@ -251,9 +260,7 @@ class Serializer(object): 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 - return self._methods.get(mimetype, repr)(data) + return self.handler(data) def _to_json(self, data): import json -- cgit From 1d83acca365b13319bddbd628725d7b666879091 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 27 Sep 2010 12:58:35 -0500 Subject: More re-work around the ORM changes and testing --- nova/api/rackspace/servers.py | 47 +++++++++++++++++++------------------ nova/tests/api/rackspace/flavors.py | 15 +++++++++++- nova/tests/api/rackspace/servers.py | 37 ++++++++++++++++++++++------- 3 files changed, 67 insertions(+), 32 deletions(-) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index d94295861..7973fa770 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -16,6 +16,7 @@ # under the License. import time +from nova import wsgi from nova import db from nova import flags from nova import rpc @@ -29,13 +30,12 @@ from webob import exc FLAGS = flags.FLAGS class Controller(wsgi.Controller): + _serialization_metadata = { 'application/xml': { - "plurals": "servers", "attributes": { "server": [ "id", "imageId", "name", "flavorId", "hostId", - "status", "progress", "addresses", "metadata", - "progress" ] + "status", "progress", "progress" ] } } } @@ -46,21 +46,19 @@ class Controller(wsgi.Controller): self.db = utils.import_object(db_driver) def index(self, req): - unfiltered = [ 'id', 'name'] instance_list = self.db.instance_get_all(None) - res = [self._entity_inst(inst, unfiltered) for inst in \ - instance_list] + res = [self._entity_inst(inst)['server'] for inst in instance_list] return self._entity_list(res) def detail(self, req): - res = [self._entity_inst(inst) for inst in \ + res = [self._entity_detail(inst)['server'] for inst in \ self.db.instance_get_all(None)] return self._entity_list(res) def show(self, req, id): inst = self.db.instance_get(None, id) if inst: - return self._entity_inst(inst) + return self._entity_detail(inst) raise exc.HTTPNotFound() def delete(self, req, id): @@ -128,25 +126,28 @@ class Controller(wsgi.Controller): def _entity_list(self, entities): return dict(servers=entities) - def _entity_inst(self, inst, allowed_keys=None): + def _entity_detail(self, inst): """ Maps everything to Rackspace-like attributes for return""" + inst_dir = {} + + mapped_keys = dict(status='state_description', imageId='image_id', + flavorId='instance_type', name='name') - translated_keys = dict(metadata={}, status=state_description, - id=instance_id, imageId=image_id, flavorId=instance_type,) + for k,v in mapped_keys.iteritems(): + inst_dir[k] = inst[v] - for k,v in translated_keys.iteritems(): - inst[k] = inst[v] + inst_dir['addresses'] = dict(public=[], private=[]) + inst_dir['metadata'] = {} + inst_dir['hostId'] = '' - filtered_keys = ['instance_id', 'state_description', 'state', - 'reservation_id', 'project_id', 'launch_time', - 'bridge_name', 'mac_address', 'user_id'] + return dict(server=inst_dir) - for key in filtered_keys: - del inst[key] + def _entity_inst(self, inst): + """ Filters all model attributes save for id and name """ + return dict(server=dict(id=inst['id'], name=inst['name'])) - if allowed_keys: - for key in inst.keys(): - if key not in allowed_keys: - del inst[key] + def _to_rs_power_state(self, inst): + pass - return dict(server=inst) + def _from_rs_power_state(self, inst): + pass diff --git a/nova/tests/api/rackspace/flavors.py b/nova/tests/api/rackspace/flavors.py index fb8ba94a5..7bd1ea1c4 100644 --- a/nova/tests/api/rackspace/flavors.py +++ b/nova/tests/api/rackspace/flavors.py @@ -16,19 +16,32 @@ # under the License. import unittest +import stubout +import nova.api from nova.api.rackspace import flavors +from nova.tests.api.rackspace import test_helper from nova.tests.api.test_helper import * class FlavorsTest(unittest.TestCase): def setUp(self): self.stubs = stubout.StubOutForTesting() + test_helper.FakeAuthManager.auth_data = {} + test_helper.FakeAuthDatabase.data = {} + test_helper.stub_for_testing(self.stubs) + test_helper.stub_out_rate_limiting(self.stubs) + test_helper.stub_out_auth(self.stubs) def tearDown(self): self.stubs.UnsetAll() def test_get_flavor_list(self): - pass + req = webob.Request.blank('/v1.0/flavors') + res = req.get_response(nova.api.API()) + print res def test_get_flavor_by_id(self): pass + +if __name__ == '__main__': + unittest.main() diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index e50581632..0f3483207 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -17,10 +17,25 @@ import unittest import stubout -from nova.api.rackspace import servers import nova.api.rackspace +import nova.db.api +from nova import flags +from nova.api.rackspace import servers +from nova.db.sqlalchemy.models import Instance from nova.tests.api.test_helper import * from nova.tests.api.rackspace import test_helper +from nova import db + +FLAGS = flags.FLAGS + +def return_server(context, id): + return stub_instance(id) + +def return_servers(context): + return [stub_instance(i) for i in xrange(5)] + +def stub_instance(id): + return Instance(id=id, state=0, ) class ServersTest(unittest.TestCase): def setUp(self): @@ -30,26 +45,32 @@ class ServersTest(unittest.TestCase): test_helper.stub_for_testing(self.stubs) test_helper.stub_out_rate_limiting(self.stubs) test_helper.stub_out_auth(self.stubs) + self.stubs.Set(nova.db.api, 'instance_get_all', return_servers) + self.stubs.Set(nova.db.api, 'instance_get', return_server) def tearDown(self): self.stubs.UnsetAll() - def test_get_server_list(self): - req = webob.Request.blank('/v1.0/servers') + def test_get_server_by_id(self): + req = webob.Request.blank('/v1.0/servers/1') res = req.get_response(nova.api.API()) print res - def test_create_instance(self): + def test_get_backup_schedule(self): pass - def test_get_server_by_id(self): - pass + def test_get_server_list(self): + req = webob.Request.blank('/v1.0/servers') + res = req.get_response(nova.api.API()) + print res - def test_get_backup_schedule(self): + def test_create_instance(self): pass def test_get_server_details(self): - pass + req = webob.Request.blank('/v1.0/servers/detail') + res = req.get_response(nova.api.API()) + print res def test_get_server_ips(self): pass -- cgit From 0e6c3b6034ef4927e381b231bf120a4980512c4e Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 27 Sep 2010 17:01:37 -0500 Subject: Power state mapping --- nova/api/rackspace/servers.py | 42 +++++++++++++++++++++++-------------- nova/tests/api/rackspace/servers.py | 1 + nova/tests/api/test_helper.py | 1 + 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 7973fa770..53824ee1b 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -14,8 +14,9 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -import time +import time +import nova.image.service from nova import wsgi from nova import db from nova import flags @@ -23,6 +24,7 @@ from nova import rpc from nova import utils from nova import compute from nova import flags +from nova.compute import power_state from nova.api.rackspace import base from nova.api.rackspace import _id_translator from webob import exc @@ -31,6 +33,16 @@ FLAGS = flags.FLAGS class Controller(wsgi.Controller): + _power_mapping = { + power_state.NOSTATE: 'build', + power_state.RUNNING: 'active', + power_state.BLOCKED: 'active', + power_state.PAUSED: 'suspended', + power_state.SHUTDOWN: 'active', + power_state.SHUTOFF: 'active', + power_state.CRASHED: 'error' + } + _serialization_metadata = { 'application/xml': { "attributes": { @@ -74,7 +86,7 @@ class Controller(wsgi.Controller): rpc.cast( FLAGS.compute_topic, { "method": "run_instance", - "args": {"instance_id": inst.instance_id}}) + "args": {"instance_id": inst.id}}) return _entity_inst(inst) def update(self, req, id): @@ -121,33 +133,31 @@ class Controller(wsgi.Controller): 'default')['bridge_name'] inst.save() - return _entity_inst(inst) + return inst + + def _filter_params(self, inst_dict): + pass def _entity_list(self, entities): return dict(servers=entities) def _entity_detail(self, inst): """ Maps everything to Rackspace-like attributes for return""" - inst_dir = {} + inst_dict = {} - mapped_keys = dict(status='state_description', imageId='image_id', + mapped_keys = dict(status='state', imageId='image_id', flavorId='instance_type', name='name') for k,v in mapped_keys.iteritems(): - inst_dir[k] = inst[v] + inst_dict[k] = inst[v] - inst_dir['addresses'] = dict(public=[], private=[]) - inst_dir['metadata'] = {} - inst_dir['hostId'] = '' + inst_dict['status'] = Controller._power_mapping[inst_dict['status']] + inst_dict['addresses'] = dict(public=[], private=[]) + inst_dict['metadata'] = {} + inst_dict['hostId'] = '' - return dict(server=inst_dir) + return dict(server=inst_dict) def _entity_inst(self, inst): """ Filters all model attributes save for id and name """ return dict(server=dict(id=inst['id'], name=inst['name'])) - - def _to_rs_power_state(self, inst): - pass - - def _from_rs_power_state(self, inst): - pass diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index 0f3483207..dd54a6ac8 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -53,6 +53,7 @@ class ServersTest(unittest.TestCase): def test_get_server_by_id(self): req = webob.Request.blank('/v1.0/servers/1') + req.headers['content-type'] = 'application/json' res = req.get_response(nova.api.API()) print res diff --git a/nova/tests/api/test_helper.py b/nova/tests/api/test_helper.py index 8151a4af6..d0a2cc027 100644 --- a/nova/tests/api/test_helper.py +++ b/nova/tests/api/test_helper.py @@ -1,4 +1,5 @@ import webob.dec +from nova import wsgi class APIStub(object): """Class to verify request and mark it was called.""" -- cgit From 2f72b2a9fc9fee508b16c0b96285124279ef89ca Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 28 Sep 2010 00:23:49 -0500 Subject: More cleanup, backup_schedules controller, server details and the beginnings of the servers action route --- nova/api/rackspace/__init__.py | 17 +++++-- nova/api/rackspace/backup_schedules.py | 37 ++++++++++++++ nova/api/rackspace/flavors.py | 1 - nova/api/rackspace/images.py | 1 - nova/api/rackspace/servers.py | 22 +++++---- nova/db/sqlalchemy/models.py | 4 +- nova/tests/api/rackspace/servers.py | 85 ++++++++++++++++++++++++++++----- nova/tests/api/rackspace/test_helper.py | 1 + 8 files changed, 141 insertions(+), 27 deletions(-) create mode 100644 nova/api/rackspace/backup_schedules.py diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index 63b0edc6a..a10a9c6df 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -35,6 +35,7 @@ from nova.api.rackspace import flavors from nova.api.rackspace import images from nova.api.rackspace import ratelimiting from nova.api.rackspace import servers +from nova.api.rackspace import backup_schedules from nova.api.rackspace import sharedipgroups from nova.auth import manager @@ -67,8 +68,10 @@ class AuthMiddleware(wsgi.Middleware): if not user: return webob.exc.HTTPUnauthorized() - context = {'user': user} - req.environ['nova.context'] = context + + if not req.environ.has_key('nova.context'): + req.environ['nova.context'] = {} + req.environ['nova.context']['user'] = user return self.application class RateLimitingMiddleware(wsgi.Middleware): @@ -146,11 +149,19 @@ class APIRouter(wsgi.Router): def __init__(self): mapper = routes.Mapper() mapper.resource("server", "servers", controller=servers.Controller(), - collection={'detail': 'GET'}) + collection={ 'detail': 'GET'}, + member={'action':'POST'}) + + mapper.resource("backup_schedule", "backup_schedules", + controller=backup_schedules.Controller(), + parent_resource=dict(member_name='server', + collection_name = 'servers')) + mapper.resource("image", "images", controller=images.Controller(), collection={'detail': 'GET'}) mapper.resource("flavor", "flavors", controller=flavors.Controller(), collection={'detail': 'GET'}) mapper.resource("sharedipgroup", "sharedipgroups", controller=sharedipgroups.Controller()) + super(APIRouter, self).__init__(mapper) diff --git a/nova/api/rackspace/backup_schedules.py b/nova/api/rackspace/backup_schedules.py new file mode 100644 index 000000000..a18dfb87c --- /dev/null +++ b/nova/api/rackspace/backup_schedules.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import time +import nova.image.service +from nova import wsgi +from nova.api.rackspace import _id_translator +from webob import exc + +class Controller(wsgi.Controller): + def __init__(self): + pass + + def index(self, req, server_id): + return exc.HTTPNotFound() + + def create(self, req, server_id): + """ No actual update method required, since the existing API allows + both create and update through a POST """ + return exc.HTTPNotFound() + + def delete(self, req, server_id): + return exc.HTTPNotFound() diff --git a/nova/api/rackspace/flavors.py b/nova/api/rackspace/flavors.py index 024011a71..3bcf170e5 100644 --- a/nova/api/rackspace/flavors.py +++ b/nova/api/rackspace/flavors.py @@ -15,7 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -from nova.api.rackspace import base from nova.compute import instance_types from nova import wsgi from webob import exc diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 9aaec52e2..10e15de3f 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -17,7 +17,6 @@ import nova.image.service from nova import wsgi -from nova.api.rackspace import base from nova.api.rackspace import _id_translator from webob import exc diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 53824ee1b..d825e734e 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -25,7 +25,6 @@ from nova import utils from nova import compute from nova import flags from nova.compute import power_state -from nova.api.rackspace import base from nova.api.rackspace import _id_translator from webob import exc @@ -63,11 +62,12 @@ class Controller(wsgi.Controller): return self._entity_list(res) def detail(self, req): - res = [self._entity_detail(inst)['server'] for inst in \ + res = [self._entity_detail(inst)['server'] for inst in self.db.instance_get_all(None)] return self._entity_list(res) def show(self, req, id): + user = req.environ['nova.context']['user'] inst = self.db.instance_get(None, id) if inst: return self._entity_detail(inst) @@ -75,10 +75,9 @@ class Controller(wsgi.Controller): def delete(self, req, id): instance = self.db.instance_get(None, id) - if not instance: return exc.HTTPNotFound() - instance.destroy() + self.db.instance_destroy(None, id) return exc.HTTPAccepted() def create(self, req): @@ -93,10 +92,17 @@ class Controller(wsgi.Controller): instance = self.db.instance_get(None, id) if not instance: return exc.HTTPNotFound() - instance.update(kwargs['server']) - instance.save() + + attrs = req.environ['nova.context'].get('model_attributes', None) + if attrs: + self.db.instance_update(None, id, attrs) return exc.HTTPNoContent() + def action(self, req, id): + """ multi-purpose method used to reboot, rebuild, and + resize a server """ + return {} + def _id_translator(self): service = nova.image.service.ImageService.load() return _id_translator.RackspaceAPIIdTranslator( @@ -132,7 +138,7 @@ class Controller(wsgi.Controller): inst['project_id'], 'default')['bridge_name'] - inst.save() + self.db.instance_create(None, inst) return inst def _filter_params(self, inst_dict): @@ -146,7 +152,7 @@ class Controller(wsgi.Controller): inst_dict = {} mapped_keys = dict(status='state', imageId='image_id', - flavorId='instance_type', name='name') + flavorId='instance_type', name='name', id='id') for k,v in mapped_keys.iteritems(): inst_dict[k] = inst[v] diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f6ba7953f..47c505f25 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -209,12 +209,14 @@ class Instance(BASE, NovaBase): @property def name(self): - return self.str_id + return self.server_name or self.str_id image_id = Column(String(255)) kernel_id = Column(String(255)) ramdisk_id = Column(String(255)) + server_name = Column(String(255)) + # image_id = Column(Integer, ForeignKey('images.id'), nullable=True) # kernel_id = Column(Integer, ForeignKey('images.id'), nullable=True) # ramdisk_id = Column(Integer, ForeignKey('images.id'), nullable=True) diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index dd54a6ac8..0ef0f4256 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import unittest import stubout import nova.api.rackspace @@ -35,7 +36,9 @@ def return_servers(context): return [stub_instance(i) for i in xrange(5)] def stub_instance(id): - return Instance(id=id, state=0, ) + return Instance( + id=id, state=0, image_id=10, server_name='server%s'%id + ) class ServersTest(unittest.TestCase): def setUp(self): @@ -53,9 +56,10 @@ class ServersTest(unittest.TestCase): def test_get_server_by_id(self): req = webob.Request.blank('/v1.0/servers/1') - req.headers['content-type'] = 'application/json' res = req.get_response(nova.api.API()) - print res + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['id'], '1') + self.assertEqual(res_dict['server']['name'], 'server1') def test_get_backup_schedule(self): pass @@ -63,30 +67,85 @@ class ServersTest(unittest.TestCase): def test_get_server_list(self): req = webob.Request.blank('/v1.0/servers') res = req.get_response(nova.api.API()) - print res + res_dict = json.loads(res.body) + + i = 0 + for s in res_dict['servers']: + self.assertEqual(s['id'], i) + self.assertEqual(s['name'], 'server%d'%i) + self.assertEqual(s.get('imageId', None), None) + i += 1 def test_create_instance(self): pass - def test_get_server_details(self): - req = webob.Request.blank('/v1.0/servers/detail') - res = req.get_response(nova.api.API()) - print res + def test_update_server_password(self): + pass - def test_get_server_ips(self): + def test_update_server_name(self): pass + def test_create_backup_schedules(self): + req = webob.Request.blank('/v1.0/servers/1/backup_schedules') + req.method = 'POST' + res = req.get_response(nova.api.API()) + self.assertEqual(res.status, '404 Not Found') + + def test_delete_backup_schedules(self): + req = webob.Request.blank('/v1.0/servers/1/backup_schedules') + req.method = 'DELETE' + res = req.get_response(nova.api.API()) + self.assertEqual(res.status, '404 Not Found') + + def test_get_server_backup_schedules(self): + req = webob.Request.blank('/v1.0/servers/1/backup_schedules') + res = req.get_response(nova.api.API()) + self.assertEqual(res.status, '404 Not Found') + + def test_get_all_server_details(self): + req = webob.Request.blank('/v1.0/servers/detail') + res = req.get_response(nova.api.API()) + res_dict = json.loads(res.body) + + i = 0 + for s in res_dict['servers']: + self.assertEqual(s['id'], i) + self.assertEqual(s['name'], 'server%d'%i) + self.assertEqual(s['imageId'], 10) + i += 1 + def test_server_reboot(self): - pass + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + res = req.get_response(nova.api.API()) + res_dict = json.loads(res.body) def test_server_rebuild(self): - pass + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + res = req.get_response(nova.api.API()) + res_dict = json.loads(res.body) def test_server_resize(self): - pass + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + res = req.get_response(nova.api.API()) + res_dict = json.loads(res.body) def test_delete_server_instance(self): - pass + req = webob.Request.blank('/v1.0/servers/1') + req.method = 'DELETE' + + self.server_delete_called = False + def instance_destroy_mock(context, id): + self.server_delete_called = True + + self.stubs.Set(nova.db.api, 'instance_destroy', + instance_destroy_mock) + + res = req.get_response(nova.api.API()) + self.assertEqual(res.status, '202 Accepted') + self.assertEqual(self.server_delete_called, True) if __name__ == "__main__": unittest.main() diff --git a/nova/tests/api/rackspace/test_helper.py b/nova/tests/api/rackspace/test_helper.py index 971eaf20a..d44b8c30e 100644 --- a/nova/tests/api/rackspace/test_helper.py +++ b/nova/tests/api/rackspace/test_helper.py @@ -31,6 +31,7 @@ def fake_auth_init(self): @webob.dec.wsgify def fake_wsgi(self, req): + req.environ['nova.context'] = dict(user=dict(id=1)) return self.application def stub_out_auth(stubs): -- cgit From 7e25838ea1965231df09f29675fc3ab40e194483 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 28 Sep 2010 00:44:32 -0500 Subject: db api call to get instances by user and user checking in each of the server actions --- nova/api/rackspace/servers.py | 29 +++++++++++++++++++---------- nova/db/api.py | 3 +++ nova/db/sqlalchemy/api.py | 7 +++++++ nova/tests/api/rackspace/servers.py | 12 ++++++++---- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index d825e734e..becac9140 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -57,28 +57,32 @@ class Controller(wsgi.Controller): self.db = utils.import_object(db_driver) def index(self, req): - instance_list = self.db.instance_get_all(None) + user_id = req.environ['nova.context']['user']['id'] + instance_list = self.db.instance_get_all_by_user(None, user_id) res = [self._entity_inst(inst)['server'] for inst in instance_list] return self._entity_list(res) def detail(self, req): + user_id = req.environ['nova.context']['user']['id'] res = [self._entity_detail(inst)['server'] for inst in - self.db.instance_get_all(None)] + self.db.instance_get_all_by_user(None, user_id)] return self._entity_list(res) def show(self, req, id): - user = req.environ['nova.context']['user'] + user_id = req.environ['nova.context']['user']['id'] inst = self.db.instance_get(None, id) if inst: - return self._entity_detail(inst) + if inst.user_id == user_id: + return self._entity_detail(inst) raise exc.HTTPNotFound() def delete(self, req, id): + user_id = req.environ['nova.context']['user']['id'] instance = self.db.instance_get(None, id) - if not instance: - return exc.HTTPNotFound() - self.db.instance_destroy(None, id) - return exc.HTTPAccepted() + if instance and instance['user_id'] == user_id: + self.db.instance_destroy(None, id) + return exc.HTTPAccepted() + return exc.HTTPNotFound() def create(self, req): inst = self._build_server_instance(req) @@ -95,7 +99,7 @@ class Controller(wsgi.Controller): attrs = req.environ['nova.context'].get('model_attributes', None) if attrs: - self.db.instance_update(None, id, attrs) + self.db.instance_update(None, id, self._filter_params(attrs)) return exc.HTTPNoContent() def action(self, req, id): @@ -142,7 +146,12 @@ class Controller(wsgi.Controller): return inst def _filter_params(self, inst_dict): - pass + keys = ['name', 'adminPass'] + new_attrs = {} + for k in keys: + if inst_dict.has_key(k): + new_attrs[k] = inst_dict[k] + return new_attrs def _entity_list(self, entities): return dict(servers=entities) diff --git a/nova/db/api.py b/nova/db/api.py index c1cb1953a..b0b8f234d 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -251,6 +251,9 @@ def instance_get_all(context): """Get all instances.""" return IMPL.instance_get_all(context) +def instance_get_all_by_user(context, user_id): + """Get all instances.""" + return IMPL.instance_get_all(context, user_id) def instance_get_by_project(context, project_id): """Get all instance belonging to a project.""" diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 2b0dd6ea6..5c1ceee92 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -389,6 +389,13 @@ def instance_get_all(context): ).filter_by(deleted=_deleted(context) ).all() +def instance_get_all_by_user(context, user_id): + session = get_session() + return session.query(models.Instance + ).options(joinedload_all('fixed_ip.floating_ips') + ).filter_by(deleted=_deleted(context) + ).filter_by(user_id=user_id + ).all() def instance_get_by_project(context, project_id): session = get_session() diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index 0ef0f4256..674dab0b6 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -32,12 +32,14 @@ FLAGS = flags.FLAGS def return_server(context, id): return stub_instance(id) -def return_servers(context): - return [stub_instance(i) for i in xrange(5)] +def return_servers(context, user_id=1): + return [stub_instance(i, user_id) for i in xrange(5)] -def stub_instance(id): + +def stub_instance(id, user_id=1): return Instance( - id=id, state=0, image_id=10, server_name='server%s'%id + id=id, state=0, image_id=10, server_name='server%s'%id, + user_id=user_id ) class ServersTest(unittest.TestCase): @@ -50,6 +52,8 @@ class ServersTest(unittest.TestCase): test_helper.stub_out_auth(self.stubs) self.stubs.Set(nova.db.api, 'instance_get_all', return_servers) self.stubs.Set(nova.db.api, 'instance_get', return_server) + self.stubs.Set(nova.db.api, 'instance_get_all_by_user', + return_servers) def tearDown(self): self.stubs.UnsetAll() -- cgit From fd41a784ccee500ae8a36311ad3c80963e866b31 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 28 Sep 2010 12:54:17 -0400 Subject: Add Serializer.deserialize(xml_or_json_string) --- nova/tests/api/wsgi_test.py | 24 +++++++++++++++++++++ nova/wsgi.py | 51 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/nova/tests/api/wsgi_test.py b/nova/tests/api/wsgi_test.py index 145b1bfee..9425b01d0 100644 --- a/nova/tests/api/wsgi_test.py +++ b/nova/tests/api/wsgi_test.py @@ -121,3 +121,27 @@ class SerializerTest(unittest.TestCase): def test_suffix_takes_precedence_over_accept_header(self): self.match('/servers/4.xml', 'application/json', expect='xml') self.match('/servers/4.xml.', 'application/json', expect='json') + + def test_deserialize(self): + xml = """ + + 123 + 1 + 1 + + """.strip() + as_dict = dict(a={ + 'a1': '1', + 'a2': '2', + 'bs': ['1', '2', '3', {'c': dict(c1='1')}], + 'd': {'e': '1'}, + 'f': '1'}) + metadata = {'application/xml': dict(plurals={'bs': 'b', 'ts': 't'})} + serializer = wsgi.Serializer({}, metadata) + self.assertEqual(serializer.deserialize(xml), as_dict) + + def test_deserialize_empty_xml(self): + xml = """""" + as_dict = {"a": {}} + serializer = wsgi.Serializer({}) + self.assertEqual(serializer.deserialize(xml), as_dict) diff --git a/nova/wsgi.py b/nova/wsgi.py index ac319db40..da9374542 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -21,8 +21,10 @@ Utility methods for working with WSGI servers """ +import json import logging import sys +from xml.dom import minidom import eventlet import eventlet.wsgi @@ -231,7 +233,7 @@ class Controller(object): class Serializer(object): """ - Serializes a dictionary to a Content Type specified by a WSGI environment. + Serializes and deserializes dictionaries to certain MIME types. """ def __init__(self, environ, metadata=None): @@ -256,21 +258,58 @@ class Serializer(object): 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. + 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. """ return self.handler(data) + def deserialize(self, datastring): + """ + Deserialize a string to a dictionary. + + The string must be in the format of a supported MIME type. + """ + datastring = datastring.strip() + is_xml = (datastring[0] == '<') + if not is_xml: + return json.loads(datastring) + return self._from_xml(datastring) + + def _from_xml(self, datastring): + xmldata = self.metadata.get('application/xml', {}) + plurals = set(xmldata.get('plurals', {})) + node = minidom.parseString(datastring).childNodes[0] + return {node.nodeName: self._from_xml_node(node, plurals)} + + def _from_xml_node(self, node, listnames): + """ + Convert a minidom node to a simple Python type. + + listnames is a collection of names of XML nodes whose subnodes should + be considered list items. + """ + if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3: + return node.childNodes[0].nodeValue + elif node.nodeName in listnames: + return [self._from_xml_node(n, listnames) for n in node.childNodes] + else: + result = dict() + for attr in node.attributes.keys(): + result[attr] = node.attributes[attr].nodeValue + for child in node.childNodes: + if child.nodeType != node.TEXT_NODE: + result[child.nodeName] = self._from_xml_node(child, listnames) + return result + def _to_json(self, data): - import json return json.dumps(data) def _to_xml(self, data): 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=' ') -- cgit From 663ed27a2d7b3cb3a5290e0516eb8d602d7e65ba Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 28 Sep 2010 12:56:01 -0500 Subject: Testing testing testing --- nova/api/rackspace/_id_translator.py | 2 +- nova/api/rackspace/servers.py | 22 +++++++++++++++----- nova/tests/api/rackspace/servers.py | 37 +++++++++++++++++++++++++++------ nova/tests/api/rackspace/test_helper.py | 22 ++++++++++++++++++-- 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/nova/api/rackspace/_id_translator.py b/nova/api/rackspace/_id_translator.py index aec5fb6a5..333aa8434 100644 --- a/nova/api/rackspace/_id_translator.py +++ b/nova/api/rackspace/_id_translator.py @@ -37,6 +37,6 @@ class RackspaceAPIIdTranslator(object): # every int id be used.) return int(self._store.hget(self._fwd_key, str(opaque_id))) - def from_rs_id(self, strategy_name, rs_id): + def from_rs_id(self, rs_id): """Convert a Rackspace id to a strategy-specific one.""" return self._store.hget(self._rev_key, rs_id) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index becac9140..dec369ef0 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -85,7 +85,11 @@ class Controller(wsgi.Controller): return exc.HTTPNotFound() def create(self, req): + if not req.environ.has_key('inst_dict'): + return exc.HTTPUnprocessableEntity() + inst = self._build_server_instance(req) + rpc.cast( FLAGS.compute_topic, { "method": "run_instance", @@ -93,6 +97,9 @@ class Controller(wsgi.Controller): return _entity_inst(inst) def update(self, req, id): + if not req.environ.has_key('inst_dict'): + return exc.HTTPUnprocessableEntity() + instance = self.db.instance_get(None, id) if not instance: return exc.HTTPNotFound() @@ -105,25 +112,30 @@ class Controller(wsgi.Controller): def action(self, req, id): """ multi-purpose method used to reboot, rebuild, and resize a server """ - return {} + if not req.environ.has_key('inst_dict'): + return exc.HTTPUnprocessableEntity() - def _id_translator(self): + def translator_instance(self): service = nova.image.service.ImageService.load() return _id_translator.RackspaceAPIIdTranslator( - "image", self.service.__class__.__name__) + "image", service.__class__.__name__) def _build_server_instance(self, req): """Build instance data structure and save it to the data store.""" ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) inst = {} + env = req.environ['inst_dict'] + image_id = env['server']['imageId'] - opaque_id = self._id_translator.from_rs_id(image_id) + opaque_id = self.translator_instance().from_rs_id(image_id) inst['name'] = env['server']['name'] inst['image_id'] = opaque_id inst['instance_type'] = env['server']['flavorId'] - inst['user_id'] = env['user']['id'] + + user_id = req.environ['nova.context']['user']['id'] + inst['user_id'] = user_id inst['launch_time'] = ltime inst['mac_address'] = utils.generate_mac() diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index 674dab0b6..a22736dd2 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -80,9 +80,19 @@ class ServersTest(unittest.TestCase): self.assertEqual(s.get('imageId', None), None) i += 1 - def test_create_instance(self): - pass - + #def test_create_instance(self): + # test_helper.stub_out_image_translator(self.stubs) + # body = dict(server=dict( + # name='server_test', imageId=2, flavorId=2, metadata={}, + # personality = {} + # )) + # req = webob.Request.blank('/v1.0/servers') + # req.method = 'POST' + # req.body = json.dumps(body) + + # res = req.get_response(nova.api.API()) + + # print res def test_update_server_password(self): pass @@ -119,22 +129,37 @@ class ServersTest(unittest.TestCase): i += 1 def test_server_reboot(self): + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality = {} + )) req = webob.Request.blank('/v1.0/servers/1/action') req.method = 'POST' + req.content_type= 'application/json' + req.body = json.dumps(body) res = req.get_response(nova.api.API()) - res_dict = json.loads(res.body) def test_server_rebuild(self): + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality = {} + )) req = webob.Request.blank('/v1.0/servers/1/action') req.method = 'POST' + req.content_type= 'application/json' + req.body = json.dumps(body) res = req.get_response(nova.api.API()) - res_dict = json.loads(res.body) def test_server_resize(self): + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality = {} + )) req = webob.Request.blank('/v1.0/servers/1/action') req.method = 'POST' + req.content_type= 'application/json' + req.body = json.dumps(body) res = req.get_response(nova.api.API()) - res_dict = json.loads(res.body) def test_delete_server_instance(self): req = webob.Request.blank('/v1.0/servers/1') diff --git a/nova/tests/api/rackspace/test_helper.py b/nova/tests/api/rackspace/test_helper.py index d44b8c30e..09ff26ac7 100644 --- a/nova/tests/api/rackspace/test_helper.py +++ b/nova/tests/api/rackspace/test_helper.py @@ -1,10 +1,12 @@ -from nova import utils +import json import webob import webob.dec import datetime +import nova.api.rackspace.auth +import nova.api.rackspace._id_translator from nova.wsgi import Router from nova import auth -import nova.api.rackspace.auth +from nova import utils from nova import flags FLAGS = flags.FLAGS @@ -32,8 +34,24 @@ def fake_auth_init(self): @webob.dec.wsgify def fake_wsgi(self, req): req.environ['nova.context'] = dict(user=dict(id=1)) + if req.body: + req.environ['inst_dict'] = json.loads(req.body) return self.application +def stub_out_image_translator(stubs): + class FakeTranslator(object): + def __init__(self, id_type, service_name): + pass + + def to_rs_id(self, id): + return id + + def from_rs_id(self, id): + return id + + stubs.Set(nova.api.rackspace._id_translator, + 'RackspaceAPIIdTranslator', FakeTranslator) + def stub_out_auth(stubs): def fake_auth_init(self, app): self.application = app -- cgit From 7ebf1e292b4840e0da4190d2aaf3fa8fc5439846 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Tue, 28 Sep 2010 11:49:20 -0700 Subject: Implemented random instance and volume strings for ec2 api --- nova/api/ec2/cloud.py | 30 +++++++++++++++--------------- nova/db/api.py | 12 ++++++------ nova/db/sqlalchemy/api.py | 29 ++++++++++++++++++++++++----- nova/db/sqlalchemy/models.py | 7 +++++-- nova/volume/manager.py | 10 +++++----- 5 files changed, 55 insertions(+), 33 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 367511e3b..88d53527c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -143,7 +143,7 @@ class CloudController(object): }, 'hostname': hostname, 'instance-action': 'none', - 'instance-id': instance_ref['str_id'], + 'instance-id': instance_ref['ec2_id'], 'instance-type': instance_ref['instance_type'], 'local-hostname': hostname, 'local-ipv4': address, @@ -245,7 +245,7 @@ class CloudController(object): def get_console_output(self, context, instance_id, **kwargs): # instance_id is passed in as a list of instances - instance_ref = db.instance_get_by_str(context, instance_id[0]) + instance_ref = db.instance_get_by_ec2_id(context, instance_id[0]) return rpc.call('%s.%s' % (FLAGS.compute_topic, instance_ref['host']), {"method": "get_console_output", @@ -264,7 +264,7 @@ class CloudController(object): def _format_volume(self, context, volume): v = {} - v['volumeId'] = volume['str_id'] + v['volumeId'] = volume['ec2_id'] v['status'] = volume['status'] v['size'] = volume['size'] v['availabilityZone'] = volume['availability_zone'] @@ -282,7 +282,7 @@ class CloudController(object): 'device': volume['mountpoint'], 'instanceId': volume['instance_id'], 'status': 'attached', - 'volume_id': volume['str_id']}] + 'volume_id': volume['ec2_id']}] else: v['attachmentSet'] = [{}] return v @@ -314,13 +314,13 @@ class CloudController(object): def attach_volume(self, context, volume_id, instance_id, device, **kwargs): - volume_ref = db.volume_get_by_str(context, volume_id) + volume_ref = db.volume_get_by_ec2_id(context, volume_id) # TODO(vish): abstract status checking? if volume_ref['status'] != "available": raise exception.ApiError("Volume status must be available") if volume_ref['attach_status'] == "attached": raise exception.ApiError("Volume is already attached") - instance_ref = db.instance_get_by_str(context, instance_id) + instance_ref = db.instance_get_by_ec2_id(context, instance_id) host = instance_ref['host'] rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), {"method": "attach_volume", @@ -336,7 +336,7 @@ class CloudController(object): 'volumeId': volume_ref['id']} def detach_volume(self, context, volume_id, **kwargs): - volume_ref = db.volume_get_by_str(context, volume_id) + volume_ref = db.volume_get_by_ec2_id(context, volume_id) instance_ref = db.volume_get_instance(context, volume_ref['id']) if not instance_ref: raise exception.ApiError("Volume isn't attached to anything!") @@ -356,7 +356,7 @@ class CloudController(object): db.volume_detached(context) return {'attachTime': volume_ref['attach_time'], 'device': volume_ref['mountpoint'], - 'instanceId': instance_ref['str_id'], + 'instanceId': instance_ref['ec2_id'], 'requestId': context.request_id, 'status': volume_ref['attach_status'], 'volumeId': volume_ref['id']} @@ -395,7 +395,7 @@ class CloudController(object): if instance['image_id'] == FLAGS.vpn_image_id: continue i = {} - i['instanceId'] = instance['str_id'] + i['instanceId'] = instance['ec2_id'] i['imageId'] = instance['image_id'] i['instanceState'] = { 'code': instance['state'], @@ -446,7 +446,7 @@ class CloudController(object): instance_id = None if (floating_ip_ref['fixed_ip'] and floating_ip_ref['fixed_ip']['instance']): - instance_id = floating_ip_ref['fixed_ip']['instance']['str_id'] + instance_id = floating_ip_ref['fixed_ip']['instance']['ec2_id'] address_rv = {'public_ip': address, 'instance_id': instance_id} if context.user.is_admin(): @@ -481,7 +481,7 @@ class CloudController(object): return {'releaseResponse': ["Address released."]} def associate_address(self, context, instance_id, public_ip, **kwargs): - instance_ref = db.instance_get_by_str(context, instance_id) + instance_ref = db.instance_get_by_ec2_id(context, instance_id) fixed_ip_ref = db.fixed_ip_get_by_instance(context, instance_ref['id']) floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) network_topic = self._get_network_topic(context) @@ -589,7 +589,7 @@ class CloudController(object): inst = {} inst['mac_address'] = utils.generate_mac() inst['launch_index'] = num - inst['hostname'] = instance_ref['str_id'] + inst['hostname'] = instance_ref['ec2_id'] db.instance_update(context, inst_id, inst) address = self.network_manager.allocate_fixed_ip(context, inst_id, @@ -618,7 +618,7 @@ class CloudController(object): for id_str in instance_id: logging.debug("Going to try and terminate %s" % id_str) try: - instance_ref = db.instance_get_by_str(context, id_str) + instance_ref = db.instance_get_by_ec2_id(context, id_str) except exception.NotFound: logging.warning("Instance %s was not found during terminate" % id_str) @@ -664,7 +664,7 @@ class CloudController(object): def reboot_instances(self, context, instance_id, **kwargs): """instance_id is a list of instance ids""" for id_str in instance_id: - instance_ref = db.instance_get_by_str(context, id_str) + instance_ref = db.instance_get_by_ec2_id(context, id_str) host = instance_ref['host'] rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), {"method": "reboot_instance", @@ -674,7 +674,7 @@ class CloudController(object): def delete_volume(self, context, volume_id, **kwargs): # TODO: return error if not authorized - volume_ref = db.volume_get_by_str(context, volume_id) + volume_ref = db.volume_get_by_ec2_id(context, volume_id) if volume_ref['status'] != "available": raise exception.ApiError("Volume status must be available") now = datetime.datetime.utcnow() diff --git a/nova/db/api.py b/nova/db/api.py index c1cb1953a..d642a5942 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -272,9 +272,9 @@ def instance_get_floating_address(context, instance_id): return IMPL.instance_get_floating_address(context, instance_id) -def instance_get_by_str(context, str_id): - """Get an instance by string id.""" - return IMPL.instance_get_by_str(context, str_id) +def instance_get_by_ec2_id(context, ec2_id): + """Get an instance by ec2 id.""" + return IMPL.instance_get_by_ec2_id(context, ec2_id) def instance_is_vpn(context, instance_id): @@ -537,9 +537,9 @@ def volume_get_by_project(context, project_id): return IMPL.volume_get_by_project(context, project_id) -def volume_get_by_str(context, str_id): - """Get a volume by string id.""" - return IMPL.volume_get_by_str(context, str_id) +def volume_get_by_ec2_id(context, ec2_id): + """Get a volume by ec2 id.""" + return IMPL.volume_get_by_ec2_id(context, ec2_id) def volume_get_shelf_and_blade(context, volume_id): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 2b0dd6ea6..f9999caa3 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -22,6 +22,7 @@ Implementation of SQLAlchemy backend from nova import db from nova import exception from nova import flags +from nova import utils from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from sqlalchemy import or_ @@ -356,6 +357,7 @@ def instance_create(_context, values): instance_ref = models.Instance() for (key, value) in values.iteritems(): instance_ref[key] = value + instance_ref.ec2_id = utils.generate_uid(instance_ref.__prefix__) instance_ref.save() return instance_ref @@ -408,8 +410,16 @@ def instance_get_by_reservation(_context, reservation_id): ).all() -def instance_get_by_str(context, str_id): - return models.Instance.find_by_str(str_id, deleted=_deleted(context)) +def instance_get_by_ec2_id(context, ec2_id): + session = get_session() + instance_ref = session.query(models.Instance + ).filter_by(ec2_id=ec2_id + ).filter_by(deleted=_deleted(context) + ).first() + if not instance_ref: + raise exception.NotFound('Instance %s not found' % (ec2_id)) + + return instance_ref def instance_get_fixed_address(_context, instance_id): @@ -699,7 +709,7 @@ def auth_create_token(_context, token): tk[k] = v tk.save() return tk - + ################### @@ -768,6 +778,7 @@ def volume_create(_context, values): volume_ref = models.Volume() for (key, value) in values.iteritems(): volume_ref[key] = value + volume_ref.ec2_id = utils.generate_uid(volume_ref.__prefix__) volume_ref.save() return volume_ref @@ -821,8 +832,16 @@ def volume_get_by_project(context, project_id): ).all() -def volume_get_by_str(context, str_id): - return models.Volume.find_by_str(str_id, deleted=_deleted(context)) +def volume_get_by_ec2_id(context, ec2_id): + session = get_session() + volume_ref = session.query(models.Volume + ).filter_by(ec2_id=ec2_id + ).filter_by(deleted=_deleted(context) + ).first() + if not volume_ref: + raise exception.NotFound('Volume %s not found' % (ec2_id)) + + return volume_ref def volume_get_instance(_context, volume_id): diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f6ba7953f..baf48d306 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -126,6 +126,7 @@ class NovaBase(object): # __tablename__ = 'images' # __prefix__ = 'ami' # id = Column(Integer, primary_key=True) +# ec2_id = Column(String(12), unique=True) # user_id = Column(String(255)) # project_id = Column(String(255)) # image_type = Column(String(255)) @@ -195,6 +196,7 @@ class Instance(BASE, NovaBase): __tablename__ = 'instances' __prefix__ = 'i' id = Column(Integer, primary_key=True) + ec2_id = Column(String(10), unique=True) user_id = Column(String(255)) project_id = Column(String(255)) @@ -265,6 +267,7 @@ class Volume(BASE, NovaBase): __tablename__ = 'volumes' __prefix__ = 'vol' id = Column(Integer, primary_key=True) + ec2_id = Column(String(12), unique=True) user_id = Column(String(255)) project_id = Column(String(255)) @@ -398,8 +401,8 @@ class NetworkIndex(BASE, NovaBase): uselist=False)) class AuthToken(BASE, NovaBase): - """Represents an authorization token for all API transactions. Fields - are a string representing the actual token and a user id for mapping + """Represents an authorization token for all API transactions. Fields + are a string representing the actual token and a user id for mapping to the actual user""" __tablename__ = 'auth_tokens' token_hash = Column(String(255), primary_key=True) diff --git a/nova/volume/manager.py b/nova/volume/manager.py index 034763512..8508f27b2 100644 --- a/nova/volume/manager.py +++ b/nova/volume/manager.py @@ -77,7 +77,7 @@ class AOEManager(manager.Manager): size = volume_ref['size'] logging.debug("volume %s: creating lv of size %sG", volume_id, size) - yield self.driver.create_volume(volume_ref['str_id'], size) + yield self.driver.create_volume(volume_ref['ec2_id'], size) logging.debug("volume %s: allocating shelf & blade", volume_id) self._ensure_blades(context) @@ -87,7 +87,7 @@ class AOEManager(manager.Manager): logging.debug("volume %s: exporting shelf %s & blade %s", volume_id, shelf_id, blade_id) - yield self.driver.create_export(volume_ref['str_id'], + yield self.driver.create_export(volume_ref['ec2_id'], shelf_id, blade_id) @@ -111,10 +111,10 @@ class AOEManager(manager.Manager): raise exception.Error("Volume is not local to this node") shelf_id, blade_id = self.db.volume_get_shelf_and_blade(context, volume_id) - yield self.driver.remove_export(volume_ref['str_id'], + yield self.driver.remove_export(volume_ref['ec2_id'], shelf_id, blade_id) - yield self.driver.delete_volume(volume_ref['str_id']) + yield self.driver.delete_volume(volume_ref['ec2_id']) self.db.volume_destroy(context, volume_id) defer.returnValue(True) @@ -125,7 +125,7 @@ class AOEManager(manager.Manager): Returns path to device. """ volume_ref = self.db.volume_get(context, volume_id) - yield self.driver.discover_volume(volume_ref['str_id']) + yield self.driver.discover_volume(volume_ref['ec2_id']) shelf_id, blade_id = self.db.volume_get_shelf_and_blade(context, volume_id) defer.returnValue("/dev/etherd/e%s.%s" % (shelf_id, blade_id)) -- cgit From 5ebefd0d5de7a7c753297bcde8ae842c4f92e33e Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 28 Sep 2010 13:21:24 -0700 Subject: add disabled column to services and check for it in scheduler --- nova/db/sqlalchemy/api.py | 4 +++- nova/db/sqlalchemy/models.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 2b0dd6ea6..2ecf21685 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -61,6 +61,7 @@ def service_get_all_by_topic(context, topic): session = get_session() return session.query(models.Service ).filter_by(deleted=False + ).filter_by(disabled=False ).filter_by(topic=topic ).all() @@ -70,6 +71,7 @@ def _service_get_all_topic_subquery(_context, session, topic, subq, label): return session.query(models.Service, func.coalesce(sort_value, 0) ).filter_by(topic=topic ).filter_by(deleted=False + ).filter_by(disabled=False ).outerjoin((subq, models.Service.host == subq.c.host) ).order_by(sort_value ).all() @@ -699,7 +701,7 @@ def auth_create_token(_context, token): tk[k] = v tk.save() return tk - + ################### diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f6ba7953f..021091b60 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -173,6 +173,7 @@ class Service(BASE, NovaBase): binary = Column(String(255)) topic = Column(String(255)) report_count = Column(Integer, nullable=False, default=0) + disabled = Column(Boolean, default=False) @classmethod def find_by_args(cls, host, binary, session=None, deleted=False): -- cgit From 641b6ee5630ed00ee3e921769cd408a8603ff62b Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 28 Sep 2010 16:46:21 -0500 Subject: Merge prop fixes and pylint/pep8 cleanup --- nova/api/rackspace/__init__.py | 2 +- nova/api/rackspace/auth.py | 8 +- nova/api/rackspace/backup_schedules.py | 5 +- nova/api/rackspace/images.py | 5 +- nova/api/rackspace/servers.py | 141 ++++++++++++++++++-------------- nova/db/sqlalchemy/models.py | 2 +- nova/tests/api/rackspace/servers.py | 8 +- nova/tests/api/rackspace/test_helper.py | 10 ++- pylintrc | 3 +- 9 files changed, 105 insertions(+), 79 deletions(-) diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index a10a9c6df..98802663f 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -31,11 +31,11 @@ import webob from nova import flags from nova import utils from nova import wsgi +from nova.api.rackspace import backup_schedules from nova.api.rackspace import flavors from nova.api.rackspace import images from nova.api.rackspace import ratelimiting from nova.api.rackspace import servers -from nova.api.rackspace import backup_schedules from nova.api.rackspace import sharedipgroups from nova.auth import manager diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py index ce5a967eb..8bfb0753e 100644 --- a/nova/api/rackspace/auth.py +++ b/nova/api/rackspace/auth.py @@ -1,13 +1,15 @@ import datetime +import hashlib import json import time + import webob.exc import webob.dec -import hashlib -from nova import flags + from nova import auth -from nova import manager from nova import db +from nova import flags +from nova import manager from nova import utils FLAGS = flags.FLAGS diff --git a/nova/api/rackspace/backup_schedules.py b/nova/api/rackspace/backup_schedules.py index a18dfb87c..46da778ee 100644 --- a/nova/api/rackspace/backup_schedules.py +++ b/nova/api/rackspace/backup_schedules.py @@ -16,10 +16,11 @@ # under the License. import time -import nova.image.service +from webob import exc + from nova import wsgi from nova.api.rackspace import _id_translator -from webob import exc +import nova.image.service class Controller(wsgi.Controller): def __init__(self): diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 10e15de3f..11b058dec 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -15,10 +15,11 @@ # License for the specific language governing permissions and limitations # under the License. -import nova.image.service +from webob import exc + from nova import wsgi from nova.api.rackspace import _id_translator -from webob import exc +import nova.image.service class Controller(wsgi.Controller): diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index dec369ef0..4ab04bde7 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -16,23 +16,43 @@ # under the License. import time -import nova.image.service -from nova import wsgi -from nova import db + +from webob import exc + from nova import flags from nova import rpc from nova import utils -from nova import compute -from nova import flags -from nova.compute import power_state +from nova import wsgi from nova.api.rackspace import _id_translator -from webob import exc +from nova.compute import power_state +import nova.image.service FLAGS = flags.FLAGS -class Controller(wsgi.Controller): - _power_mapping = { + +def translator_instance(): + """ Helper method for initializing the image id translator """ + service = nova.image.service.ImageService.load() + return _id_translator.RackspaceAPIIdTranslator( + "image", service.__class__.__name__) + +def _filter_params(inst_dict): + """ Extracts all updatable parameters for a server update request """ + keys = ['name', 'adminPass'] + new_attrs = {} + for k in keys: + if inst_dict.has_key(k): + new_attrs[k] = inst_dict[k] + return new_attrs + +def _entity_list(entities): + """ Coerces a list of servers into proper dictionary format """ + return dict(servers=entities) + +def _entity_detail(inst): + """ Maps everything to Rackspace-like attributes for return""" + power_mapping = { power_state.NOSTATE: 'build', power_state.RUNNING: 'active', power_state.BLOCKED: 'active', @@ -41,6 +61,28 @@ class Controller(wsgi.Controller): power_state.SHUTOFF: 'active', power_state.CRASHED: 'error' } + inst_dict = {} + + mapped_keys = dict(status='state', imageId='image_id', + flavorId='instance_type', name='server_name', id='id') + + for k, v in mapped_keys.iteritems(): + inst_dict[k] = inst[v] + + inst_dict['status'] = power_mapping[inst_dict['status']] + inst_dict['addresses'] = dict(public=[], private=[]) + inst_dict['metadata'] = {} + inst_dict['hostId'] = '' + + return dict(server=inst_dict) + +def _entity_inst(inst): + """ Filters all model attributes save for id and name """ + return dict(server=dict(id=inst['id'], name=inst['server_name'])) + +class Controller(wsgi.Controller): + """ The Server API controller for the Openstack API """ + _serialization_metadata = { 'application/xml': { @@ -54,37 +96,43 @@ class Controller(wsgi.Controller): def __init__(self, db_driver=None): if not db_driver: db_driver = FLAGS.db_driver - self.db = utils.import_object(db_driver) + self.db_driver = utils.import_object(db_driver) + super(Controller, self).__init__() def index(self, req): + """ Returns a list of server names and ids for a given user """ user_id = req.environ['nova.context']['user']['id'] - instance_list = self.db.instance_get_all_by_user(None, user_id) - res = [self._entity_inst(inst)['server'] for inst in instance_list] - return self._entity_list(res) + instance_list = self.db_driver.instance_get_all_by_user(None, user_id) + res = [_entity_inst(inst)['server'] for inst in instance_list] + return _entity_list(res) def detail(self, req): + """ Returns a list of server details for a given user """ user_id = req.environ['nova.context']['user']['id'] - res = [self._entity_detail(inst)['server'] for inst in - self.db.instance_get_all_by_user(None, user_id)] - return self._entity_list(res) + res = [_entity_detail(inst)['server'] for inst in + self.db_driver.instance_get_all_by_user(None, user_id)] + return _entity_list(res) def show(self, req, id): + """ Returns server details by server id """ user_id = req.environ['nova.context']['user']['id'] - inst = self.db.instance_get(None, id) + inst = self.db_driver.instance_get(None, id) if inst: if inst.user_id == user_id: - return self._entity_detail(inst) + return _entity_detail(inst) raise exc.HTTPNotFound() def delete(self, req, id): + """ Destroys a server """ user_id = req.environ['nova.context']['user']['id'] - instance = self.db.instance_get(None, id) + instance = self.db_driver.instance_get(None, id) if instance and instance['user_id'] == user_id: - self.db.instance_destroy(None, id) + self.db_driver.instance_destroy(None, id) return exc.HTTPAccepted() return exc.HTTPNotFound() def create(self, req): + """ Creates a new server for a given user """ if not req.environ.has_key('inst_dict'): return exc.HTTPUnprocessableEntity() @@ -93,20 +141,21 @@ class Controller(wsgi.Controller): rpc.cast( FLAGS.compute_topic, { "method": "run_instance", - "args": {"instance_id": inst.id}}) + "args": {"instance_id": inst['id']}}) return _entity_inst(inst) def update(self, req, id): + """ Updates the server name or password """ if not req.environ.has_key('inst_dict'): return exc.HTTPUnprocessableEntity() - instance = self.db.instance_get(None, id) + instance = self.db_driver.instance_get(None, id) if not instance: return exc.HTTPNotFound() attrs = req.environ['nova.context'].get('model_attributes', None) if attrs: - self.db.instance_update(None, id, self._filter_params(attrs)) + self.db_driver.instance_update(None, id, _filter_params(attrs)) return exc.HTTPNoContent() def action(self, req, id): @@ -115,11 +164,6 @@ class Controller(wsgi.Controller): if not req.environ.has_key('inst_dict'): return exc.HTTPUnprocessableEntity() - def translator_instance(self): - service = nova.image.service.ImageService.load() - return _id_translator.RackspaceAPIIdTranslator( - "image", service.__class__.__name__) - def _build_server_instance(self, req): """Build instance data structure and save it to the data store.""" ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) @@ -128,9 +172,9 @@ class Controller(wsgi.Controller): env = req.environ['inst_dict'] image_id = env['server']['imageId'] - opaque_id = self.translator_instance().from_rs_id(image_id) + opaque_id = translator_instance().from_rs_id(image_id) - inst['name'] = env['server']['name'] + inst['name'] = env['server']['server_name'] inst['image_id'] = opaque_id inst['instance_type'] = env['server']['flavorId'] @@ -148,43 +192,16 @@ class Controller(wsgi.Controller): 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'] - self.db.instance_create(None, inst) + ref = self.db_driver.instance_create(None, inst) + inst['id'] = ref.id + return inst - def _filter_params(self, inst_dict): - keys = ['name', 'adminPass'] - new_attrs = {} - for k in keys: - if inst_dict.has_key(k): - new_attrs[k] = inst_dict[k] - return new_attrs - - def _entity_list(self, entities): - return dict(servers=entities) - - def _entity_detail(self, inst): - """ Maps everything to Rackspace-like attributes for return""" - inst_dict = {} - - mapped_keys = dict(status='state', imageId='image_id', - flavorId='instance_type', name='name', id='id') - - for k,v in mapped_keys.iteritems(): - inst_dict[k] = inst[v] - - inst_dict['status'] = Controller._power_mapping[inst_dict['status']] - inst_dict['addresses'] = dict(public=[], private=[]) - inst_dict['metadata'] = {} - inst_dict['hostId'] = '' - - return dict(server=inst_dict) - - def _entity_inst(self, inst): - """ Filters all model attributes save for id and name """ - return dict(server=dict(id=inst['id'], name=inst['name'])) + diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 47c505f25..63f8c6172 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -209,7 +209,7 @@ class Instance(BASE, NovaBase): @property def name(self): - return self.server_name or self.str_id + return self.str_id image_id = Column(String(255)) kernel_id = Column(String(255)) diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index a22736dd2..9fd8e5e88 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -17,15 +17,17 @@ import json import unittest + import stubout -import nova.api.rackspace -import nova.db.api + +from nova import db from nova import flags +import nova.api.rackspace from nova.api.rackspace import servers +import nova.db.api from nova.db.sqlalchemy.models import Instance from nova.tests.api.test_helper import * from nova.tests.api.rackspace import test_helper -from nova import db FLAGS = flags.FLAGS diff --git a/nova/tests/api/rackspace/test_helper.py b/nova/tests/api/rackspace/test_helper.py index 09ff26ac7..aa7fb382c 100644 --- a/nova/tests/api/rackspace/test_helper.py +++ b/nova/tests/api/rackspace/test_helper.py @@ -1,13 +1,15 @@ +import datetime import json + import webob import webob.dec -import datetime -import nova.api.rackspace.auth -import nova.api.rackspace._id_translator -from nova.wsgi import Router + from nova import auth from nova import utils from nova import flags +import nova.api.rackspace.auth +import nova.api.rackspace._id_translator +from nova.wsgi import Router FLAGS = flags.FLAGS diff --git a/pylintrc b/pylintrc index 6702ca895..024802835 100644 --- a/pylintrc +++ b/pylintrc @@ -1,7 +1,8 @@ [Messages Control] # W0511: TODOs in code comments are fine. # W0142: *args and **kwargs are fine. -disable-msg=W0511,W0142 +# W0622: Redefining id is fine. +disable-msg=W0511,W0142,W0622 [Basic] # Variable names can be 1 to 31 characters long, with lowercase and underscores -- cgit From 60cd2a9e292eb5a8a0bc45605d79d0a511525342 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Tue, 28 Sep 2010 15:15:05 -0700 Subject: Added checks for uniqueness for ec2 id --- nova/db/sqlalchemy/api.py | 35 +++++++++++++++++++++++++++++------ nova/db/sqlalchemy/models.py | 4 ---- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index f9999caa3..7229e5b2c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -27,7 +27,7 @@ from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from sqlalchemy import or_ from sqlalchemy.orm import joinedload_all -from sqlalchemy.sql import func +from sqlalchemy.sql import exists, func FLAGS = flags.FLAGS @@ -357,10 +357,15 @@ def instance_create(_context, values): instance_ref = models.Instance() for (key, value) in values.iteritems(): instance_ref[key] = value - instance_ref.ec2_id = utils.generate_uid(instance_ref.__prefix__) - instance_ref.save() - return instance_ref + session = get_session() + with session.begin(): + while instance_ref.ec2_id == None: + ec2_id = utils.generate_uid(instance_ref.__prefix__) + if not instance_ec2_id_exists(_context, ec2_id, session=session): + instance_ref.ec2_id = ec2_id + instance_ref.save(session=session) + return instance_ref def instance_data_get_for_project(_context, project_id): session = get_session() @@ -422,6 +427,12 @@ def instance_get_by_ec2_id(context, ec2_id): return instance_ref +def instance_ec2_id_exists(context, ec2_id, session=None): + if not session: + session = get_session() + return session.query(exists().where(models.Instance.id==ec2_id)).one()[0] + + def instance_get_fixed_address(_context, instance_id): session = get_session() with session.begin(): @@ -778,8 +789,14 @@ def volume_create(_context, values): volume_ref = models.Volume() for (key, value) in values.iteritems(): volume_ref[key] = value - volume_ref.ec2_id = utils.generate_uid(volume_ref.__prefix__) - volume_ref.save() + + session = get_session() + with session.begin(): + while volume_ref.ec2_id == None: + ec2_id = utils.generate_uid(volume_ref.__prefix__) + if not volume_ec2_id_exists(_context, ec2_id, session=session): + volume_ref.ec2_id = ec2_id + volume_ref.save(session=session) return volume_ref @@ -844,6 +861,12 @@ def volume_get_by_ec2_id(context, ec2_id): return volume_ref +def volume_ec2_id_exists(context, ec2_id, session=None): + if not session: + session = get_session() + return session.query(exists().where(models.Volume.id==ec2_id)).one()[0] + + def volume_get_instance(_context, volume_id): session = get_session() with session.begin(): diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index baf48d306..b898d8037 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -457,10 +457,6 @@ class FloatingIp(BASE, NovaBase): project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) - @property - def str_id(self): - return self.address - @classmethod def find_by_str(cls, str_id, session=None, deleted=False): if not session: -- cgit From 116f0fc0b18c6b04a86de5123d2985205d954093 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Tue, 28 Sep 2010 16:10:47 -0700 Subject: Fixed name property on instance model --- nova/api/ec2/cloud.py | 16 ++++++++-------- nova/db/sqlalchemy/models.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 88d53527c..05e8065f3 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -103,7 +103,7 @@ class CloudController(object): result = {} for instance in db.instance_get_by_project(None, project_id): if instance['fixed_ip']: - line = '%s slots=%d' % (instance['fixed_ip']['str_id'], + line = '%s slots=%d' % (instance['fixed_ip']['address'], INSTANCE_TYPES[instance['instance_type']]['vcpus']) key = str(instance['key_name']) if key in result: @@ -404,10 +404,10 @@ class CloudController(object): fixed_addr = None floating_addr = None if instance['fixed_ip']: - fixed_addr = instance['fixed_ip']['str_id'] + fixed_addr = instance['fixed_ip']['address'] if instance['fixed_ip']['floating_ips']: fixed = instance['fixed_ip'] - floating_addr = fixed['floating_ips'][0]['str_id'] + floating_addr = fixed['floating_ips'][0]['address'] i['privateDnsName'] = fixed_addr i['publicDnsName'] = floating_addr i['dnsName'] = i['publicDnsName'] or i['privateDnsName'] @@ -442,7 +442,7 @@ class CloudController(object): iterator = db.floating_ip_get_by_project(context, context.project.id) for floating_ip_ref in iterator: - address = floating_ip_ref['str_id'] + address = floating_ip_ref['address'] instance_id = None if (floating_ip_ref['fixed_ip'] and floating_ip_ref['fixed_ip']['instance']): @@ -477,7 +477,7 @@ class CloudController(object): rpc.cast(network_topic, {"method": "deallocate_floating_ip", "args": {"context": None, - "floating_address": floating_ip_ref['str_id']}}) + "floating_address": floating_ip_ref['address']}}) return {'releaseResponse': ["Address released."]} def associate_address(self, context, instance_id, public_ip, **kwargs): @@ -488,8 +488,8 @@ class CloudController(object): rpc.cast(network_topic, {"method": "associate_floating_ip", "args": {"context": None, - "floating_address": floating_ip_ref['str_id'], - "fixed_address": fixed_ip_ref['str_id']}}) + "floating_address": floating_ip_ref['address'], + "fixed_address": fixed_ip_ref['address']}}) return {'associateResponse': ["Address associated."]} def disassociate_address(self, context, public_ip, **kwargs): @@ -498,7 +498,7 @@ class CloudController(object): rpc.cast(network_topic, {"method": "disassociate_floating_ip", "args": {"context": None, - "floating_address": floating_ip_ref['str_id']}}) + "floating_address": floating_ip_ref['address']}}) return {'disassociateResponse': ["Address disassociated."]} def _get_network_topic(self, context): diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index b898d8037..7549a7082 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -211,7 +211,7 @@ class Instance(BASE, NovaBase): @property def name(self): - return self.str_id + return self.ec2_id image_id = Column(String(255)) kernel_id = Column(String(255)) -- cgit From bbc00d9eca3b874e240e50bfa9f397afc36d0bee Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 28 Sep 2010 16:34:21 -0700 Subject: removed extra code that slipped in from a test branch --- nova/tests/rpc_unittest.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/nova/tests/rpc_unittest.py b/nova/tests/rpc_unittest.py index f4d7b4b28..e12a28fbc 100644 --- a/nova/tests/rpc_unittest.py +++ b/nova/tests/rpc_unittest.py @@ -67,17 +67,6 @@ class RpcTestCase(test.BaseTestCase): except rpc.RemoteError as exc: self.assertEqual(int(exc.value), value) - def test_pass_object(self): - """Test that we can pass objects through rpc""" - class x(): - pass - obj = x() - x.foo = 'bar' - x.baz = 100 - - result = yield rpc.call('test', {"method": "echo", - "args": {"value": obj}}) - print result class TestReceiver(object): """Simple Proxy class so the consumer has methods to call -- cgit From 2327378a1e5c9fa942d56001919caaeb1be1c7cb Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Tue, 28 Sep 2010 18:38:19 -0700 Subject: Removed str_id from FixedIp references --- bin/nova-manage | 2 +- nova/compute/manager.py | 10 +++++----- nova/network/linux_net.py | 2 +- nova/network/manager.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index baa1cb4db..d0fde6f17 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -316,7 +316,7 @@ class FloatingIpCommands(object): for floating_ip in floating_ips: instance = None if floating_ip['fixed_ip']: - instance = floating_ip['fixed_ip']['instance']['str_id'] + instance = floating_ip['fixed_ip']['instance']['address'] print "%s\t%s\t%s" % (floating_ip['host'], floating_ip['address'], instance) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 24538e4f1..f370ede8b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -67,7 +67,7 @@ class ComputeManager(manager.Manager): def run_instance(self, context, instance_id, **_kwargs): """Launch a new instance with specified options.""" instance_ref = self.db.instance_get(context, instance_id) - if instance_ref['str_id'] in self.driver.list_instances(): + if instance_ref['ec2_id'] in self.driver.list_instances(): raise exception.Error("Instance has already been created") logging.debug("instance %s: starting...", instance_id) project_id = instance_ref['project_id'] @@ -129,7 +129,7 @@ class ComputeManager(manager.Manager): raise exception.Error( 'trying to reboot a non-running' 'instance: %s (state: %s excepted: %s)' % - (instance_ref['str_id'], + (instance_ref['ec2_id'], instance_ref['state'], power_state.RUNNING)) @@ -151,7 +151,7 @@ class ComputeManager(manager.Manager): if FLAGS.connection_type == 'libvirt': fname = os.path.abspath(os.path.join(FLAGS.instances_path, - instance_ref['str_id'], + instance_ref['ec2_id'], 'console.log')) with open(fname, 'r') as f: output = f.read() @@ -174,7 +174,7 @@ class ComputeManager(manager.Manager): instance_ref = self.db.instance_get(context, instance_id) dev_path = yield self.volume_manager.setup_compute_volume(context, volume_id) - yield self.driver.attach_volume(instance_ref['str_id'], + yield self.driver.attach_volume(instance_ref['ec2_id'], dev_path, mountpoint) self.db.volume_attached(context, volume_id, instance_id, mountpoint) @@ -189,7 +189,7 @@ class ComputeManager(manager.Manager): volume_id) instance_ref = self.db.instance_get(context, instance_id) volume_ref = self.db.volume_get(context, volume_id) - yield self.driver.detach_volume(instance_ref['str_id'], + yield self.driver.detach_volume(instance_ref['ec2_id'], volume_ref['mountpoint']) self.db.volume_detached(context, volume_id) defer.returnValue(True) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 41aeb5da7..25a9456b9 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -133,7 +133,7 @@ def get_dhcp_hosts(context, network_id): """Get a string containing a network's hosts config in dnsmasq format""" hosts = [] for fixed_ip in db.network_get_associated_fixed_ips(context, network_id): - hosts.append(_host_dhcp(fixed_ip['str_id'])) + hosts.append(_host_dhcp(fixed_ip['address'])) return '\n'.join(hosts) diff --git a/nova/network/manager.py b/nova/network/manager.py index 191c1d364..321dac914 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -270,7 +270,7 @@ class VlanManager(NetworkManager): raise exception.Error("IP %s leased to bad mac %s vs %s" % (address, instance_ref['mac_address'], mac)) self.db.fixed_ip_update(context, - fixed_ip_ref['str_id'], + fixed_ip_ref['address'], {'leased': True}) def release_fixed_ip(self, context, mac, address): -- cgit From e3102b6b9be148597a2f502d2b2baf750ecc0a34 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Tue, 28 Sep 2010 18:43:36 -0700 Subject: Fixed tests --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index d0fde6f17..a5087bfec 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -316,7 +316,7 @@ class FloatingIpCommands(object): for floating_ip in floating_ips: instance = None if floating_ip['fixed_ip']: - instance = floating_ip['fixed_ip']['instance']['address'] + instance = floating_ip['fixed_ip']['instance']['ec2_id'] print "%s\t%s\t%s" % (floating_ip['host'], floating_ip['address'], instance) -- cgit