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 3dbb3fa96779fbed89251a7c24455acbddc013e5 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 20:05:32 -0700 Subject: fixed reversed args in nova-manage project environment --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 325245ac4..1c6dc03ac 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -252,7 +252,7 @@ class ProjectCommands(object): def environment(self, project_id, user_id, filename='novarc'): """Exports environment variables to an sourcable file arguments: project_id user_id [filename='novarc]""" - rc = self.manager.get_environment_rc(project_id, user_id) + rc = self.manager.get_environment_rc(user_id, project_id) with open(filename, 'w') as f: f.write(rc) -- cgit From ab0e61a1e05bdec8b39c22ff9d93845526a0a02b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 20:22:19 -0700 Subject: implement floating_ip_get_all_by_project and renamed db methods that get more then one to get_all_by instead of get_by --- nova/db/api.py | 19 ++++++++++++------- nova/db/sqlalchemy/api.py | 14 +++++++++++--- nova/endpoint/cloud.py | 16 ++++++++-------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index d749ae50a..1529888ba 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -122,10 +122,15 @@ def floating_ip_get_all(context): def floating_ip_get_all_by_host(context, host): - """Get all floating ips.""" + """Get all floating ips by host.""" return IMPL.floating_ip_get_all_by_host(context, host) +def floating_ip_get_all_by_project(context, project_id): + """Get all floating ips by project.""" + return IMPL.floating_ip_get_all_by_project(context, project_id) + + def floating_ip_get_by_address(context, address): """Get a floating ip by address or raise if it doesn't exist.""" return IMPL.floating_ip_get_by_address(context, address) @@ -208,14 +213,14 @@ def instance_get_all(context): return IMPL.instance_get_all(context) -def instance_get_by_project(context, project_id): +def instance_get_all_by_project(context, project_id): """Get all instance belonging to a project.""" - return IMPL.instance_get_by_project(context, project_id) + return IMPL.instance_get_all_by_project(context, project_id) -def instance_get_by_reservation(context, reservation_id): +def instance_get_all_by_reservation(context, reservation_id): """Get all instance belonging to a reservation.""" - return IMPL.instance_get_by_reservation(context, reservation_id) + return IMPL.instance_get_all_by_reservation(context, reservation_id) def instance_get_fixed_address(context, instance_id): @@ -417,9 +422,9 @@ def volume_get_instance(context, volume_id): return IMPL.volume_get_instance(context, volume_id) -def volume_get_by_project(context, project_id): +def volume_get_all_by_project(context, project_id): """Get all volumes belonging to a project.""" - return IMPL.volume_get_by_project(context, project_id) + return IMPL.volume_get_all_by_project(context, project_id) def volume_get_by_str(context, str_id): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 485dca2b0..948bca224 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -159,6 +159,14 @@ def floating_ip_get_all_by_host(_context, host): ).filter_by(deleted=False ).all() +def floating_ip_get_all_by_project(_context, project_id): + session = get_session() + return session.query(models.FloatingIp + ).options(joinedload_all('fixed_ip.instance') + ).filter_by(project_id=project_id + ).filter_by(deleted=False + ).all() + def floating_ip_get_by_address(_context, address): return models.FloatingIp.find_by_str(address) @@ -288,7 +296,7 @@ def instance_get_all(context): ).all() -def instance_get_by_project(context, project_id): +def instance_get_all_by_project(context, project_id): session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') @@ -297,7 +305,7 @@ def instance_get_by_project(context, project_id): ).all() -def instance_get_by_reservation(_context, reservation_id): +def instance_get_all_by_reservation(_context, reservation_id): session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') @@ -606,7 +614,7 @@ def volume_get_all(context): return models.Volume.all(deleted=_deleted(context)) -def volume_get_by_project(context, project_id): +def volume_get_all_by_project(context, project_id): session = get_session() return session.query(models.Volume ).filter_by(project_id=project_id diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 622b4e2a4..00aa2ede8 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -84,7 +84,7 @@ class CloudController(object): def _get_mpi_data(self, project_id): result = {} - for instance in db.instance_get_by_project(None, project_id): + for instance in db.instance_get_all_by_project(None, project_id): if instance['fixed_ip']: line = '%s slots=%d' % (instance['fixed_ip']['str_id'], INSTANCE_TYPES[instance['instance_type']]['vcpus']) @@ -244,7 +244,7 @@ class CloudController(object): if context.user.is_admin(): volumes = db.volume_get_all(context) else: - volumes = db.volume_get_by_project(context, context.project.id) + volumes = db.volume_get_all_by_project(context, context.project.id) volumes = [self._format_volume(context, v) for v in volumes] @@ -363,14 +363,14 @@ class CloudController(object): def _format_instances(self, context, reservation_id=None): reservations = {} if reservation_id: - instances = db.instance_get_by_reservation(context, - reservation_id) + instances = db.instance_get_all_by_reservation(context, + reservation_id) else: if not context.user.is_admin(): instances = db.instance_get_all(context) else: - instances = db.instance_get_by_project(context, - context.project.id) + instances = db.instance_get_all_by_project(context, + context.project.id) for instance in instances: if not context.user.is_admin(): if instance['image_id'] == FLAGS.vpn_image_id: @@ -421,8 +421,8 @@ class CloudController(object): if context.user.is_admin(): iterator = db.floating_ip_get_all(context) else: - iterator = db.floating_ip_get_by_project(context, - context.project.id) + iterator = db.floating_ip_get_all_by_project(context, + context.project.id) for floating_ip_ref in iterator: address = floating_ip_ref['str_id'] instance_id = None -- cgit From 19040b7b2b908b2816bc7aca54a8437d54badd26 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 21:12:01 -0700 Subject: fixed reference to misnamed method --- nova/endpoint/cloud.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 00aa2ede8..41f23618f 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -464,14 +464,15 @@ class CloudController(object): @defer.inlineCallbacks def associate_address(self, context, instance_id, public_ip, **kwargs): instance_ref = db.instance_get_by_str(context, instance_id) - fixed_ip_ref = db.fixed_ip_get_by_instance(context, instance_ref['id']) + fixed_address = db.instance_get_fixed_address(context, + instance_ref['id']) floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) network_topic = yield self._get_network_topic(context) 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']}}) + "fixed_address": fixed_address}}) defer.returnValue({'associateResponse': ["Address associated."]}) @rbac.allow('netadmin') -- cgit From 53bba81d1e9774eefadd4f0f2b25638838a7ad07 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 22:54:47 -0700 Subject: don't allocate the same floating ip multiple times --- nova/db/sqlalchemy/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 948bca224..cc496e558 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -80,6 +80,7 @@ def floating_ip_allocate_address(_context, host, project_id): floating_ip_ref = session.query(models.FloatingIp ).filter_by(host=host ).filter_by(fixed_ip_id=None + ).filter_by(project_id=None ).filter_by(deleted=False ).with_lockmode('update' ).first() -- cgit From e45a5dd2cbcfe5d43cc59c6a20e3d065d43ee161 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 23:32:03 -0700 Subject: speed up generation of dhcp_hosts and don't run into None errors if instance is deleted --- nova/db/sqlalchemy/api.py | 1 + nova/network/linux_net.py | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index cc496e558..4cc086006 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -437,6 +437,7 @@ def network_get(_context, network_id): def network_get_associated_fixed_ips(_context, network_id): session = get_session() return session.query(models.FixedIp + ).options(joinedload_all('instance') ).filter_by(network_id=network_id ).filter(models.FixedIp.instance_id != None ).filter_by(deleted=False diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 41aeb5da7..621ae54ea 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -132,8 +132,9 @@ def ensure_bridge(bridge, interface, net_attrs=None): 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'])) + for fixed_ip_ref in db.network_get_associated_fixed_ips(context, + network_id): + hosts.append(_host_dhcp(fixed_ip_ref)) return '\n'.join(hosts) @@ -171,12 +172,12 @@ def update_dhcp(context, network_id): _execute(command, addl_env=env) -def _host_dhcp(address): +def _host_dhcp(fixed_ip_ref): """Return a host string for an address""" - instance_ref = db.fixed_ip_get_instance(None, address) + instance_ref = fixed_ip_ref['instance'] return "%s,%s.novalocal,%s" % (instance_ref['mac_address'], instance_ref['hostname'], - address) + fixed_ip_ref['str_id']) def _execute(cmd, *args, **kwargs): -- cgit From 85876fc1fa3dc7cb289e59712ab5a2b20877fc58 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 23:34:32 -0700 Subject: disassociate floating is supposed to take floating_address --- nova/endpoint/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 41f23618f..5fff72642 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -605,7 +605,7 @@ class CloudController(object): rpc.cast(network_topic, {"method": "disassociate_floating_ip", "args": {"context": None, - "address": address}}) + "floating_address": address}}) address = db.instance_get_fixed_address(context, instance_ref['id']) -- cgit From be1b1e320c17630430cfa567d8685f8cfc5773e4 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 23:51:28 -0700 Subject: make fixed_ip_get_by_address return the instance as well so we don't run into concurrency issues where it is disassociated in between --- nova/db/sqlalchemy/api.py | 16 ++++++++++++++-- nova/network/manager.py | 4 ++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 4cc086006..3d3b766fc 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -19,13 +19,15 @@ Implementation of SQLAlchemy backend """ +import sys + from nova import db from nova import exception from nova import flags 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.orm import exc, joinedload_all FLAGS = flags.FLAGS @@ -243,7 +245,17 @@ def fixed_ip_disassociate(_context, address): def fixed_ip_get_by_address(_context, address): - return models.FixedIp.find_by_str(address) + session = get_session() + with session.begin(): + try: + return session.query(models.FixedIp + ).options(joinedload_all('instance') + ).filter_by(address=address + ).filter_by(deleted=False + ).one() + except exc.NoResultFound: + new_exc = exception.NotFound("No model for address %s" % address) + raise new_exc.__class__, new_exc, sys.exc_info()[2] def fixed_ip_get_instance(_context, address): diff --git a/nova/network/manager.py b/nova/network/manager.py index 7a3bcfc2f..9564a3e33 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -262,7 +262,7 @@ class VlanManager(NetworkManager): if not fixed_ip_ref['allocated']: logging.warn("IP %s leased that was already deallocated", address) return - instance_ref = self.db.fixed_ip_get_instance(context, address) + instance_ref = fixed_ip_ref['instance'] if not instance_ref: raise exception.Error("IP %s leased that isn't associated" % address) @@ -280,7 +280,7 @@ class VlanManager(NetworkManager): if not fixed_ip_ref['leased']: logging.warn("IP %s released that was not leased", address) return - instance_ref = self.db.fixed_ip_get_instance(context, address) + instance_ref = fixed_ip_ref['instance'] if not instance_ref: raise exception.Error("IP %s released that isn't associated" % address) -- cgit From 970654239267fc702f767bbaff3e22207576d0cd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 13 Sep 2010 01:58:40 -0700 Subject: multiple network controllers will not create duplicate indexes --- nova/db/api.py | 9 ++++++--- nova/db/sqlalchemy/api.py | 8 ++++++-- nova/db/sqlalchemy/models.py | 2 +- nova/network/manager.py | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index 9f6ff99c3..a775dc301 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -365,9 +365,12 @@ def network_index_count(context): return IMPL.network_index_count(context) -def network_index_create(context, values): - """Create a network index from the values dict""" - return IMPL.network_index_create(context, values) +def network_index_create_safe(context, values): + """Create a network index from the values dict + + The index is not returned. If the create violates the unique + constraints because the index already exists, no exception is raised.""" + return IMPL.network_index_create_safe(context, values) def network_set_cidr(context, network_id, cidr): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index d612fe669..35585b1b0 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -25,6 +25,7 @@ from nova import flags from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from sqlalchemy import or_ +from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload_all from sqlalchemy.sql import func @@ -567,11 +568,14 @@ def network_index_count(_context): return models.NetworkIndex.count() -def network_index_create(_context, values): +def network_index_create_safe(_context, values): network_index_ref = models.NetworkIndex() for (key, value) in values.iteritems(): network_index_ref[key] = value - network_index_ref.save() + try: + network_index_ref.save() + except IntegrityError: + pass def network_set_host(_context, network_id, host_id): diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 41013f41b..6ff0decb5 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -355,7 +355,7 @@ class NetworkIndex(BASE, NovaBase): """ __tablename__ = 'network_indexes' id = Column(Integer, primary_key=True) - index = Column(Integer) + index = Column(Integer, unique=True) network_id = Column(Integer, ForeignKey('networks.id'), nullable=True) network = relationship(Network, backref=backref('network_index', uselist=False)) diff --git a/nova/network/manager.py b/nova/network/manager.py index 191c1d364..570d5f8d8 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -343,7 +343,7 @@ class VlanManager(NetworkManager): This could use a manage command instead of keying off of a flag""" if not self.db.network_index_count(context): for index in range(FLAGS.num_networks): - self.db.network_index_create(context, {'index': index}) + self.db.network_index_create_safe(context, {'index': index}) def _on_set_network_host(self, context, network_id): """Called when this host becomes the host for a project""" -- 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 01a757ee7bc3624c17dbbcfd3bc65d3e2f674b03 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 15 Sep 2010 17:40:12 -0700 Subject: Added iptables host initial configuration --- bin/nova-manage | 1 + nova/endpoint/api.py | 3 +- nova/flags.py | 5 ++- nova/manager.py | 10 +++++ nova/network/linux_net.py | 44 +++++++++++++++------- nova/network/manager.py | 7 ++++ nova/service.py | 1 + tools/setup_ipchains.sh | 94 +++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 148 insertions(+), 17 deletions(-) create mode 100644 tools/setup_ipchains.sh diff --git a/bin/nova-manage b/bin/nova-manage index 325245ac4..909435ede 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -384,3 +384,4 @@ def main(): if __name__ == '__main__': main() + diff --git a/nova/endpoint/api.py b/nova/endpoint/api.py index 40be00bb7..6de3698e1 100755 --- a/nova/endpoint/api.py +++ b/nova/endpoint/api.py @@ -42,8 +42,6 @@ from nova.endpoint import cloud FLAGS = flags.FLAGS -flags.DEFINE_integer('cc_port', 8773, 'cloud controller port') - _log = logging.getLogger("api") _log.setLevel(logging.DEBUG) @@ -342,3 +340,4 @@ class APIServerApplication(tornado.web.Application): (r'/1.0/([-A-Za-z0-9/]*)', MetadataRequestHandler), ], pool=multiprocessing.Pool(4)) self.controllers = controllers + diff --git a/nova/flags.py b/nova/flags.py index 7b0c95a3c..55b452fc3 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -184,7 +184,9 @@ DEFINE_string('rabbit_userid', 'guest', 'rabbit userid') DEFINE_string('rabbit_password', 'guest', 'rabbit password') DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') -DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', +DEFINE_string('cc_ip', '127.0.0.1', 'ip of api server') +DEFINE_integer('cc_port', 8773, 'cloud controller port') +DEFINE_string('ec2_url', 'http://%s:%s/services/Cloud' % (FLAGS.cc_ip, FLAGS.cc_port), 'Url to ec2 api server') DEFINE_string('default_image', 'ami-11111', @@ -220,3 +222,4 @@ DEFINE_string('host', socket.gethostname(), # UNUSED DEFINE_string('node_availability_zone', 'nova', 'availability zone of this node') + diff --git a/nova/manager.py b/nova/manager.py index e9aa50c56..495b1f0d1 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -37,3 +37,13 @@ class Manager(object): if not db_driver: db_driver = FLAGS.db_driver self.db = utils.import_object(db_driver) # pylint: disable-msg=C0103 + + def init_host(self): + """Do any initialization that needs to be run if this is a standalone service. + + Child classes should override this method. + """ + + + + diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 41aeb5da7..604d11c93 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -36,13 +36,28 @@ flags.DEFINE_string('dhcpbridge_flagfile', flags.DEFINE_string('networks_path', utils.abspath('../networks'), 'Location to keep network config files') flags.DEFINE_string('public_interface', 'vlan1', - 'Interface for public IP addresses') + 'Interface for public IP addresses') flags.DEFINE_string('bridge_dev', 'eth0', - 'network device for bridges') - + 'network device for bridges') +flags.DEFINE_string('routing_source_ip', utils.get_my_ip(), + 'Public IP of network host') DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)] +def init_host(): + """Basic networking setup goes here""" + # NOTE(devcamcar): Cloud public DNAT entries, CloudPipe port + # forwarding entries and a default DNAT entry. + _confirm_rule("-t nat -A nova_prerouting -s 0.0.0.0/0 " + "-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT " + "--to-destination %s:%s" % (FLAGS.cc_ip, FLAGS.cc_port)) + + # NOTE(devcamcar): Cloud public SNAT entries and the default + # SNAT rule for outbound traffic. + _confirm_rule("-t nat -A nova_postrouting -s %s " + "-j SNAT --to-source %s" + % (FLAGS.private_range, FLAGS.routing_source_ip)) + def bind_floating_ip(floating_ip): """Bind ip to public interface""" @@ -58,37 +73,37 @@ def unbind_floating_ip(floating_ip): def ensure_vlan_forward(public_ip, port, private_ip): """Sets up forwarding rules for vlan""" - _confirm_rule("FORWARD -d %s -p udp --dport 1194 -j ACCEPT" % private_ip) + _confirm_rule("nova_forward -d %s -p udp --dport 1194 -j ACCEPT" % private_ip) _confirm_rule( - "PREROUTING -t nat -d %s -p udp --dport %s -j DNAT --to %s:1194" + "nova_prerouting -t nat -d %s -p udp --dport %s -j DNAT --to %s:1194" % (public_ip, port, private_ip)) def ensure_floating_forward(floating_ip, fixed_ip): """Ensure floating ip forwarding rule""" - _confirm_rule("PREROUTING -t nat -d %s -j DNAT --to %s" + _confirm_rule("nova_prerouting -t nat -d %s -j DNAT --to %s" % (floating_ip, fixed_ip)) - _confirm_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" + _confirm_rule("nova_postrouting -t nat -s %s -j SNAT --to %s" % (fixed_ip, floating_ip)) # TODO(joshua): Get these from the secgroup datastore entries - _confirm_rule("FORWARD -d %s -p icmp -j ACCEPT" + _confirm_rule("nova_forward -d %s -p icmp -j ACCEPT" % (fixed_ip)) for (protocol, port) in DEFAULT_PORTS: _confirm_rule( - "FORWARD -d %s -p %s --dport %s -j ACCEPT" + "nova_forward -d %s -p %s --dport %s -j ACCEPT" % (fixed_ip, protocol, port)) def remove_floating_forward(floating_ip, fixed_ip): """Remove forwarding for floating ip""" - _remove_rule("PREROUTING -t nat -d %s -j DNAT --to %s" + _remove_rule("nova_prerouting -t nat -d %s -j DNAT --to %s" % (floating_ip, fixed_ip)) - _remove_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" + _remove_rule("nova_postrouting -t nat -s %s -j SNAT --to %s" % (fixed_ip, floating_ip)) - _remove_rule("FORWARD -d %s -p icmp -j ACCEPT" + _remove_rule("nova_forward -d %s -p icmp -j ACCEPT" % (fixed_ip)) for (protocol, port) in DEFAULT_PORTS: - _remove_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT" + _remove_rule("nova_forward -d %s -p %s --dport %s -j ACCEPT" % (fixed_ip, protocol, port)) @@ -124,7 +139,7 @@ def ensure_bridge(bridge, interface, net_attrs=None): net_attrs['gateway'], net_attrs['broadcast'], net_attrs['netmask'])) - _confirm_rule("FORWARD --in-interface %s -j ACCEPT" % bridge) + _confirm_rule("nova_forward --in-interface %s -j ACCEPT" % bridge) else: _execute("sudo ifconfig %s up" % bridge) @@ -256,3 +271,4 @@ def _dnsmasq_pid_for(vlan): if os.path.exists(pid_file): with open(pid_file, 'r') as f: return int(f.read()) + diff --git a/nova/network/manager.py b/nova/network/manager.py index 7a3bcfc2f..87c3d8e46 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -218,6 +218,12 @@ class FlatManager(NetworkManager): class VlanManager(NetworkManager): """Vlan network with dhcp""" + + def init_host(self): + """Do any initialization that needs to be run if this is a standalone service. + """ + driver.init_host() + def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): """Gets a fixed ip from the pool""" network_ref = self.db.project_get_network(context, context.project.id) @@ -363,3 +369,4 @@ class VlanManager(NetworkManager): parent_reserved = super(VlanManager, self)._top_reserved_ips return parent_reserved + FLAGS.cnt_vpn_clients + diff --git a/nova/service.py b/nova/service.py index 870dd6ceb..8f1db1b8e 100644 --- a/nova/service.py +++ b/nova/service.py @@ -158,3 +158,4 @@ class Service(object, service.Service): self.model_disconnected = True logging.exception("model server went away") yield + diff --git a/tools/setup_ipchains.sh b/tools/setup_ipchains.sh new file mode 100644 index 000000000..b1ab1c6f7 --- /dev/null +++ b/tools/setup_ipchains.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +CMD="global" +IP="XXX" +PRIVATE_RANGE="10.128.0.0/12" + +if [ -n "$1" ]; then + CMD=$1 +fi + +if [ -n "$2" ]; then + IP=$2 +fi + +if [ -n "$3" ]; then + PRIVATE_RANGE=$3 +fi + +if [ "$CMD" == "global" ]; then + iptables -P INPUT DROP + iptables -A INPUT -m state --state INVALID -j DROP + iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -A INPUT -m tcp -p tcp -d $MGMT_IP --dport 22 -j ACCEPT + iptables -A INPUT -m udp -p udp --dport 123 -j ACCEPT + iptables -N nova_input + iptables -A INPUT -j nova_input + iptables -A INPUT -p icmp -j ACCEPT + iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset + iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable + + iptables -P FORWARD DROP + iptables -A FORWARD -m state --state INVALID -j DROP + iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu + iptables -N nova_forward + iptables -A FORWARD -j nova_forward + + iptables -P OUTPUT DROP + iptables -A OUTPUT -m state --state INVALID -j DROP + iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -N nova_output + iptables -A OUTPUT -j nova_output + + iptables -t nat -N nova_prerouting + iptables -t nat -A PREROUTING -j nova_prerouting + + iptables -t nat -N nova_postrouting + iptables -t nat -A POSTROUTING -j nova_postrouting + + iptables -t nat -N nova_output + iptables -t nat -A OUTPUT -j nova_output + + # ganglia (all hosts) + iptables -A nova_input -m tcp -p tcp -d $IP --dport 8649 -j ACCEPT + iptables -A nova_input -m udp -p udp -d $IP --dport 8649 -j ACCEPT +fi + +if [ "$CMD" == "dashboard" ]; then + # dashboard + iptables -A nova_input -m tcp -p tcp -d $IP --dport 80 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 443 -j ACCEPT +fi + +if [ "$CMD" == "objectstore" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 3333 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 8773 -j ACCEPT +fi + +if [ "$CMD" == "redis" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 6379 -j ACCEPT +fi + +if [ "$CMD" == "mysql" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 3306 -j ACCEPT +fi + +if [ "$CMD" == "rabbitmq" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 4369 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 5672 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 53284 -j ACCEPT +fi + +if [ "$CMD" == "dnsmasq" ]; then + # NOTE(vish): this could theoretically be setup per network + # for each host, but it seems like overkill + iptables -A nova_input -m tcp -p tcp -s $PRIVATE_RANGE --dport 53 -j ACCEPT + iptables -A nova_input -m udp -p udp -s $PRIVATE_RANGE --dport 53 -j ACCEPT + iptables -A nova_input -m udp -p udp --dport 67 -j ACCEPT + +if [ "$CMD" == "ldap" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 389 -j ACCEPT +fi + + -- cgit From bc2641148359352ed83d4190baaf1e208e00a6b9 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 15 Sep 2010 17:49:15 -0700 Subject: Added iptables host initial configuration --- nova/network/manager.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index 87c3d8e46..25a9ba474 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -219,10 +219,11 @@ class FlatManager(NetworkManager): class VlanManager(NetworkManager): """Vlan network with dhcp""" - def init_host(self): - """Do any initialization that needs to be run if this is a standalone service. - """ - driver.init_host() + def init_host(self): + """Do any initialization that needs to be run if this is a + standalone service. + """ + driver.init_host() def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): """Gets a fixed ip from the pool""" -- cgit From e1ddec70bc7522a75b4a50953a0f4b20ace6cce1 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 16 Sep 2010 11:40:04 -0700 Subject: Added missing masquerade rules --- nova/network/linux_net.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 604d11c93..75acf2afc 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -58,6 +58,8 @@ def init_host(): "-j SNAT --to-source %s" % (FLAGS.private_range, FLAGS.routing_source_ip)) + _confirm_rule("-A nova_postrouting -s %s MASQUERADE" % FLAGS.private_range) + _confirm_rule("-A nova_postrouting -s %(range)s -d %(range)s" % {'range': FLAGS.private_range}) def bind_floating_ip(floating_ip): """Bind ip to public interface""" -- cgit From d0708205759880e7fb78fbb1df33df939f669413 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 16 Sep 2010 11:44:51 -0700 Subject: Whitespace fixes --- bin/nova-manage | 1 - nova/manager.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 909435ede..325245ac4 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -384,4 +384,3 @@ def main(): if __name__ == '__main__': main() - diff --git a/nova/manager.py b/nova/manager.py index 495b1f0d1..b7b97bced 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -44,6 +44,3 @@ class Manager(object): Child classes should override this method. """ - - - -- cgit From 68633fadeb92a5a26d1ab613bed6094ddfa2a014 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Mon, 20 Sep 2010 15:35:44 -0700 Subject: Whitespace fixes --- nova/endpoint/api.py | 1 - nova/flags.py | 1 - nova/network/linux_net.py | 1 - nova/network/manager.py | 1 - nova/service.py | 1 - 5 files changed, 5 deletions(-) diff --git a/nova/endpoint/api.py b/nova/endpoint/api.py index 6de3698e1..56481b2c0 100755 --- a/nova/endpoint/api.py +++ b/nova/endpoint/api.py @@ -340,4 +340,3 @@ class APIServerApplication(tornado.web.Application): (r'/1.0/([-A-Za-z0-9/]*)', MetadataRequestHandler), ], pool=multiprocessing.Pool(4)) self.controllers = controllers - diff --git a/nova/flags.py b/nova/flags.py index 55b452fc3..ce30d5033 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -222,4 +222,3 @@ DEFINE_string('host', socket.gethostname(), # UNUSED DEFINE_string('node_availability_zone', 'nova', 'availability zone of this node') - diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 75acf2afc..65dcf51ee 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -273,4 +273,3 @@ def _dnsmasq_pid_for(vlan): if os.path.exists(pid_file): with open(pid_file, 'r') as f: return int(f.read()) - diff --git a/nova/network/manager.py b/nova/network/manager.py index 25a9ba474..c7bcfa175 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -370,4 +370,3 @@ class VlanManager(NetworkManager): parent_reserved = super(VlanManager, self)._top_reserved_ips return parent_reserved + FLAGS.cnt_vpn_clients - diff --git a/nova/service.py b/nova/service.py index 8f1db1b8e..870dd6ceb 100644 --- a/nova/service.py +++ b/nova/service.py @@ -158,4 +158,3 @@ class Service(object, service.Service): self.model_disconnected = True logging.exception("model server went away") yield - -- cgit From e74b8070f73d8bada01cfe2d26223e5180ab67fb Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Tue, 21 Sep 2010 00:03:53 -0700 Subject: Renamed cc_ip flag to cc_host --- nova/flags.py | 4 ++-- nova/network/linux_net.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index ce30d5033..9d27d336d 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -184,9 +184,9 @@ DEFINE_string('rabbit_userid', 'guest', 'rabbit userid') DEFINE_string('rabbit_password', 'guest', 'rabbit password') DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') -DEFINE_string('cc_ip', '127.0.0.1', 'ip of api server') +DEFINE_string('cc_host', '127.0.0.1', 'ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') -DEFINE_string('ec2_url', 'http://%s:%s/services/Cloud' % (FLAGS.cc_ip, FLAGS.cc_port), +DEFINE_string('ec2_url', 'http://%s:%s/services/Cloud' % (FLAGS.cc_host, FLAGS.cc_port), 'Url to ec2 api server') DEFINE_string('default_image', 'ami-11111', diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 65dcf51ee..53fb2df94 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -50,7 +50,7 @@ def init_host(): # forwarding entries and a default DNAT entry. _confirm_rule("-t nat -A nova_prerouting -s 0.0.0.0/0 " "-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT " - "--to-destination %s:%s" % (FLAGS.cc_ip, FLAGS.cc_port)) + "--to-destination %s:%s" % (FLAGS.cc_host, FLAGS.cc_port)) # NOTE(devcamcar): Cloud public SNAT entries and the default # SNAT rule for outbound traffic. -- 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 b68ab98d6718d5a7237f5620e8caffc770dfe822 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 21 Sep 2010 19:24:19 -0400 Subject: User updatable name & description for images. --- nova/endpoint/cloud.py | 12 ++++++++++++ nova/endpoint/images.py | 6 ++++++ nova/objectstore/handler.py | 19 +++++++++++++++---- nova/objectstore/image.py | 12 ++++++++++++ nova/tests/cloud_unittest.py | 26 +++++++++++++++++++++++++- nova/tests/objectstore_unittest.py | 6 ++++++ 6 files changed, 76 insertions(+), 5 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index e29d09a89..b1678ecce 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -752,3 +752,15 @@ class CloudController(object): raise exception.ApiError('operation_type must be add or remove') result = images.modify(context, image_id, operation_type) return defer.succeed(result) + + @rbac.allow('projectmanager', 'sysadmin') + def set_image_name(self, context, image_id, name): + result = images.update_user_editable_field(context, image_id, + 'displayName', name) + return defer.succeed(result) + + @rbac.allow('projectmanager', 'sysadmin') + def set_image_description(self, context, image_id, name): + result = images.update_user_editable_field(context, image_id, + 'displayDescription', name) + return defer.succeed(result) diff --git a/nova/endpoint/images.py b/nova/endpoint/images.py index 4579cd81a..a63c059bb 100644 --- a/nova/endpoint/images.py +++ b/nova/endpoint/images.py @@ -43,6 +43,12 @@ def modify(context, image_id, operation): return True +def update_user_editable_field(context, image_id, field, value): + conn(context).make_request( + method='POST', + bucket='_images', + query_args=qs({'image_id': image_id, 'field': field, 'value': value})) + return True def register(context, image_location): """ rpc call to register a new image based from a manifest """ diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py index 5c3dc286b..f4596e982 100644 --- a/nova/objectstore/handler.py +++ b/nova/objectstore/handler.py @@ -382,15 +382,26 @@ class ImagesResource(resource.Resource): def render_POST(self, request): # pylint: disable-msg=R0201 """Update image attributes: public/private""" + # image_id required for all requests image_id = get_argument(request, 'image_id', u'') - operation = get_argument(request, 'operation', u'') - image_object = image.Image(image_id) - if not image_object.is_authorized(request.context): + logging.debug("not authorized for handle_POST in images") raise exception.NotAuthorized - image_object.set_public(operation=='add') + operation = get_argument(request, 'operation', u'') + field = get_argument(request, 'field', u'') + value = get_argument(request, 'value', u'') + if operation: + # operation implies publicity toggle + logging.debug("handling publicity toggle") + image_object.set_public(operation=='add') + elif field: + # field implies user field editing (value can be blank) + logging.debug("update user field") + image_object.update_user_editable_field(field, value) + else: + logging.debug("unknown action for handle_POST in images") return '' diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index f3c02a425..ad1745af6 100644 --- a/nova/objectstore/image.py +++ b/nova/objectstore/image.py @@ -82,6 +82,18 @@ class Image(object): with open(os.path.join(self.path, 'info.json'), 'w') as f: json.dump(md, f) + def update_user_editable_field(self, field, value): + fields = ['displayName', 'displayDescription'] + if field not in fields: + raise KeyError("Invalid field: %s" % field) + info = self.metadata + if value: + info[field] = value + elif field in info: + del info[field] + with open(os.path.join(self.path, 'info.json'), 'w') as f: + json.dump(info, f) + @staticmethod def all(): images = [] diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index c36d5a34f..2e97180be 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -16,9 +16,13 @@ # License for the specific language governing permissions and limitations # under the License. +import json import logging +import os import StringIO +import tempfile import time + from tornado import ioloop from twisted.internet import defer import unittest @@ -32,15 +36,22 @@ from nova.auth import manager from nova.compute import power_state from nova.endpoint import api from nova.endpoint import cloud +from nova.objectstore import image FLAGS = flags.FLAGS +# Temp dirs for working with image attributes through the cloud controller +# (stole this from objectstore_unittest.py) +OSS_TEMPDIR = tempfile.mkdtemp(prefix='test_oss-') +IMAGES_PATH = os.path.join(OSS_TEMPDIR, 'images') +os.makedirs(IMAGES_PATH) + class CloudTestCase(test.BaseTestCase): def setUp(self): super(CloudTestCase, self).setUp() - self.flags(connection_type='fake') + self.flags(connection_type='fake', images_path=IMAGES_PATH) self.conn = rpc.Connection.instance() logging.getLogger().setLevel(logging.DEBUG) @@ -156,3 +167,16 @@ class CloudTestCase(test.BaseTestCase): #for i in xrange(4): # data = self.cloud.get_metadata(instance(i)['private_dns_name']) # self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i) + + def test_user_editable_endpoint(self): + pathdir = os.path.join(FLAGS.images_path, 'i-testing') + os.mkdir(pathdir) + info = {} + with open(os.path.join(pathdir, 'info.json'), 'w') as f: + json.dump(info, f) + yield self.cloud.set_image_description(self.context, 'i-testing', + 'Foo Img') + img = image.Image('i-testing') + self.assertEqual('Foo Img', img.metadata['displayDescription']) + self.cloud.set_image_description(self.context, 'i-testing', '') + self.assert_(not 'displayDescription' in img.metadata) diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py index dece4b5d5..82c6e2c49 100644 --- a/nova/tests/objectstore_unittest.py +++ b/nova/tests/objectstore_unittest.py @@ -164,6 +164,12 @@ class ObjectStoreTestCase(test.BaseTestCase): self.context.project = self.auth_manager.get_project('proj2') self.assertFalse(my_img.is_authorized(self.context)) + # change user-editable fields + my_img.update_user_editable_field('displayName', 'my cool image') + self.assertEqual('my cool image', my_img.metadata['displayName']) + my_img.update_user_editable_field('displayName', '') + self.assert_(not 'displayName' in my_img.metadata) + class TestHTTPChannel(http.HTTPChannel): """Dummy site required for twisted.web""" -- cgit From 71c41338e4aac98ec03eec1c90ee99e43d51bcb7 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 22 Sep 2010 16:42:35 -0400 Subject: Add user display fields to instances & volumes. --- nova/db/sqlalchemy/models.py | 9 ++++++++- nova/endpoint/cloud.py | 32 ++++++++++++++++++++++++++++++++ nova/tests/cloud_unittest.py | 43 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 41013f41b..359087db0 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -233,7 +233,6 @@ class Instance(BASE, NovaBase): vcpus = Column(Integer) local_gb = Column(Integer) - hostname = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) @@ -247,6 +246,10 @@ class Instance(BASE, NovaBase): scheduled_at = Column(DateTime) launched_at = Column(DateTime) terminated_at = Column(DateTime) + + display_name = Column(String(255)) + display_description = Column(String(255)) + # TODO(vish): see Ewan's email about state improvements, probably # should be in a driver base class or some such # vmstate_state = running, halted, suspended, paused @@ -282,6 +285,10 @@ class Volume(BASE, NovaBase): launched_at = Column(DateTime) terminated_at = Column(DateTime) + display_name = Column(String(255)) + display_description = Column(String(255)) + + class Quota(BASE, NovaBase): """Represents quota overrides for a project""" __tablename__ = 'quotas' diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index b1678ecce..50fdee60b 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -280,6 +280,10 @@ class CloudController(object): 'volume_id': volume['str_id']}] else: v['attachmentSet'] = [{}] + if 'display_name' in volume: + v['display_name'] = volume['display_name'] + if 'display_description' in volume: + v['display_description'] = volume['display_description'] return v @rbac.allow('projectmanager', 'sysadmin') @@ -299,6 +303,8 @@ class CloudController(object): vol['availability_zone'] = FLAGS.storage_availability_zone vol['status'] = "creating" vol['attach_status'] = "detached" + vol['display_name'] = kwargs.get('display_name') + vol['display_description'] = kwargs.get('display_description') volume_ref = db.volume_create(context, vol) rpc.cast(FLAGS.scheduler_topic, @@ -367,6 +373,17 @@ class CloudController(object): lst = [lst] return [{label: x} for x in lst] + @rbac.allow('projectmanager', 'sysadmin') + def update_volume(self, context, volume_id, **kwargs): + updatable_fields = ['display_name', 'display_description'] + changes = {} + for field in updatable_fields: + if field in kwargs: + changes[field] = kwargs[field] + if changes: + db.volume_update(context, volume_id, kwargs) + return defer.succeed(True) + @rbac.allow('all') def describe_instances(self, context, **kwargs): return defer.succeed(self._format_describe_instances(context)) @@ -420,6 +437,8 @@ class CloudController(object): i['instanceType'] = instance['instance_type'] i['launchTime'] = instance['created_at'] i['amiLaunchIndex'] = instance['launch_index'] + i['displayName'] = instance['display_name'] + i['displayDescription'] = instance['display_description'] if not reservations.has_key(instance['reservation_id']): r = {} r['reservationId'] = instance['reservation_id'] @@ -589,6 +608,8 @@ class CloudController(object): base_options['user_data'] = kwargs.get('user_data', '') base_options['security_group'] = security_group base_options['instance_type'] = instance_type + base_options['display_name'] = kwargs.get('display_name') + base_options['display_description'] = kwargs.get('display_description') type_data = INSTANCE_TYPES[instance_type] base_options['memory_mb'] = type_data['memory_mb'] @@ -689,6 +710,17 @@ class CloudController(object): "instance_id": instance_ref['id']}}) return defer.succeed(True) + @rbac.allow('projectmanager', 'sysadmin') + def update_instance(self, context, instance_id, **kwargs): + updatable_fields = ['display_name', 'display_description'] + changes = {} + for field in updatable_fields: + if field in kwargs: + changes[field] = kwargs[field] + if changes: + db.instance_update(context, instance_id, kwargs) + return defer.succeed(True) + @rbac.allow('projectmanager', 'sysadmin') def delete_volume(self, context, volume_id, **kwargs): # TODO: return error if not authorized diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 2e97180be..18ad19ede 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -28,6 +28,7 @@ from twisted.internet import defer import unittest from xml.etree import ElementTree +from nova import db from nova import flags from nova import rpc from nova import test @@ -168,15 +169,47 @@ class CloudTestCase(test.BaseTestCase): # data = self.cloud.get_metadata(instance(i)['private_dns_name']) # self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i) - def test_user_editable_endpoint(self): - pathdir = os.path.join(FLAGS.images_path, 'i-testing') + def test_user_editable_image_endpoint(self): + pathdir = os.path.join(FLAGS.images_path, 'ami-testing') os.mkdir(pathdir) info = {} with open(os.path.join(pathdir, 'info.json'), 'w') as f: json.dump(info, f) - yield self.cloud.set_image_description(self.context, 'i-testing', + yield self.cloud.set_image_description(self.context, 'ami-testing', 'Foo Img') - img = image.Image('i-testing') + img = image.Image('ami-testing') self.assertEqual('Foo Img', img.metadata['displayDescription']) - self.cloud.set_image_description(self.context, 'i-testing', '') + self.cloud.set_image_description(self.context, 'ami-testing', '') self.assert_(not 'displayDescription' in img.metadata) + + def test_update_of_instance_display_fields(self): + inst = db.instance_create({}, {}) + self.cloud.update_instance(self.context, inst['id'], + display_name='c00l 1m4g3') + inst = db.instance_get({}, inst['id']) + self.assertEqual('c00l 1m4g3', inst['display_name']) + db.instance_destroy({}, inst['id']) + + def test_update_of_instance_wont_update_private_fields(self): + inst = db.instance_create({}, {}) + self.cloud.update_instance(self.context, inst['id'], + mac_address='DE:AD:BE:EF') + inst = db.instance_get({}, inst['id']) + self.assertEqual(None, inst['mac_address']) + db.instance_destroy({}, inst['id']) + + def test_update_of_volume_display_fields(self): + vol = db.volume_create({}, {}) + self.cloud.update_volume(self.context, vol['id'], + display_name='c00l v0lum3') + vol = db.volume_get({}, vol['id']) + self.assertEqual('c00l v0lum3', vol['display_name']) + db.volume_destroy({}, vol['id']) + + def test_update_of_volume_wont_update_private_fields(self): + vol = db.volume_create({}, {}) + self.cloud.update_volume(self.context, vol['id'], + mountpoint='/not/here') + vol = db.volume_get({}, vol['id']) + self.assertEqual(None, vol['mountpoint']) + db.volume_destroy({}, vol['id']) -- cgit From 4f2edd43ca2c4a175b4d9dce23ae9e28941122e2 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 23 Sep 2010 11:23:32 -0700 Subject: renamed ipchains to iptables --- setup_iptables.sh | 94 +++++++++++++++++++++++++++++++++++++++++++++++++ tools/setup_ipchains.sh | 94 ------------------------------------------------- 2 files changed, 94 insertions(+), 94 deletions(-) create mode 100644 setup_iptables.sh delete mode 100644 tools/setup_ipchains.sh diff --git a/setup_iptables.sh b/setup_iptables.sh new file mode 100644 index 000000000..b1ab1c6f7 --- /dev/null +++ b/setup_iptables.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +CMD="global" +IP="XXX" +PRIVATE_RANGE="10.128.0.0/12" + +if [ -n "$1" ]; then + CMD=$1 +fi + +if [ -n "$2" ]; then + IP=$2 +fi + +if [ -n "$3" ]; then + PRIVATE_RANGE=$3 +fi + +if [ "$CMD" == "global" ]; then + iptables -P INPUT DROP + iptables -A INPUT -m state --state INVALID -j DROP + iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -A INPUT -m tcp -p tcp -d $MGMT_IP --dport 22 -j ACCEPT + iptables -A INPUT -m udp -p udp --dport 123 -j ACCEPT + iptables -N nova_input + iptables -A INPUT -j nova_input + iptables -A INPUT -p icmp -j ACCEPT + iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset + iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable + + iptables -P FORWARD DROP + iptables -A FORWARD -m state --state INVALID -j DROP + iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu + iptables -N nova_forward + iptables -A FORWARD -j nova_forward + + iptables -P OUTPUT DROP + iptables -A OUTPUT -m state --state INVALID -j DROP + iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -N nova_output + iptables -A OUTPUT -j nova_output + + iptables -t nat -N nova_prerouting + iptables -t nat -A PREROUTING -j nova_prerouting + + iptables -t nat -N nova_postrouting + iptables -t nat -A POSTROUTING -j nova_postrouting + + iptables -t nat -N nova_output + iptables -t nat -A OUTPUT -j nova_output + + # ganglia (all hosts) + iptables -A nova_input -m tcp -p tcp -d $IP --dport 8649 -j ACCEPT + iptables -A nova_input -m udp -p udp -d $IP --dport 8649 -j ACCEPT +fi + +if [ "$CMD" == "dashboard" ]; then + # dashboard + iptables -A nova_input -m tcp -p tcp -d $IP --dport 80 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 443 -j ACCEPT +fi + +if [ "$CMD" == "objectstore" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 3333 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 8773 -j ACCEPT +fi + +if [ "$CMD" == "redis" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 6379 -j ACCEPT +fi + +if [ "$CMD" == "mysql" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 3306 -j ACCEPT +fi + +if [ "$CMD" == "rabbitmq" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 4369 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 5672 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 53284 -j ACCEPT +fi + +if [ "$CMD" == "dnsmasq" ]; then + # NOTE(vish): this could theoretically be setup per network + # for each host, but it seems like overkill + iptables -A nova_input -m tcp -p tcp -s $PRIVATE_RANGE --dport 53 -j ACCEPT + iptables -A nova_input -m udp -p udp -s $PRIVATE_RANGE --dport 53 -j ACCEPT + iptables -A nova_input -m udp -p udp --dport 67 -j ACCEPT + +if [ "$CMD" == "ldap" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 389 -j ACCEPT +fi + + diff --git a/tools/setup_ipchains.sh b/tools/setup_ipchains.sh deleted file mode 100644 index b1ab1c6f7..000000000 --- a/tools/setup_ipchains.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env bash - -CMD="global" -IP="XXX" -PRIVATE_RANGE="10.128.0.0/12" - -if [ -n "$1" ]; then - CMD=$1 -fi - -if [ -n "$2" ]; then - IP=$2 -fi - -if [ -n "$3" ]; then - PRIVATE_RANGE=$3 -fi - -if [ "$CMD" == "global" ]; then - iptables -P INPUT DROP - iptables -A INPUT -m state --state INVALID -j DROP - iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT - iptables -A INPUT -m tcp -p tcp -d $MGMT_IP --dport 22 -j ACCEPT - iptables -A INPUT -m udp -p udp --dport 123 -j ACCEPT - iptables -N nova_input - iptables -A INPUT -j nova_input - iptables -A INPUT -p icmp -j ACCEPT - iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset - iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable - - iptables -P FORWARD DROP - iptables -A FORWARD -m state --state INVALID -j DROP - iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT - iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu - iptables -N nova_forward - iptables -A FORWARD -j nova_forward - - iptables -P OUTPUT DROP - iptables -A OUTPUT -m state --state INVALID -j DROP - iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT - iptables -N nova_output - iptables -A OUTPUT -j nova_output - - iptables -t nat -N nova_prerouting - iptables -t nat -A PREROUTING -j nova_prerouting - - iptables -t nat -N nova_postrouting - iptables -t nat -A POSTROUTING -j nova_postrouting - - iptables -t nat -N nova_output - iptables -t nat -A OUTPUT -j nova_output - - # ganglia (all hosts) - iptables -A nova_input -m tcp -p tcp -d $IP --dport 8649 -j ACCEPT - iptables -A nova_input -m udp -p udp -d $IP --dport 8649 -j ACCEPT -fi - -if [ "$CMD" == "dashboard" ]; then - # dashboard - iptables -A nova_input -m tcp -p tcp -d $IP --dport 80 -j ACCEPT - iptables -A nova_input -m tcp -p tcp -d $IP --dport 443 -j ACCEPT -fi - -if [ "$CMD" == "objectstore" ]; then - iptables -A nova_input -m tcp -p tcp -d $IP --dport 3333 -j ACCEPT - iptables -A nova_input -m tcp -p tcp -d $IP --dport 8773 -j ACCEPT -fi - -if [ "$CMD" == "redis" ]; then - iptables -A nova_input -m tcp -p tcp -d $IP --dport 6379 -j ACCEPT -fi - -if [ "$CMD" == "mysql" ]; then - iptables -A nova_input -m tcp -p tcp -d $IP --dport 3306 -j ACCEPT -fi - -if [ "$CMD" == "rabbitmq" ]; then - iptables -A nova_input -m tcp -p tcp -d $IP --dport 4369 -j ACCEPT - iptables -A nova_input -m tcp -p tcp -d $IP --dport 5672 -j ACCEPT - iptables -A nova_input -m tcp -p tcp -d $IP --dport 53284 -j ACCEPT -fi - -if [ "$CMD" == "dnsmasq" ]; then - # NOTE(vish): this could theoretically be setup per network - # for each host, but it seems like overkill - iptables -A nova_input -m tcp -p tcp -s $PRIVATE_RANGE --dport 53 -j ACCEPT - iptables -A nova_input -m udp -p udp -s $PRIVATE_RANGE --dport 53 -j ACCEPT - iptables -A nova_input -m udp -p udp --dport 67 -j ACCEPT - -if [ "$CMD" == "ldap" ]; then - iptables -A nova_input -m tcp -p tcp -d $IP --dport 389 -j ACCEPT -fi - - -- cgit From 44a3fe22d72f7359f57e7eb9ce443c974391991c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 23 Sep 2010 11:40:09 -0700 Subject: fixed a couple of typos --- nova/manager.py | 3 ++- nova/network/manager.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/manager.py b/nova/manager.py index b7b97bced..65300354b 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -40,7 +40,8 @@ class Manager(object): def init_host(self): """Do any initialization that needs to be run if this is a standalone service. - + Child classes should override this method. """ + pass diff --git a/nova/network/manager.py b/nova/network/manager.py index dcca21127..abe4dcebc 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -218,12 +218,12 @@ class FlatManager(NetworkManager): class VlanManager(NetworkManager): """Vlan network with dhcp""" - + def init_host(self): """Do any initialization that needs to be run if this is a standalone service. """ - driver.init_host() + self.driver.init_host() def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): """Gets a fixed ip from the pool""" -- cgit From 47a957acb176d108aac4183cbf5a882149d7462d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 23 Sep 2010 11:58:33 -0700 Subject: put setup_iptables in the right dir --- setup_iptables.sh | 94 ------------------------------------------------- tools/setup_iptables.sh | 94 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 94 deletions(-) delete mode 100644 setup_iptables.sh create mode 100644 tools/setup_iptables.sh diff --git a/setup_iptables.sh b/setup_iptables.sh deleted file mode 100644 index b1ab1c6f7..000000000 --- a/setup_iptables.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env bash - -CMD="global" -IP="XXX" -PRIVATE_RANGE="10.128.0.0/12" - -if [ -n "$1" ]; then - CMD=$1 -fi - -if [ -n "$2" ]; then - IP=$2 -fi - -if [ -n "$3" ]; then - PRIVATE_RANGE=$3 -fi - -if [ "$CMD" == "global" ]; then - iptables -P INPUT DROP - iptables -A INPUT -m state --state INVALID -j DROP - iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT - iptables -A INPUT -m tcp -p tcp -d $MGMT_IP --dport 22 -j ACCEPT - iptables -A INPUT -m udp -p udp --dport 123 -j ACCEPT - iptables -N nova_input - iptables -A INPUT -j nova_input - iptables -A INPUT -p icmp -j ACCEPT - iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset - iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable - - iptables -P FORWARD DROP - iptables -A FORWARD -m state --state INVALID -j DROP - iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT - iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu - iptables -N nova_forward - iptables -A FORWARD -j nova_forward - - iptables -P OUTPUT DROP - iptables -A OUTPUT -m state --state INVALID -j DROP - iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT - iptables -N nova_output - iptables -A OUTPUT -j nova_output - - iptables -t nat -N nova_prerouting - iptables -t nat -A PREROUTING -j nova_prerouting - - iptables -t nat -N nova_postrouting - iptables -t nat -A POSTROUTING -j nova_postrouting - - iptables -t nat -N nova_output - iptables -t nat -A OUTPUT -j nova_output - - # ganglia (all hosts) - iptables -A nova_input -m tcp -p tcp -d $IP --dport 8649 -j ACCEPT - iptables -A nova_input -m udp -p udp -d $IP --dport 8649 -j ACCEPT -fi - -if [ "$CMD" == "dashboard" ]; then - # dashboard - iptables -A nova_input -m tcp -p tcp -d $IP --dport 80 -j ACCEPT - iptables -A nova_input -m tcp -p tcp -d $IP --dport 443 -j ACCEPT -fi - -if [ "$CMD" == "objectstore" ]; then - iptables -A nova_input -m tcp -p tcp -d $IP --dport 3333 -j ACCEPT - iptables -A nova_input -m tcp -p tcp -d $IP --dport 8773 -j ACCEPT -fi - -if [ "$CMD" == "redis" ]; then - iptables -A nova_input -m tcp -p tcp -d $IP --dport 6379 -j ACCEPT -fi - -if [ "$CMD" == "mysql" ]; then - iptables -A nova_input -m tcp -p tcp -d $IP --dport 3306 -j ACCEPT -fi - -if [ "$CMD" == "rabbitmq" ]; then - iptables -A nova_input -m tcp -p tcp -d $IP --dport 4369 -j ACCEPT - iptables -A nova_input -m tcp -p tcp -d $IP --dport 5672 -j ACCEPT - iptables -A nova_input -m tcp -p tcp -d $IP --dport 53284 -j ACCEPT -fi - -if [ "$CMD" == "dnsmasq" ]; then - # NOTE(vish): this could theoretically be setup per network - # for each host, but it seems like overkill - iptables -A nova_input -m tcp -p tcp -s $PRIVATE_RANGE --dport 53 -j ACCEPT - iptables -A nova_input -m udp -p udp -s $PRIVATE_RANGE --dport 53 -j ACCEPT - iptables -A nova_input -m udp -p udp --dport 67 -j ACCEPT - -if [ "$CMD" == "ldap" ]; then - iptables -A nova_input -m tcp -p tcp -d $IP --dport 389 -j ACCEPT -fi - - diff --git a/tools/setup_iptables.sh b/tools/setup_iptables.sh new file mode 100644 index 000000000..b1ab1c6f7 --- /dev/null +++ b/tools/setup_iptables.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +CMD="global" +IP="XXX" +PRIVATE_RANGE="10.128.0.0/12" + +if [ -n "$1" ]; then + CMD=$1 +fi + +if [ -n "$2" ]; then + IP=$2 +fi + +if [ -n "$3" ]; then + PRIVATE_RANGE=$3 +fi + +if [ "$CMD" == "global" ]; then + iptables -P INPUT DROP + iptables -A INPUT -m state --state INVALID -j DROP + iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -A INPUT -m tcp -p tcp -d $MGMT_IP --dport 22 -j ACCEPT + iptables -A INPUT -m udp -p udp --dport 123 -j ACCEPT + iptables -N nova_input + iptables -A INPUT -j nova_input + iptables -A INPUT -p icmp -j ACCEPT + iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset + iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable + + iptables -P FORWARD DROP + iptables -A FORWARD -m state --state INVALID -j DROP + iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu + iptables -N nova_forward + iptables -A FORWARD -j nova_forward + + iptables -P OUTPUT DROP + iptables -A OUTPUT -m state --state INVALID -j DROP + iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -N nova_output + iptables -A OUTPUT -j nova_output + + iptables -t nat -N nova_prerouting + iptables -t nat -A PREROUTING -j nova_prerouting + + iptables -t nat -N nova_postrouting + iptables -t nat -A POSTROUTING -j nova_postrouting + + iptables -t nat -N nova_output + iptables -t nat -A OUTPUT -j nova_output + + # ganglia (all hosts) + iptables -A nova_input -m tcp -p tcp -d $IP --dport 8649 -j ACCEPT + iptables -A nova_input -m udp -p udp -d $IP --dport 8649 -j ACCEPT +fi + +if [ "$CMD" == "dashboard" ]; then + # dashboard + iptables -A nova_input -m tcp -p tcp -d $IP --dport 80 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 443 -j ACCEPT +fi + +if [ "$CMD" == "objectstore" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 3333 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 8773 -j ACCEPT +fi + +if [ "$CMD" == "redis" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 6379 -j ACCEPT +fi + +if [ "$CMD" == "mysql" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 3306 -j ACCEPT +fi + +if [ "$CMD" == "rabbitmq" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 4369 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 5672 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport 53284 -j ACCEPT +fi + +if [ "$CMD" == "dnsmasq" ]; then + # NOTE(vish): this could theoretically be setup per network + # for each host, but it seems like overkill + iptables -A nova_input -m tcp -p tcp -s $PRIVATE_RANGE --dport 53 -j ACCEPT + iptables -A nova_input -m udp -p udp -s $PRIVATE_RANGE --dport 53 -j ACCEPT + iptables -A nova_input -m udp -p udp --dport 67 -j ACCEPT + +if [ "$CMD" == "ldap" ]; then + iptables -A nova_input -m tcp -p tcp -d $IP --dport 389 -j ACCEPT +fi + + -- cgit From 564105a3f0087f31a879460d70e73bc358e0e8c0 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 23 Sep 2010 12:18:56 -0700 Subject: made use of nova_ chains a flag and fixed a few typos --- nova/network/linux_net.py | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 53fb2df94..149848750 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -41,6 +41,8 @@ flags.DEFINE_string('bridge_dev', 'eth0', 'network device for bridges') flags.DEFINE_string('routing_source_ip', utils.get_my_ip(), 'Public IP of network host') +flags.DEFINE_string('use_nova_chains', False, + 'use the nova_ routing chains instead of default') DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)] @@ -48,18 +50,18 @@ def init_host(): """Basic networking setup goes here""" # NOTE(devcamcar): Cloud public DNAT entries, CloudPipe port # forwarding entries and a default DNAT entry. - _confirm_rule("-t nat -A nova_prerouting -s 0.0.0.0/0 " + _confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 " "-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT " "--to-destination %s:%s" % (FLAGS.cc_host, FLAGS.cc_port)) # NOTE(devcamcar): Cloud public SNAT entries and the default # SNAT rule for outbound traffic. - _confirm_rule("-t nat -A nova_postrouting -s %s " + _confirm_rule("POSTROUTING", "-t nat -s %s " "-j SNAT --to-source %s" % (FLAGS.private_range, FLAGS.routing_source_ip)) - _confirm_rule("-A nova_postrouting -s %s MASQUERADE" % FLAGS.private_range) - _confirm_rule("-A nova_postrouting -s %(range)s -d %(range)s" % {'range': FLAGS.private_range}) + _confirm_rule("POSTROUTING", "-t nat -s %s MASQUERADE" % FLAGS.private_range) + _confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s" % {'range': FLAGS.private_range}) def bind_floating_ip(floating_ip): """Bind ip to public interface""" @@ -75,37 +77,37 @@ def unbind_floating_ip(floating_ip): def ensure_vlan_forward(public_ip, port, private_ip): """Sets up forwarding rules for vlan""" - _confirm_rule("nova_forward -d %s -p udp --dport 1194 -j ACCEPT" % private_ip) + _confirm_rule("FORWARD", "-d %s -p udp --dport 1194 -j ACCEPT" % private_ip) _confirm_rule( - "nova_prerouting -t nat -d %s -p udp --dport %s -j DNAT --to %s:1194" + "PREROUTING -t nat -d %s -p udp --dport %s -j DNAT --to %s:1194" % (public_ip, port, private_ip)) def ensure_floating_forward(floating_ip, fixed_ip): """Ensure floating ip forwarding rule""" - _confirm_rule("nova_prerouting -t nat -d %s -j DNAT --to %s" + _confirm_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s" % (floating_ip, fixed_ip)) - _confirm_rule("nova_postrouting -t nat -s %s -j SNAT --to %s" + _confirm_rule("POSTROUTING", "-t nat -s %s -j SNAT --to %s" % (fixed_ip, floating_ip)) # TODO(joshua): Get these from the secgroup datastore entries - _confirm_rule("nova_forward -d %s -p icmp -j ACCEPT" + _confirm_rule("FORWARD", "-d %s -p icmp -j ACCEPT" % (fixed_ip)) for (protocol, port) in DEFAULT_PORTS: _confirm_rule( - "nova_forward -d %s -p %s --dport %s -j ACCEPT" + "FORWARD -d %s -p %s --dport %s -j ACCEPT" % (fixed_ip, protocol, port)) def remove_floating_forward(floating_ip, fixed_ip): """Remove forwarding for floating ip""" - _remove_rule("nova_prerouting -t nat -d %s -j DNAT --to %s" + _remove_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s" % (floating_ip, fixed_ip)) - _remove_rule("nova_postrouting -t nat -s %s -j SNAT --to %s" + _remove_rule("POSTROUTING", "-t nat -s %s -j SNAT --to %s" % (fixed_ip, floating_ip)) - _remove_rule("nova_forward -d %s -p icmp -j ACCEPT" + _remove_rule("FORWARD", "-d %s -p icmp -j ACCEPT" % (fixed_ip)) for (protocol, port) in DEFAULT_PORTS: - _remove_rule("nova_forward -d %s -p %s --dport %s -j ACCEPT" + _remove_rule("FORWARD", "-d %s -p %s --dport %s -j ACCEPT" % (fixed_ip, protocol, port)) @@ -141,7 +143,7 @@ def ensure_bridge(bridge, interface, net_attrs=None): net_attrs['gateway'], net_attrs['broadcast'], net_attrs['netmask'])) - _confirm_rule("nova_forward --in-interface %s -j ACCEPT" % bridge) + _confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge) else: _execute("sudo ifconfig %s up" % bridge) @@ -211,15 +213,19 @@ def _device_exists(device): return not err -def _confirm_rule(cmd): +def _confirm_rule(chain, cmd): """Delete and re-add iptables rule""" - _execute("sudo iptables --delete %s" % (cmd), check_exit_code=False) - _execute("sudo iptables -I %s" % (cmd)) + if FLAGS.use_nova_chains: + chain = "nova_%s" % chain.lower() + _execute("sudo iptables --delete %s %s" % (chain, cmd), check_exit_code=False) + _execute("sudo iptables -I %s %s" % (chain, cmd)) -def _remove_rule(cmd): +def _remove_rule(chain, cmd): """Remove iptables rule""" - _execute("sudo iptables --delete %s" % (cmd)) + if FLAGS.use_nova_chains: + chain = "%S" % chain.lower() + _execute("sudo iptables --delete %s %s" % (chain, cmd)) def _dnsmasq_cmd(net): -- cgit From 81fc2078ca3d3e07728a39b6cdec47af871f2f2f Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 23 Sep 2010 12:20:40 -0700 Subject: removed extra line in manage --- nova/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/manager.py b/nova/manager.py index 65300354b..94e4ae959 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -44,4 +44,3 @@ class Manager(object): Child classes should override this method. """ pass - -- cgit From 065257fb0686d848fcf20235a4e04b76872a5b01 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 23 Sep 2010 12:43:41 -0700 Subject: fixed a few missing params from iptables rules --- nova/network/linux_net.py | 4 ++-- nova/service.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 149848750..38a616e83 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -60,8 +60,8 @@ def init_host(): "-j SNAT --to-source %s" % (FLAGS.private_range, FLAGS.routing_source_ip)) - _confirm_rule("POSTROUTING", "-t nat -s %s MASQUERADE" % FLAGS.private_range) - _confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s" % {'range': FLAGS.private_range}) + _confirm_rule("POSTROUTING", "-t nat -s %s -j MASQUERADE" % FLAGS.private_range) + _confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" % {'range': FLAGS.private_range}) def bind_floating_ip(floating_ip): """Bind ip to public interface""" diff --git a/nova/service.py b/nova/service.py index 870dd6ceb..dcd2a09ef 100644 --- a/nova/service.py +++ b/nova/service.py @@ -50,6 +50,7 @@ class Service(object, service.Service): self.topic = topic manager_class = utils.import_class(manager) self.manager = manager_class(host=host, *args, **kwargs) + self.manager.init_host() self.model_disconnected = False super(Service, self).__init__(*args, **kwargs) try: -- 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 15c2678d3e3899e7ab6180dce457ae6d3e54937d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 24 Sep 2010 18:21:58 -0700 Subject: improved the shell script for iptables --- tools/setup_iptables.sh | 124 ++++++++++++++++++++++++++++++------------------ 1 file changed, 78 insertions(+), 46 deletions(-) mode change 100644 => 100755 tools/setup_iptables.sh diff --git a/tools/setup_iptables.sh b/tools/setup_iptables.sh old mode 100644 new mode 100755 index b1ab1c6f7..fd32f6f82 --- a/tools/setup_iptables.sh +++ b/tools/setup_iptables.sh @@ -1,93 +1,125 @@ #!/usr/bin/env bash - -CMD="global" -IP="XXX" -PRIVATE_RANGE="10.128.0.0/12" +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. if [ -n "$1" ]; then CMD=$1 +else + CMD="all" fi if [ -n "$2" ]; then IP=$2 +else + # NOTE(vish): this will just get the first ip in the list, so if you + # have more than one eth device set up, this will fail + IP=`ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'` fi if [ -n "$3" ]; then PRIVATE_RANGE=$3 +else + PRIVATE_RANGE="10.0.0.0/12" +fi + + +if [ -n "$4" ]; then + MGMT_IP=$4 +else + MGMT_IP="$IP" fi -if [ "$CMD" == "global" ]; then - iptables -P INPUT DROP - iptables -A INPUT -m state --state INVALID -j DROP - iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT - iptables -A INPUT -m tcp -p tcp -d $MGMT_IP --dport 22 -j ACCEPT - iptables -A INPUT -m udp -p udp --dport 123 -j ACCEPT - iptables -N nova_input - iptables -A INPUT -j nova_input - iptables -A INPUT -p icmp -j ACCEPT - iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset - iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable - - iptables -P FORWARD DROP - iptables -A FORWARD -m state --state INVALID -j DROP - iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT - iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu - iptables -N nova_forward - iptables -A FORWARD -j nova_forward - - iptables -P OUTPUT DROP - iptables -A OUTPUT -m state --state INVALID -j DROP - iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT - iptables -N nova_output - iptables -A OUTPUT -j nova_output - - iptables -t nat -N nova_prerouting - iptables -t nat -A PREROUTING -j nova_prerouting - - iptables -t nat -N nova_postrouting - iptables -t nat -A POSTROUTING -j nova_postrouting - - iptables -t nat -N nova_output - iptables -t nat -A OUTPUT -j nova_output - - # ganglia (all hosts) +iptables -F +iptables -P INPUT DROP +iptables -A INPUT -m state --state INVALID -j DROP +iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT +iptables -A INPUT -m tcp -p tcp -d $MGMT_IP --dport 22 -j ACCEPT +iptables -A INPUT -m udp -p udp --dport 123 -j ACCEPT +iptables -N nova_input +iptables -A INPUT -j nova_input +iptables -A INPUT -p icmp -j ACCEPT +iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset +iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable + +iptables -P FORWARD DROP +iptables -A FORWARD -m state --state INVALID -j DROP +iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT +iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu +iptables -N nova_forward +iptables -A FORWARD -j nova_forward + +iptables -P OUTPUT DROP +iptables -A OUTPUT -m state --state INVALID -j DROP +iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT +iptables -N nova_output +iptables -A OUTPUT -j nova_output + +iptables -t nat -N nova_prerouting +iptables -t nat -A PREROUTING -j nova_prerouting + +iptables -t nat -N nova_postrouting +iptables -t nat -A POSTROUTING -j nova_postrouting + +iptables -t nat -N nova_output +iptables -t nat -A OUTPUT -j nova_output + +if [ "$CMD" == "ganglia" ] || [ "$CMD" == "all" ]; then iptables -A nova_input -m tcp -p tcp -d $IP --dport 8649 -j ACCEPT iptables -A nova_input -m udp -p udp -d $IP --dport 8649 -j ACCEPT fi -if [ "$CMD" == "dashboard" ]; then +if [ "$CMD" == "dashboard" ] || [ "$CMD" == "all" ]; then # dashboard iptables -A nova_input -m tcp -p tcp -d $IP --dport 80 -j ACCEPT iptables -A nova_input -m tcp -p tcp -d $IP --dport 443 -j ACCEPT fi -if [ "$CMD" == "objectstore" ]; then +if [ "$CMD" == "objectstore" ] || [ "$CMD" == "all" ]; then iptables -A nova_input -m tcp -p tcp -d $IP --dport 3333 -j ACCEPT +fi + +if [ "$CMD" == "api" ] || [ "$CMD" == "all" ]; then iptables -A nova_input -m tcp -p tcp -d $IP --dport 8773 -j ACCEPT fi -if [ "$CMD" == "redis" ]; then +if [ "$CMD" == "redis" ] || [ "$CMD" == "all" ]; then iptables -A nova_input -m tcp -p tcp -d $IP --dport 6379 -j ACCEPT fi -if [ "$CMD" == "mysql" ]; then +if [ "$CMD" == "mysql" ] || [ "$CMD" == "all" ]; then iptables -A nova_input -m tcp -p tcp -d $IP --dport 3306 -j ACCEPT fi -if [ "$CMD" == "rabbitmq" ]; then +if [ "$CMD" == "rabbitmq" ] || [ "$CMD" == "all" ]; then iptables -A nova_input -m tcp -p tcp -d $IP --dport 4369 -j ACCEPT iptables -A nova_input -m tcp -p tcp -d $IP --dport 5672 -j ACCEPT iptables -A nova_input -m tcp -p tcp -d $IP --dport 53284 -j ACCEPT fi -if [ "$CMD" == "dnsmasq" ]; then +if [ "$CMD" == "dnsmasq" ] || [ "$CMD" == "all" ]; then # NOTE(vish): this could theoretically be setup per network # for each host, but it seems like overkill iptables -A nova_input -m tcp -p tcp -s $PRIVATE_RANGE --dport 53 -j ACCEPT iptables -A nova_input -m udp -p udp -s $PRIVATE_RANGE --dport 53 -j ACCEPT iptables -A nova_input -m udp -p udp --dport 67 -j ACCEPT +fi -if [ "$CMD" == "ldap" ]; then +if [ "$CMD" == "ldap" ] || [ "$CMD" == "all" ]; then iptables -A nova_input -m tcp -p tcp -d $IP --dport 389 -j ACCEPT fi -- cgit From 41a598f09baee94125608873f4d7118000fc55ea Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 24 Sep 2010 19:57:41 -0700 Subject: add a reset command --- tools/setup_iptables.sh | 74 +++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/tools/setup_iptables.sh b/tools/setup_iptables.sh index fd32f6f82..7368fadf9 100755 --- a/tools/setup_iptables.sh +++ b/tools/setup_iptables.sh @@ -43,40 +43,48 @@ if [ -n "$4" ]; then else MGMT_IP="$IP" fi +if [ "$CMD" == "clear" ]; then + iptables -P INPUT ACCEPT + iptables -P FORWARD ACCEPT + iptables -P OUTPUT ACCEPT + iptables -F + iptables -X +fi -iptables -F -iptables -P INPUT DROP -iptables -A INPUT -m state --state INVALID -j DROP -iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT -iptables -A INPUT -m tcp -p tcp -d $MGMT_IP --dport 22 -j ACCEPT -iptables -A INPUT -m udp -p udp --dport 123 -j ACCEPT -iptables -N nova_input -iptables -A INPUT -j nova_input -iptables -A INPUT -p icmp -j ACCEPT -iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset -iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable - -iptables -P FORWARD DROP -iptables -A FORWARD -m state --state INVALID -j DROP -iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT -iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu -iptables -N nova_forward -iptables -A FORWARD -j nova_forward - -iptables -P OUTPUT DROP -iptables -A OUTPUT -m state --state INVALID -j DROP -iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT -iptables -N nova_output -iptables -A OUTPUT -j nova_output - -iptables -t nat -N nova_prerouting -iptables -t nat -A PREROUTING -j nova_prerouting - -iptables -t nat -N nova_postrouting -iptables -t nat -A POSTROUTING -j nova_postrouting - -iptables -t nat -N nova_output -iptables -t nat -A OUTPUT -j nova_output +if [ "$CMD" == "base" ] || [ "$CMD" == "all" ]; then + iptables -P INPUT DROP + iptables -A INPUT -m state --state INVALID -j DROP + iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -A INPUT -m tcp -p tcp -d $MGMT_IP --dport 22 -j ACCEPT + iptables -A INPUT -m udp -p udp --dport 123 -j ACCEPT + iptables -N nova_input + iptables -A INPUT -j nova_input + iptables -A INPUT -p icmp -j ACCEPT + iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset + iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable + + iptables -P FORWARD DROP + iptables -A FORWARD -m state --state INVALID -j DROP + iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu + iptables -N nova_forward + iptables -A FORWARD -j nova_forward + + iptables -P OUTPUT DROP + iptables -A OUTPUT -m state --state INVALID -j DROP + iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT + iptables -N nova_output + iptables -A OUTPUT -j nova_output + + iptables -t nat -N nova_prerouting + iptables -t nat -A PREROUTING -j nova_prerouting + + iptables -t nat -N nova_postrouting + iptables -t nat -A POSTROUTING -j nova_postrouting + + iptables -t nat -N nova_output + iptables -t nat -A OUTPUT -j nova_output +fi if [ "$CMD" == "ganglia" ] || [ "$CMD" == "all" ]; then iptables -A nova_input -m tcp -p tcp -d $IP --dport 8649 -j ACCEPT -- cgit From 73cc0e446297781182fab1b8e8447e7c6d100b08 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 24 Sep 2010 23:32:00 -0400 Subject: Update auth manager to have a update_user method and better tests. --- nova/auth/ldapdriver.py | 16 +- nova/auth/manager.py | 6 + nova/tests/auth_unittest.py | 408 +++++++++++++++++++++++++++++--------------- 3 files changed, 290 insertions(+), 140 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 021851ebf..640ea169e 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -256,8 +256,7 @@ class LdapDriver(object): if not self.__user_exists(uid): raise exception.NotFound("User %s doesn't exist" % uid) self.__remove_from_all(uid) - self.conn.delete_s('uid=%s,%s' % (uid, - FLAGS.ldap_user_subtree)) + self.conn.delete_s(self.__uid_to_dn(uid)) def delete_project(self, project_id): """Delete a project""" @@ -265,6 +264,19 @@ class LdapDriver(object): self.__delete_roles(project_dn) self.__delete_group(project_dn) + def modify_user(self, uid, access_key=None, secret_key=None, admin=None): + """Modify an existing project""" + if not access_key and not secret_key and admin is None: + return + attr = [] + if access_key: + attr.append((self.ldap.MOD_REPLACE, 'accessKey', access_key)) + if secret_key: + attr.append((self.ldap.MOD_REPLACE, 'secretKey', secret_key)) + if admin is not None: + attr.append((self.ldap.MOD_REPLACE, 'isAdmin', str(admin).upper())) + self.conn.modify_s(self.__uid_to_dn(uid), attr) + def __user_exists(self, uid): """Check if user exists""" return self.get_user(uid) != None diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 55fbf42aa..0bc12c80f 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -630,6 +630,12 @@ class AuthManager(object): with self.driver() as drv: drv.delete_user(uid) + def modify_user(self, user, access_key=None, secret_key=None, admin=None): + """Modify credentials for a user""" + uid = User.safe_id(user) + with self.driver() as drv: + drv.modify_user(uid, access_key, secret_key, admin) + def get_credentials(self, user, project=None): """Get credential zip for user in project""" if not isinstance(user, User): diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py index 3235dea39..7065105c3 100644 --- a/nova/tests/auth_unittest.py +++ b/nova/tests/auth_unittest.py @@ -28,25 +28,73 @@ from nova.api.ec2 import cloud FLAGS = flags.FLAGS - +class user_generator(object): + def __init__(self, manager, **user_state): + if 'name' not in user_state: + user_state['name'] = 'test1' + self.manager = manager + self.user = manager.create_user(**user_state) + + def __enter__(self): + return self.user + + def __exit__(self, value, type, trace): + self.manager.delete_user(self.user) + +class project_generator(object): + def __init__(self, manager, **project_state): + if 'name' not in project_state: + project_state['name'] = 'testproj' + if 'manager_user' not in project_state: + project_state['manager_user'] = 'test1' + self.manager = manager + self.project = manager.create_project(**project_state) + + def __enter__(self): + return self.project + + def __exit__(self, value, type, trace): + self.manager.delete_project(self.project) + +class user_and_project_generator(object): + def __init__(self, manager, user_state={}, project_state={}): + self.manager = manager + if 'name' not in user_state: + user_state['name'] = 'test1' + if 'name' not in project_state: + project_state['name'] = 'testproj' + if 'manager_user' not in project_state: + project_state['manager_user'] = 'test1' + self.user = manager.create_user(**user_state) + self.project = manager.create_project(**project_state) + + def __enter__(self): + return (self.user, self.project) + + def __exit__(self, value, type, trace): + self.manager.delete_user(self.user) + self.manager.delete_project(self.project) + +# TODO(todd): Have a test class that tests just the AuthManager functions +# and different test classes for User and Project wrappers class AuthTestCase(test.BaseTestCase): def setUp(self): super(AuthTestCase, self).setUp() self.flags(connection_type='fake') self.manager = manager.AuthManager() - def test_001_can_create_users(self): - self.manager.create_user('test1', 'access', 'secret') - self.manager.create_user('test2') - - def test_002_can_get_user(self): - user = self.manager.get_user('test1') + def test_create_and_find_user(self): + with user_generator(self.manager): + self.assert_(self.manager.get_user('test1')) - def test_003_can_retreive_properties(self): - user = self.manager.get_user('test1') - self.assertEqual('test1', user.id) - self.assertEqual('access', user.access) - self.assertEqual('secret', user.secret) + def test_create_and_find_with_properties(self): + with user_generator(self.manager, name="herbert", secret="classified", + access="private-party"): + u = self.manager.get_user('herbert') + self.assertEqual('herbert', u.id) + self.assertEqual('herbert', u.name) + self.assertEqual('classified', u.secret) + self.assertEqual('private-party', u.access) def test_004_signature_is_valid(self): #self.assertTrue(self.manager.authenticate( **boto.generate_url ... ? ? ? )) @@ -63,133 +111,217 @@ class AuthTestCase(test.BaseTestCase): 'export S3_URL="http://127.0.0.1:3333/"\n' + 'export EC2_USER_ID="test1"\n') - def test_010_can_list_users(self): - users = self.manager.get_users() - logging.warn(users) - self.assertTrue(filter(lambda u: u.id == 'test1', users)) - - def test_101_can_add_user_role(self): - self.assertFalse(self.manager.has_role('test1', 'itsec')) - self.manager.add_role('test1', 'itsec') - self.assertTrue(self.manager.has_role('test1', 'itsec')) - - def test_199_can_remove_user_role(self): - self.assertTrue(self.manager.has_role('test1', 'itsec')) - self.manager.remove_role('test1', 'itsec') - self.assertFalse(self.manager.has_role('test1', 'itsec')) - - def test_201_can_create_project(self): - project = self.manager.create_project('testproj', 'test1', 'A test project', ['test1']) - self.assertTrue(filter(lambda p: p.name == 'testproj', self.manager.get_projects())) - self.assertEqual(project.name, 'testproj') - self.assertEqual(project.description, 'A test project') - self.assertEqual(project.project_manager_id, 'test1') - self.assertTrue(project.has_member('test1')) - - def test_202_user1_is_project_member(self): - self.assertTrue(self.manager.get_user('test1').is_project_member('testproj')) - - def test_203_user2_is_not_project_member(self): - self.assertFalse(self.manager.get_user('test2').is_project_member('testproj')) - - def test_204_user1_is_project_manager(self): - self.assertTrue(self.manager.get_user('test1').is_project_manager('testproj')) - - def test_205_user2_is_not_project_manager(self): - self.assertFalse(self.manager.get_user('test2').is_project_manager('testproj')) - - def test_206_can_add_user_to_project(self): - self.manager.add_to_project('test2', 'testproj') - self.assertTrue(self.manager.get_project('testproj').has_member('test2')) - - def test_207_can_remove_user_from_project(self): - self.manager.remove_from_project('test2', 'testproj') - self.assertFalse(self.manager.get_project('testproj').has_member('test2')) - - def test_208_can_remove_add_user_with_role(self): - self.manager.add_to_project('test2', 'testproj') - self.manager.add_role('test2', 'developer', 'testproj') - self.manager.remove_from_project('test2', 'testproj') - self.assertFalse(self.manager.has_role('test2', 'developer', 'testproj')) - self.manager.add_to_project('test2', 'testproj') - self.manager.remove_from_project('test2', 'testproj') - - def test_209_can_generate_x509(self): - # MUST HAVE RUN CLOUD SETUP BY NOW - self.cloud = cloud.CloudController() - self.cloud.setup() - _key, cert_str = self.manager._generate_x509_cert('test1', 'testproj') - logging.debug(cert_str) - - # Need to verify that it's signed by the right intermediate CA - full_chain = crypto.fetch_ca(project_id='testproj', chain=True) - int_cert = crypto.fetch_ca(project_id='testproj', chain=False) - cloud_cert = crypto.fetch_ca() - logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain) - signed_cert = X509.load_cert_string(cert_str) - chain_cert = X509.load_cert_string(full_chain) - int_cert = X509.load_cert_string(int_cert) - cloud_cert = X509.load_cert_string(cloud_cert) - self.assertTrue(signed_cert.verify(chain_cert.get_pubkey())) - self.assertTrue(signed_cert.verify(int_cert.get_pubkey())) - - if not FLAGS.use_intermediate_ca: - self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey())) - else: - self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey())) - - def test_210_can_add_project_role(self): - project = self.manager.get_project('testproj') - self.assertFalse(project.has_role('test1', 'sysadmin')) - self.manager.add_role('test1', 'sysadmin') - self.assertFalse(project.has_role('test1', 'sysadmin')) - project.add_role('test1', 'sysadmin') - self.assertTrue(project.has_role('test1', 'sysadmin')) - - def test_211_can_list_project_roles(self): - project = self.manager.get_project('testproj') - user = self.manager.get_user('test1') - self.manager.add_role(user, 'netadmin', project) - roles = self.manager.get_user_roles(user) - self.assertTrue('sysadmin' in roles) - self.assertFalse('netadmin' in roles) - project_roles = self.manager.get_user_roles(user, project) - self.assertTrue('sysadmin' in project_roles) - self.assertTrue('netadmin' in project_roles) - # has role should be false because global role is missing - self.assertFalse(self.manager.has_role(user, 'netadmin', project)) - - - def test_212_can_remove_project_role(self): - project = self.manager.get_project('testproj') - self.assertTrue(project.has_role('test1', 'sysadmin')) - project.remove_role('test1', 'sysadmin') - self.assertFalse(project.has_role('test1', 'sysadmin')) - self.manager.remove_role('test1', 'sysadmin') - self.assertFalse(project.has_role('test1', 'sysadmin')) - - def test_214_can_retrieve_project_by_user(self): - project = self.manager.create_project('testproj2', 'test2', 'Another test project', ['test2']) - self.assert_(len(self.manager.get_projects()) > 1) - self.assertEqual(len(self.manager.get_projects('test2')), 1) - - def test_220_can_modify_project(self): - self.manager.modify_project('testproj', 'test2', 'new description') - project = self.manager.get_project('testproj') - self.assertEqual(project.project_manager_id, 'test2') - self.assertEqual(project.description, 'new description') - - def test_299_can_delete_project(self): - self.manager.delete_project('testproj') - self.assertFalse(filter(lambda p: p.name == 'testproj', self.manager.get_projects())) - self.manager.delete_project('testproj2') - - def test_999_can_delete_users(self): + def test_can_list_users(self): + with user_generator(self.manager): + with user_generator(self.manager, name="test2"): + users = self.manager.get_users() + self.assert_(filter(lambda u: u.id == 'test1', users)) + self.assert_(filter(lambda u: u.id == 'test2', users)) + self.assert_(not filter(lambda u: u.id == 'test3', users)) + + def test_can_add_and_remove_user_role(self): + with user_generator(self.manager): + self.assertFalse(self.manager.has_role('test1', 'itsec')) + self.manager.add_role('test1', 'itsec') + self.assertTrue(self.manager.has_role('test1', 'itsec')) + self.manager.remove_role('test1', 'itsec') + self.assertFalse(self.manager.has_role('test1', 'itsec')) + + def test_can_create_and_get_project(self): + with user_and_project_generator(self.manager) as (u,p): + self.assert_(self.manager.get_user('test1')) + self.assert_(self.manager.get_user('test1')) + self.assert_(self.manager.get_project('testproj')) + + def test_can_list_projects(self): + with user_and_project_generator(self.manager): + with project_generator(self.manager, name="testproj2"): + projects = self.manager.get_projects() + self.assert_(filter(lambda p: p.name == 'testproj', projects)) + self.assert_(filter(lambda p: p.name == 'testproj2', projects)) + self.assert_(not filter(lambda p: p.name == 'testproj3', + projects)) + + def test_can_create_and_get_project_with_attributes(self): + with user_generator(self.manager): + with project_generator(self.manager, description='A test project'): + project = self.manager.get_project('testproj') + self.assertEqual('A test project', project.description) + + def test_can_create_project_with_manager(self): + with user_and_project_generator(self.manager) as (user, project): + self.assertEqual('test1', project.project_manager_id) + self.assertTrue(user.is_project_manager('testproj')) + + def test_create_project_assigns_manager_to_members(self): + with user_and_project_generator(self.manager) as (user, project): + self.assertTrue(user.is_project_member('testproj')) + self.assertTrue(project.has_member('test1')) + + def test_no_extra_project_members(self): + with user_generator(self.manager, name='test2') as baduser: + with user_and_project_generator(self.manager) as (user, project): + self.assertFalse(baduser.is_project_member('testproj')) + self.assertFalse(project.has_member('test2')) + + def test_no_extra_project_managers(self): + with user_generator(self.manager, name='test2') as baduser: + with user_and_project_generator(self.manager) as (user, project): + self.assertFalse(baduser.is_project_manager('testproj')) + + def test_can_add_user_to_project(self): + with user_generator(self.manager, name='test2') as user: + with user_and_project_generator(self.manager): + self.manager.add_to_project('test2', 'testproj') + self.assertTrue(user.is_project_member('testproj')) + # NOTE(todd): have to reload after the add_to_project + project = self.manager.get_project('testproj') + self.assertTrue(project.has_member('test2')) + + def test_can_remove_user_from_project(self): + with user_generator(self.manager, name='test2') as user: + with user_and_project_generator(self.manager): + self.manager.add_to_project('test2', 'testproj') + self.assertTrue(user.is_project_member('testproj')) + self.manager.remove_from_project('test2', 'testproj') + self.assertFalse(user.is_project_member('testproj')) + # NOTE(todd): have to reload after the membership modifications + project = self.manager.get_project('testproj') + self.assertFalse(project.has_member('test2')) + + def test_can_add_remove_user_with_role(self): + with user_generator(self.manager, name='test2') as user: + with user_and_project_generator(self.manager): + self.manager.add_to_project('test2', 'testproj') + self.manager.add_role('test2', 'developer', 'testproj') + self.assertTrue(user.is_project_member('testproj')) + self.manager.remove_from_project('test2', 'testproj') + self.assertFalse(self.manager.has_role('test2', 'developer', + 'testproj')) + self.assertFalse(user.is_project_member('testproj')) + + def test_can_generate_x509(self): + # NOTE(todd): this doesn't assert against the auth manager + # so it probably belongs in crypto_unittest + # but I'm leaving it where I found it. + with user_and_project_generator(self.manager) as (user, project): + # NOTE(todd): Should mention why we must setup controller first + # (somebody please clue me in) + cloud_controller = cloud.CloudController() + cloud_controller.setup() + _key, cert_str = self.manager._generate_x509_cert('test1', + 'testproj') + logging.debug(cert_str) + + # Need to verify that it's signed by the right intermediate CA + full_chain = crypto.fetch_ca(project_id='testproj', chain=True) + int_cert = crypto.fetch_ca(project_id='testproj', chain=False) + cloud_cert = crypto.fetch_ca() + logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain) + signed_cert = X509.load_cert_string(cert_str) + chain_cert = X509.load_cert_string(full_chain) + int_cert = X509.load_cert_string(int_cert) + cloud_cert = X509.load_cert_string(cloud_cert) + self.assertTrue(signed_cert.verify(chain_cert.get_pubkey())) + self.assertTrue(signed_cert.verify(int_cert.get_pubkey())) + if not FLAGS.use_intermediate_ca: + self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey())) + else: + self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey())) + + def test_adding_role_to_project_is_ignored_unless_added_to_user(self): + with user_and_project_generator(self.manager) as (user, project): + self.assertFalse(project.has_role('test1', 'sysadmin')) + project.add_role('test1', 'sysadmin') + # NOTE(todd): it will still show up in get_user_roles(u, project) + self.assertFalse(project.has_role('test1', 'sysadmin')) + self.manager.add_role('test1', 'sysadmin') + self.assertTrue(project.has_role('test1', 'sysadmin')) + + def test_add_user_role_doesnt_infect_project_roles(self): + with user_and_project_generator(self.manager) as (user, project): + self.assertFalse(project.has_role('test1', 'sysadmin')) + self.manager.add_role('test1', 'sysadmin') + self.assertFalse(project.has_role('test1', 'sysadmin')) + + def test_can_list_user_roles(self): + with user_and_project_generator(self.manager) as (user, project): + self.manager.add_role('test1', 'sysadmin') + roles = self.manager.get_user_roles(user) + self.assertTrue('sysadmin' in roles) + self.assertFalse('netadmin' in roles) + + def test_can_list_project_roles(self): + with user_and_project_generator(self.manager) as (user, project): + self.manager.add_role('test1', 'sysadmin') + project.add_role('test1', 'sysadmin') + project.add_role('test1', 'netadmin') + project_roles = self.manager.get_user_roles(user, project) + self.assertTrue('sysadmin' in project_roles) + self.assertTrue('netadmin' in project_roles) + # has role should be false user-level role is missing + self.assertFalse(self.manager.has_role(user, 'netadmin', project)) + + def test_can_remove_user_roles(self): + with user_and_project_generator(self.manager) as (user, project): + self.manager.add_role('test1', 'sysadmin') + self.assertTrue(user.has_role('sysadmin')) + self.manager.remove_role('test1', 'sysadmin') + self.assertFalse(user.has_role('sysadmin')) + + def test_removing_user_role_hides_it_from_project(self): + with user_and_project_generator(self.manager) as (user, project): + self.manager.add_role('test1', 'sysadmin') + project.add_role('test1', 'sysadmin') + self.assertTrue(project.has_role('test1', 'sysadmin')) + self.manager.remove_role('test1', 'sysadmin') + self.assertFalse(project.has_role('test1', 'sysadmin')) + + def test_can_remove_project_role_but_keep_user_role(self): + with user_and_project_generator(self.manager) as (user, project): + self.manager.add_role('test1', 'sysadmin') + project.add_role('test1', 'sysadmin') + self.assertTrue(user.has_role('sysadmin')) + self.assertTrue(project.has_role('test1', 'sysadmin')) + project.remove_role('test1', 'sysadmin') + self.assertFalse(project.has_role('test1', 'sysadmin')) + self.assertTrue(user.has_role('sysadmin')) + + def test_can_retrieve_project_by_user(self): + with user_and_project_generator(self.manager) as (user, project): + self.assertEqual(1, len(self.manager.get_projects('test1'))) + + def test_can_modify_project(self): + with user_and_project_generator(self.manager): + with user_generator(self.manager, name='test2'): + self.manager.modify_project('testproj', 'test2', 'new desc') + project = self.manager.get_project('testproj') + self.assertEqual('test2', project.project_manager_id) + self.assertEqual('new desc', project.description) + + def test_can_delete_project(self): + with user_generator(self.manager): + self.manager.create_project('testproj', 'test1') + self.assert_(self.manager.get_project('testproj')) + self.manager.delete_project('testproj') + projectlist = self.manager.get_projects() + self.assert_(not filter(lambda p: p.name == 'testproj', + projectlist)) + + def test_can_delete_user(self): + self.manager.create_user('test1') + self.assert_(self.manager.get_user('test1')) self.manager.delete_user('test1') - users = self.manager.get_users() - self.assertFalse(filter(lambda u: u.id == 'test1', users)) - self.manager.delete_user('test2') - self.assertEqual(self.manager.get_user('test2'), None) + userlist = self.manager.get_users() + self.assert_(not filter(lambda u: u.id == 'test1', userlist)) + + def test_can_modify_users(self): + with user_generator(self.manager): + self.manager.modify_user('test1', 'access', 'secret', True) + user = self.manager.get_user('test1') + self.assertEqual('access', user.access) + self.assertEqual('secret', user.secret) + self.assertTrue(user.is_admin()) if __name__ == "__main__": -- cgit From 39e06d83d265036eb0104cd55d4a828271f62e96 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sat, 25 Sep 2010 00:35:59 -0400 Subject: Test the AuthManager interface explicitly, in case the user/project wrappers fail or change at some point. Those interfaces should be tested on their own. --- nova/tests/auth_unittest.py | 113 +++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 58 deletions(-) diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py index 7065105c3..c3efc577f 100644 --- a/nova/tests/auth_unittest.py +++ b/nova/tests/auth_unittest.py @@ -75,11 +75,9 @@ class user_and_project_generator(object): self.manager.delete_user(self.user) self.manager.delete_project(self.project) -# TODO(todd): Have a test class that tests just the AuthManager functions -# and different test classes for User and Project wrappers -class AuthTestCase(test.BaseTestCase): +class AuthManagerTestCase(test.BaseTestCase): def setUp(self): - super(AuthTestCase, self).setUp() + super(AuthManagerTestCase, self).setUp() self.flags(connection_type='fake') self.manager = manager.AuthManager() @@ -151,54 +149,54 @@ class AuthTestCase(test.BaseTestCase): def test_can_create_project_with_manager(self): with user_and_project_generator(self.manager) as (user, project): self.assertEqual('test1', project.project_manager_id) - self.assertTrue(user.is_project_manager('testproj')) + self.assertTrue(self.manager.is_project_manager(user, project)) def test_create_project_assigns_manager_to_members(self): with user_and_project_generator(self.manager) as (user, project): - self.assertTrue(user.is_project_member('testproj')) - self.assertTrue(project.has_member('test1')) + self.assertTrue(self.manager.is_project_member(user, project)) def test_no_extra_project_members(self): with user_generator(self.manager, name='test2') as baduser: with user_and_project_generator(self.manager) as (user, project): - self.assertFalse(baduser.is_project_member('testproj')) - self.assertFalse(project.has_member('test2')) + self.assertFalse(self.manager.is_project_member(baduser, + project)) def test_no_extra_project_managers(self): with user_generator(self.manager, name='test2') as baduser: with user_and_project_generator(self.manager) as (user, project): - self.assertFalse(baduser.is_project_manager('testproj')) + self.assertFalse(self.manager.is_project_manager(baduser, + project)) def test_can_add_user_to_project(self): with user_generator(self.manager, name='test2') as user: - with user_and_project_generator(self.manager): - self.manager.add_to_project('test2', 'testproj') - self.assertTrue(user.is_project_member('testproj')) - # NOTE(todd): have to reload after the add_to_project + with user_and_project_generator(self.manager) as (_user, project): + self.manager.add_to_project(user, project) project = self.manager.get_project('testproj') - self.assertTrue(project.has_member('test2')) + self.assertTrue(self.manager.is_project_member(user, project)) def test_can_remove_user_from_project(self): with user_generator(self.manager, name='test2') as user: - with user_and_project_generator(self.manager): - self.manager.add_to_project('test2', 'testproj') - self.assertTrue(user.is_project_member('testproj')) - self.manager.remove_from_project('test2', 'testproj') - self.assertFalse(user.is_project_member('testproj')) - # NOTE(todd): have to reload after the membership modifications + with user_and_project_generator(self.manager) as (_user, project): + self.manager.add_to_project(user, project) project = self.manager.get_project('testproj') - self.assertFalse(project.has_member('test2')) + self.assertTrue(self.manager.is_project_member(user, project)) + self.manager.remove_from_project(user, project) + project = self.manager.get_project('testproj') + self.assertFalse(self.manager.is_project_member(user, project)) def test_can_add_remove_user_with_role(self): with user_generator(self.manager, name='test2') as user: - with user_and_project_generator(self.manager): - self.manager.add_to_project('test2', 'testproj') - self.manager.add_role('test2', 'developer', 'testproj') - self.assertTrue(user.is_project_member('testproj')) - self.manager.remove_from_project('test2', 'testproj') - self.assertFalse(self.manager.has_role('test2', 'developer', - 'testproj')) - self.assertFalse(user.is_project_member('testproj')) + with user_and_project_generator(self.manager) as (_user, project): + # NOTE(todd): after modifying users you must reload project + self.manager.add_to_project(user, project) + project = self.manager.get_project('testproj') + self.manager.add_role(user, 'developer', project) + self.assertTrue(self.manager.is_project_member(user, project)) + self.manager.remove_from_project(user, project) + project = self.manager.get_project('testproj') + self.assertFalse(self.manager.has_role(user, 'developer', + project)) + self.assertFalse(self.manager.is_project_member(user, project)) def test_can_generate_x509(self): # NOTE(todd): this doesn't assert against the auth manager @@ -231,31 +229,31 @@ class AuthTestCase(test.BaseTestCase): def test_adding_role_to_project_is_ignored_unless_added_to_user(self): with user_and_project_generator(self.manager) as (user, project): - self.assertFalse(project.has_role('test1', 'sysadmin')) - project.add_role('test1', 'sysadmin') + self.assertFalse(self.manager.has_role(user, 'sysadmin', project)) + self.manager.add_role(user, 'sysadmin', project) # NOTE(todd): it will still show up in get_user_roles(u, project) - self.assertFalse(project.has_role('test1', 'sysadmin')) - self.manager.add_role('test1', 'sysadmin') - self.assertTrue(project.has_role('test1', 'sysadmin')) + self.assertFalse(self.manager.has_role(user, 'sysadmin', project)) + self.manager.add_role(user, 'sysadmin') + self.assertTrue(self.manager.has_role(user, 'sysadmin', project)) def test_add_user_role_doesnt_infect_project_roles(self): with user_and_project_generator(self.manager) as (user, project): - self.assertFalse(project.has_role('test1', 'sysadmin')) - self.manager.add_role('test1', 'sysadmin') - self.assertFalse(project.has_role('test1', 'sysadmin')) + self.assertFalse(self.manager.has_role(user, 'sysadmin', project)) + self.manager.add_role(user, 'sysadmin') + self.assertFalse(self.manager.has_role(user, 'sysadmin', project)) def test_can_list_user_roles(self): with user_and_project_generator(self.manager) as (user, project): - self.manager.add_role('test1', 'sysadmin') + self.manager.add_role(user, 'sysadmin') roles = self.manager.get_user_roles(user) self.assertTrue('sysadmin' in roles) self.assertFalse('netadmin' in roles) def test_can_list_project_roles(self): with user_and_project_generator(self.manager) as (user, project): - self.manager.add_role('test1', 'sysadmin') - project.add_role('test1', 'sysadmin') - project.add_role('test1', 'netadmin') + self.manager.add_role(user, 'sysadmin') + self.manager.add_role(user, 'sysadmin', project) + self.manager.add_role(user, 'netadmin', project) project_roles = self.manager.get_user_roles(user, project) self.assertTrue('sysadmin' in project_roles) self.assertTrue('netadmin' in project_roles) @@ -264,28 +262,27 @@ class AuthTestCase(test.BaseTestCase): def test_can_remove_user_roles(self): with user_and_project_generator(self.manager) as (user, project): - self.manager.add_role('test1', 'sysadmin') - self.assertTrue(user.has_role('sysadmin')) - self.manager.remove_role('test1', 'sysadmin') - self.assertFalse(user.has_role('sysadmin')) + self.manager.add_role(user, 'sysadmin') + self.assertTrue(self.manager.has_role(user, 'sysadmin')) + self.manager.remove_role(user, 'sysadmin') + self.assertFalse(self.manager.has_role(user, 'sysadmin')) def test_removing_user_role_hides_it_from_project(self): with user_and_project_generator(self.manager) as (user, project): - self.manager.add_role('test1', 'sysadmin') - project.add_role('test1', 'sysadmin') - self.assertTrue(project.has_role('test1', 'sysadmin')) - self.manager.remove_role('test1', 'sysadmin') - self.assertFalse(project.has_role('test1', 'sysadmin')) + self.manager.add_role(user, 'sysadmin') + self.manager.add_role(user, 'sysadmin', project) + self.assertTrue(self.manager.has_role(user, 'sysadmin', project)) + self.manager.remove_role(user, 'sysadmin') + self.assertFalse(self.manager.has_role(user, 'sysadmin', project)) def test_can_remove_project_role_but_keep_user_role(self): with user_and_project_generator(self.manager) as (user, project): - self.manager.add_role('test1', 'sysadmin') - project.add_role('test1', 'sysadmin') - self.assertTrue(user.has_role('sysadmin')) - self.assertTrue(project.has_role('test1', 'sysadmin')) - project.remove_role('test1', 'sysadmin') - self.assertFalse(project.has_role('test1', 'sysadmin')) - self.assertTrue(user.has_role('sysadmin')) + self.manager.add_role(user, 'sysadmin') + self.manager.add_role(user, 'sysadmin', project) + self.assertTrue(self.manager.has_role(user, 'sysadmin')) + self.manager.remove_role(user, 'sysadmin', project) + self.assertFalse(self.manager.has_role(user, 'sysadmin', project)) + self.assertTrue(self.manager.has_role(user, 'sysadmin')) def test_can_retrieve_project_by_user(self): with user_and_project_generator(self.manager) as (user, project): -- cgit From c3fcb1b2176f4b7afbffb3555da55c0754bacaad Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 25 Sep 2010 01:05:39 -0700 Subject: flush the nova chains --- tools/setup_iptables.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/setup_iptables.sh b/tools/setup_iptables.sh index 7368fadf9..d045b50cd 100755 --- a/tools/setup_iptables.sh +++ b/tools/setup_iptables.sh @@ -48,6 +48,9 @@ if [ "$CMD" == "clear" ]; then iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT iptables -F + iptables -F nova_input + iptables -F nova_output + iptables -F nova_forward iptables -X fi -- cgit From 125e69dd42f6f91f727258dc388d15ce63076d1f Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 25 Sep 2010 02:51:50 -0700 Subject: allow mgmt ip access to api --- tools/setup_iptables.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/setup_iptables.sh b/tools/setup_iptables.sh index d045b50cd..b7e2f9a11 100755 --- a/tools/setup_iptables.sh +++ b/tools/setup_iptables.sh @@ -106,6 +106,9 @@ fi if [ "$CMD" == "api" ] || [ "$CMD" == "all" ]; then iptables -A nova_input -m tcp -p tcp -d $IP --dport 8773 -j ACCEPT + if [ "$IP" != "$MGMT_IP" ]; then + iptables -A nova_input -m tcp -p tcp -d $MGMT_IP --dport 8773 -j ACCEPT + fi fi if [ "$CMD" == "redis" ] || [ "$CMD" == "all" ]; then -- cgit From 7ce67ea60f6e7d20665c10318b29e2659fd91513 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 25 Sep 2010 08:35:16 -0700 Subject: fix a few missed calls to _confirm_rule and 80 char issues --- nova/network/linux_net.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 38a616e83..8058c970d 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -60,8 +60,10 @@ def init_host(): "-j SNAT --to-source %s" % (FLAGS.private_range, FLAGS.routing_source_ip)) - _confirm_rule("POSTROUTING", "-t nat -s %s -j MASQUERADE" % FLAGS.private_range) - _confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" % {'range': FLAGS.private_range}) + _confirm_rule("POSTROUTING", "-t nat -s %s -j MASQUERADE" % + FLAGS.private_range) + _confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" % + {'range': FLAGS.private_range}) def bind_floating_ip(floating_ip): """Bind ip to public interface""" @@ -77,9 +79,10 @@ def unbind_floating_ip(floating_ip): def ensure_vlan_forward(public_ip, port, private_ip): """Sets up forwarding rules for vlan""" - _confirm_rule("FORWARD", "-d %s -p udp --dport 1194 -j ACCEPT" % private_ip) - _confirm_rule( - "PREROUTING -t nat -d %s -p udp --dport %s -j DNAT --to %s:1194" + _confirm_rule("FORWARD", "-d %s -p udp --dport 1194 -j ACCEPT" % + private_ip) + _confirm_rule("PREROUTING", + "-t nat -d %s -p udp --dport %s -j DNAT --to %s:1194" % (public_ip, port, private_ip)) @@ -93,8 +96,7 @@ def ensure_floating_forward(floating_ip, fixed_ip): _confirm_rule("FORWARD", "-d %s -p icmp -j ACCEPT" % (fixed_ip)) for (protocol, port) in DEFAULT_PORTS: - _confirm_rule( - "FORWARD -d %s -p %s --dport %s -j ACCEPT" + _confirm_rule("FORWARD","-d %s -p %s --dport %s -j ACCEPT" % (fixed_ip, protocol, port)) -- cgit From 888a99182ca3152f68b762dab4fc95d7d3f1cadb Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 25 Sep 2010 09:28:05 -0700 Subject: add forwarding ACCEPT for outgoing packets on compute host --- nova/network/linux_net.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 8058c970d..78cc64cb0 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -148,6 +148,7 @@ def ensure_bridge(bridge, interface, net_attrs=None): _confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge) else: _execute("sudo ifconfig %s up" % bridge) + _confirm_rule("FORWARD", "--out-interface %s -j ACCEPT" % bridge) def get_dhcp_hosts(context, network_id): -- cgit From 6a3cd55a9c933c329da1117179d676e9141c5b4d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 25 Sep 2010 10:47:51 -0700 Subject: disable output drop for the moment because it is too restrictive --- tools/setup_iptables.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/setup_iptables.sh b/tools/setup_iptables.sh index b7e2f9a11..dd91c76e0 100755 --- a/tools/setup_iptables.sh +++ b/tools/setup_iptables.sh @@ -52,6 +52,11 @@ if [ "$CMD" == "clear" ]; then iptables -F nova_output iptables -F nova_forward iptables -X + iptables -t nat -F + iptables -t nat -F nova_input + iptables -t nat -F nova_output + iptables -t nat -F nova_forward + iptables -t nat -X fi if [ "$CMD" == "base" ] || [ "$CMD" == "all" ]; then @@ -73,7 +78,7 @@ if [ "$CMD" == "base" ] || [ "$CMD" == "all" ]; then iptables -N nova_forward iptables -A FORWARD -j nova_forward - iptables -P OUTPUT DROP + # iptables -P OUTPUT DROP # too restrictive for the moment iptables -A OUTPUT -m state --state INVALID -j DROP iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -N nova_output -- 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 0d0884b2c1692d03e0994baecbb23ce24ef71e44 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 25 Sep 2010 13:53:29 -0700 Subject: allow in and out for network and compute hosts --- nova/network/linux_net.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 78cc64cb0..6b47b6d97 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -145,10 +145,10 @@ def ensure_bridge(bridge, interface, net_attrs=None): net_attrs['gateway'], net_attrs['broadcast'], net_attrs['netmask'])) - _confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge) else: _execute("sudo ifconfig %s up" % bridge) - _confirm_rule("FORWARD", "--out-interface %s -j ACCEPT" % bridge) + _confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge) + _confirm_rule("FORWARD", "--out-interface %s -j ACCEPT" % bridge) def get_dhcp_hosts(context, network_id): -- cgit From 5d6ab2b2540743e0a53b01129df722610b3ae3b6 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 25 Sep 2010 18:33:27 -0700 Subject: reorganize iptables clear and make sure use_nova_chains is a boolean --- nova/network/linux_net.py | 4 ++-- tools/setup_iptables.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 6b47b6d97..01a1d2ad0 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -41,8 +41,8 @@ flags.DEFINE_string('bridge_dev', 'eth0', 'network device for bridges') flags.DEFINE_string('routing_source_ip', utils.get_my_ip(), 'Public IP of network host') -flags.DEFINE_string('use_nova_chains', False, - 'use the nova_ routing chains instead of default') +flags.DEFINE_boo('use_nova_chains', False, + 'use the nova_ routing chains instead of default') DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)] diff --git a/tools/setup_iptables.sh b/tools/setup_iptables.sh index dd91c76e0..b6b8414e3 100755 --- a/tools/setup_iptables.sh +++ b/tools/setup_iptables.sh @@ -48,15 +48,15 @@ if [ "$CMD" == "clear" ]; then iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT iptables -F + iptables -t nat -F iptables -F nova_input iptables -F nova_output iptables -F nova_forward - iptables -X - iptables -t nat -F iptables -t nat -F nova_input iptables -t nat -F nova_output iptables -t nat -F nova_forward iptables -t nat -X + iptables -X fi if [ "$CMD" == "base" ] || [ "$CMD" == "all" ]; then -- 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 4b26e60e3c84d4535fbd4ba7a3b2bf29f2121072 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 28 Sep 2010 14:09:53 -0400 Subject: Instance & Image renaming fixes. --- nova/endpoint/cloud.py | 24 ++++++++++-------------- nova/endpoint/images.py | 6 ++++-- nova/objectstore/handler.py | 18 +++++++++--------- nova/objectstore/image.py | 14 ++++++-------- nova/tests/cloud_unittest.py | 2 +- nova/tests/objectstore_unittest.py | 6 +++--- 6 files changed, 33 insertions(+), 37 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 2c174bb81..9c96ced9c 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -310,10 +310,11 @@ class CloudController(object): 'volume_id': volume['str_id']}] else: v['attachmentSet'] = [{}] - if 'display_name' in volume: - v['display_name'] = volume['display_name'] - if 'display_description' in volume: - v['display_description'] = volume['display_description'] + + # TODO(todd): check api version and only pass back to nova-aware + # clients + v['display_name'] = volume['display_name'] + v['display_description'] = volume['display_description'] return v @rbac.allow('projectmanager', 'sysadmin') @@ -746,7 +747,9 @@ class CloudController(object): if field in kwargs: changes[field] = kwargs[field] if changes: - db.instance_update(context, instance_id, kwargs) + db_context = {} + inst = db.instance_get_by_str(db_context, instance_id) + db.instance_update(db_context, inst['id'], kwargs) return defer.succeed(True) @rbac.allow('projectmanager', 'sysadmin') @@ -814,13 +817,6 @@ class CloudController(object): return defer.succeed(result) @rbac.allow('projectmanager', 'sysadmin') - def set_image_name(self, context, image_id, name): - result = images.update_user_editable_field(context, image_id, - 'displayName', name) - return defer.succeed(result) - - @rbac.allow('projectmanager', 'sysadmin') - def set_image_description(self, context, image_id, name): - result = images.update_user_editable_field(context, image_id, - 'displayDescription', name) + def update_image(self, context, image_id, **kwargs): + result = images.update(context, image_id, dict(kwargs)) return defer.succeed(result) diff --git a/nova/endpoint/images.py b/nova/endpoint/images.py index a63c059bb..cb54cdda2 100644 --- a/nova/endpoint/images.py +++ b/nova/endpoint/images.py @@ -43,11 +43,13 @@ def modify(context, image_id, operation): return True -def update_user_editable_field(context, image_id, field, value): +def update(context, image_id, attributes): + """update an image's attributes / info.json""" + attributes.update({"image_id": image_id}) conn(context).make_request( method='POST', bucket='_images', - query_args=qs({'image_id': image_id, 'field': field, 'value': value})) + query_args=qs(attributes)) return True def register(context, image_location): diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py index f4596e982..daf633ac2 100644 --- a/nova/objectstore/handler.py +++ b/nova/objectstore/handler.py @@ -352,6 +352,8 @@ class ImagesResource(resource.Resource): m[u'imageType'] = m['type'] elif 'imageType' in m: m[u'type'] = m['imageType'] + if 'displayName' not in m: + m[u'displayName'] = u'' return m request.write(json.dumps([decorate(i.metadata) for i in images])) @@ -386,23 +388,21 @@ class ImagesResource(resource.Resource): image_id = get_argument(request, 'image_id', u'') image_object = image.Image(image_id) if not image_object.is_authorized(request.context): - logging.debug("not authorized for handle_POST in images") + logging.debug("not authorized for render_POST in images") raise exception.NotAuthorized operation = get_argument(request, 'operation', u'') - field = get_argument(request, 'field', u'') - value = get_argument(request, 'value', u'') if operation: # operation implies publicity toggle logging.debug("handling publicity toggle") image_object.set_public(operation=='add') - elif field: - # field implies user field editing (value can be blank) - logging.debug("update user field") - image_object.update_user_editable_field(field, value) else: - logging.debug("unknown action for handle_POST in images") - + # other attributes imply update + logging.debug("update user fields") + clean_args = {} + for arg in request.args.keys(): + clean_args[arg] = request.args[arg][0] + image_object.update_user_editable_fields(clean_args) return '' def render_DELETE(self, request): # pylint: disable-msg=R0201 diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index ad1745af6..def1b8167 100644 --- a/nova/objectstore/image.py +++ b/nova/objectstore/image.py @@ -82,15 +82,13 @@ class Image(object): with open(os.path.join(self.path, 'info.json'), 'w') as f: json.dump(md, f) - def update_user_editable_field(self, field, value): - fields = ['displayName', 'displayDescription'] - if field not in fields: - raise KeyError("Invalid field: %s" % field) + def update_user_editable_fields(self, args): + """args is from the request parameters, so requires extra cleaning""" + fields = {'display_name': 'displayName', 'description': 'description'} info = self.metadata - if value: - info[field] = value - elif field in info: - del info[field] + for field in fields.keys(): + if field in args: + info[fields[field]] = args[field] with open(os.path.join(self.path, 'info.json'), 'w') as f: json.dump(info, f) diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index cea2a67ce..f7340f12d 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -219,7 +219,7 @@ class CloudTestCase(test.BaseTestCase): def test_update_of_instance_display_fields(self): inst = db.instance_create({}, {}) - self.cloud.update_instance(self.context, inst['id'], + self.cloud.update_instance(self.context, inst['str_id'], display_name='c00l 1m4g3') inst = db.instance_get({}, inst['id']) self.assertEqual('c00l 1m4g3', inst['display_name']) diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py index 82c6e2c49..4216cf190 100644 --- a/nova/tests/objectstore_unittest.py +++ b/nova/tests/objectstore_unittest.py @@ -165,10 +165,10 @@ class ObjectStoreTestCase(test.BaseTestCase): self.assertFalse(my_img.is_authorized(self.context)) # change user-editable fields - my_img.update_user_editable_field('displayName', 'my cool image') + my_img.update_user_editable_fields({'display_name': 'my cool image'}) self.assertEqual('my cool image', my_img.metadata['displayName']) - my_img.update_user_editable_field('displayName', '') - self.assert_(not 'displayName' in my_img.metadata) + my_img.update_user_editable_fields({'display_name': ''}) + self.assert_(not my_img.metadata['displayName']) class TestHTTPChannel(http.HTTPChannel): -- 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 08a6dc59add5d72c6d925d1ca43868557a4b3148 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 28 Sep 2010 15:54:05 -0400 Subject: Hook the AuthManger#modify_user method into nova-manage commands. --- bin/nova-manage | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/bin/nova-manage b/bin/nova-manage index baa1cb4db..da18e7d1b 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -228,6 +228,19 @@ class UserCommands(object): for user in self.manager.get_users(): print user.name + def modify(self, name, access_key, secret_key, is_admin): + """update a users keys & admin flag + arguments: accesskey secretkey admin + leave any field blank to ignore it, admin should be 'T', 'F', or blank + """ + if not is_admin: + is_admin = None + elif is_admin.upper()[0] == 'T': + is_admin = True + else: + is_admin = False + print "is_admin: %r" % is_admin + self.manager.modify_user(name, access_key, secret_key, is_admin) class ProjectCommands(object): """Class for managing projects.""" -- 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 c1f7914f9d8c4f7687c67de37c5eda5a95245a0d Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 28 Sep 2010 16:41:39 -0400 Subject: Remove TODO, since apparently newer boto doesn't die on extra fields. --- nova/api/ec2/cloud.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4defef87e..528380f0f 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -286,8 +286,6 @@ class CloudController(object): else: v['attachmentSet'] = [{}] - # TODO(todd): check api version and only pass back to nova-aware - # clients v['display_name'] = volume['display_name'] v['display_description'] = volume['display_description'] return v -- cgit From c80c0786baadf521c86ceff21288e3760aaea5bd Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 28 Sep 2010 16:47:29 -0400 Subject: Add authorization info for cloud endpoints. --- nova/api/ec2/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index f0aa57ee4..7a958f841 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -158,12 +158,14 @@ class Authorizer(wsgi.Middleware): 'RunInstances': ['projectmanager', 'sysadmin'], 'TerminateInstances': ['projectmanager', 'sysadmin'], 'RebootInstances': ['projectmanager', 'sysadmin'], + 'UpdateInstance': ['projectmanager', 'sysadmin'], 'DeleteVolume': ['projectmanager', 'sysadmin'], 'DescribeImages': ['all'], 'DeregisterImage': ['projectmanager', 'sysadmin'], 'RegisterImage': ['projectmanager', 'sysadmin'], 'DescribeImageAttribute': ['all'], 'ModifyImageAttribute': ['projectmanager', 'sysadmin'], + 'UpdateImage': ['projectmanager', 'sysadmin'], }, 'AdminController': { # All actions have the same permission: ['none'] (the default) -- 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 c4df3d63c83c073664a9ed0abaefe2adbe4cd061 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 28 Sep 2010 14:59:32 -0700 Subject: fix test for editable image --- nova/tests/cloud_unittest.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 8db4aeb77..cce4a5568 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -202,18 +202,37 @@ class CloudTestCase(test.BaseTestCase): # data = self.cloud.get_metadata(instance(i)['private_dns_name']) # self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i) + @staticmethod + def _fake_set_image_description(ctxt, image_id, description): + from nova.objectstore import handler + class req: + pass + request = req() + request.context = ctxt + request.args = {'image_id': [image_id], + 'description': [description]} + + resource = handler.ImagesResource() + resource.render_POST(request) + def test_user_editable_image_endpoint(self): pathdir = os.path.join(FLAGS.images_path, 'ami-testing') os.mkdir(pathdir) - info = {} + info = {'isPublic': False} with open(os.path.join(pathdir, 'info.json'), 'w') as f: json.dump(info, f) - yield self.cloud.set_image_description(self.context, 'ami-testing', - 'Foo Img') img = image.Image('ami-testing') - self.assertEqual('Foo Img', img.metadata['displayDescription']) - self.cloud.set_image_description(self.context, 'ami-testing', '') - self.assert_(not 'displayDescription' in img.metadata) + # self.cloud.set_image_description(self.context, 'ami-testing', + # 'Foo Img') + # NOTE(vish): Above won't work unless we start objectstore or create + # a fake version of api/ec2/images.py conn that can + # call methods directly instead of going through boto. + # for now, just cheat and call the method directly + self._fake_set_image_description(self.context, 'ami-testing', + 'Foo Img') + self.assertEqual('Foo Img', img.metadata['description']) + self._fake_set_image_description(self.context, 'ami-testing', '') + self.assertEqual('', img.metadata['description']) def test_update_of_instance_display_fields(self): inst = db.instance_create({}, {}) -- 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 1e4bca12e7e06698d3a13d6a208be90647f27555 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 28 Sep 2010 17:15:59 -0700 Subject: typo s/boo/bool --- nova/network/linux_net.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 01a1d2ad0..6f1d594fa 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -41,8 +41,8 @@ flags.DEFINE_string('bridge_dev', 'eth0', 'network device for bridges') flags.DEFINE_string('routing_source_ip', utils.get_my_ip(), 'Public IP of network host') -flags.DEFINE_boo('use_nova_chains', False, - 'use the nova_ routing chains instead of default') +flags.DEFINE_bool('use_nova_chains', False, + 'use the nova_ routing chains instead of default') DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)] -- cgit From 533f72379931aa7bf67a0e7d1d7664ca151afda0 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 28 Sep 2010 17:24:25 -0700 Subject: fix flag defaults --- nova/flags.py | 2 +- nova/network/linux_net.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index 5ed0f92ee..92f6766cf 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -190,7 +190,7 @@ DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('cc_host', '127.0.0.1', 'ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') -DEFINE_string('ec2_url', 'http://%s:%s/services/Cloud' % (FLAGS.cc_host, FLAGS.cc_port), +DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud' 'Url to ec2 api server') DEFINE_string('default_image', 'ami-11111', diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 6f1d594fa..fa77c5ba8 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -39,7 +39,7 @@ flags.DEFINE_string('public_interface', 'vlan1', 'Interface for public IP addresses') flags.DEFINE_string('bridge_dev', 'eth0', 'network device for bridges') -flags.DEFINE_string('routing_source_ip', utils.get_my_ip(), +flags.DEFINE_string('routing_source_ip', '127.0.0.1', 'Public IP of network host') flags.DEFINE_bool('use_nova_chains', False, 'use the nova_ routing chains instead of default') -- cgit From d1c454ba4331794931e94cc2864f4e1a6ef5bf22 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 28 Sep 2010 17:41:57 -0700 Subject: improved commenting --- tools/setup_iptables.sh | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tools/setup_iptables.sh b/tools/setup_iptables.sh index b6b8414e3..673353eb4 100755 --- a/tools/setup_iptables.sh +++ b/tools/setup_iptables.sh @@ -17,6 +17,13 @@ # License for the specific language governing permissions and limitations # under the License. +# NOTE(vish): This script sets up some reasonable defaults for iptables and +# creates nova-specific chains. If you use this script you should +# run nova-network and nova-compute with --use_nova_chains=True + +# NOTE(vish): If you run nova-api on a different port, make sure to change +# the port here +API_PORT=${API_PORT:-"8773"} if [ -n "$1" ]; then CMD=$1 else @@ -26,8 +33,9 @@ fi if [ -n "$2" ]; then IP=$2 else - # NOTE(vish): this will just get the first ip in the list, so if you - # have more than one eth device set up, this will fail + # NOTE(vish): This will just get the first ip in the list, so if you + # have more than one eth device set up, this will fail, and + # you should explicitly pass in the ip of the instance IP=`ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'` fi @@ -39,6 +47,8 @@ fi if [ -n "$4" ]; then + # NOTE(vish): Management IP is the ip over which to allow ssh traffic. It + # will also allow traffic to nova-api MGMT_IP=$4 else MGMT_IP="$IP" @@ -78,7 +88,9 @@ if [ "$CMD" == "base" ] || [ "$CMD" == "all" ]; then iptables -N nova_forward iptables -A FORWARD -j nova_forward - # iptables -P OUTPUT DROP # too restrictive for the moment + # NOTE(vish): DROP on output is too restrictive for now. We need to add + # in a bunch of more specific output rules to use it. + # iptables -P OUTPUT DROP iptables -A OUTPUT -m state --state INVALID -j DROP iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -N nova_output @@ -99,8 +111,9 @@ if [ "$CMD" == "ganglia" ] || [ "$CMD" == "all" ]; then iptables -A nova_input -m udp -p udp -d $IP --dport 8649 -j ACCEPT fi -if [ "$CMD" == "dashboard" ] || [ "$CMD" == "all" ]; then - # dashboard +if [ "$CMD" == "web" ] || [ "$CMD" == "all" ]; then + # NOTE(vish): This opens up ports for web access, allowing web-based + # dashboards to work. iptables -A nova_input -m tcp -p tcp -d $IP --dport 80 -j ACCEPT iptables -A nova_input -m tcp -p tcp -d $IP --dport 443 -j ACCEPT fi @@ -110,9 +123,9 @@ if [ "$CMD" == "objectstore" ] || [ "$CMD" == "all" ]; then fi if [ "$CMD" == "api" ] || [ "$CMD" == "all" ]; then - iptables -A nova_input -m tcp -p tcp -d $IP --dport 8773 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $IP --dport $API_PORT -j ACCEPT if [ "$IP" != "$MGMT_IP" ]; then - iptables -A nova_input -m tcp -p tcp -d $MGMT_IP --dport 8773 -j ACCEPT + iptables -A nova_input -m tcp -p tcp -d $MGMT_IP --dport $API_PORT -j ACCEPT fi fi -- cgit From bc88c73a4e986289be7835b95ec97ffb7a50f7d7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 28 Sep 2010 17:53:27 -0700 Subject: missed a comma --- nova/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/flags.py b/nova/flags.py index 92f6766cf..c32cdd7a4 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -190,7 +190,7 @@ DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('cc_host', '127.0.0.1', 'ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') -DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud' +DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', 'Url to ec2 api server') DEFINE_string('default_image', 'ami-11111', -- 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