From fa1b57724cf833276d1985759666cfa8138ba256 Mon Sep 17 00:00:00 2001 From: andy Date: Wed, 8 Sep 2010 19:57:29 +0200 Subject: [PATCH 01/33] 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/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 ++-- 6 files changed, 14 insertions(+), 26 deletions(-) diff --git a/nova/rpc.py b/nova/rpc.py index 84a9b559..d83dd7a7 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/tests/access_unittest.py b/nova/tests/access_unittest.py index fa0a090a..4cdf4209 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 0b404bfd..b219d0e3 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 900ff5a9..0add5393 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 dece4b5d..b5970d40 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 e12a28fb..e1196798 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""" From 33e86e2f73265f2ad22ea587f8e9e92f93b3c841 Mon Sep 17 00:00:00 2001 From: andy Date: Wed, 8 Sep 2010 20:06:27 +0200 Subject: [PATCH 02/33] 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 d83dd7a7..7a89ca8d 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) From 6a77fdbfac75dcde3ebbc8fa43d4094b94063804 Mon Sep 17 00:00:00 2001 From: andy Date: Wed, 8 Sep 2010 22:43:54 +0200 Subject: [PATCH 03/33] 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/tests/cloud_unittest.py | 2 +- nova/tests/rpc_unittest.py | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/nova/rpc.py b/nova/rpc.py index 7a89ca8d..210d3ccc 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/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 0add5393..893717fe 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 e1196798..1bb2405c 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""" From 3dfa83800e2a1115e06c859d6a725a03f32d99fe Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 20:05:32 -0700 Subject: [PATCH 04/33] 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 325245ac..1c6dc03a 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) From d37c77a67b07c759db88fd0f2dcdd3f90fcaa1e7 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Mon, 13 Sep 2010 23:56:32 -0700 Subject: [PATCH 05/33] 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 6e526676..26f75faf 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.""" From 719a00af953dba21cbf52c37b1fbe390416da42d Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 15 Sep 2010 17:40:12 -0700 Subject: [PATCH 06/33] Added iptables host initial configuration --- bin/nova-manage | 1 + nova/endpoint/api.py | 3 +-- nova/flags.py | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 325245ac..909435ed 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 40be00bb..6de3698e 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 7b0c95a3..55b452fc 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') + From 605e0ff41e5134413e2b28cde0ea96217674f3d8 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 16 Sep 2010 11:44:51 -0700 Subject: [PATCH 07/33] Whitespace fixes --- bin/nova-manage | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 909435ed..325245ac 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -384,4 +384,3 @@ def main(): if __name__ == '__main__': main() - From 1aad374d955d63bb7c4fb69024cd1e2f58d02841 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Mon, 20 Sep 2010 15:35:44 -0700 Subject: [PATCH 08/33] Whitespace fixes --- nova/endpoint/api.py | 1 - nova/flags.py | 1 - 2 files changed, 2 deletions(-) diff --git a/nova/endpoint/api.py b/nova/endpoint/api.py index 6de3698e..56481b2c 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 55b452fc..ce30d503 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') - From 0ef310d0710a6ea519332d60ff531fea787bd6e2 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Tue, 21 Sep 2010 00:03:53 -0700 Subject: [PATCH 09/33] Renamed cc_ip flag to cc_host --- nova/flags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index ce30d503..9d27d336 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', From ae049a6cc7a198b5d7b3b80b3b29a2f2766632dd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Sep 2010 11:12:04 -0700 Subject: [PATCH 10/33] 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 26f75faf..d2381834 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 From 4733e7d9301eb406d8bcbcf38b5f88ff7bfd6375 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Sep 2010 14:36:06 -0700 Subject: [PATCH 11/33] 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 4ae2f20d..c5c7322b 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 e12a28fb..f4d7b4b2 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 From 9f39b8ce380b6e240df5777854457c1027fe5d34 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 21 Sep 2010 19:24:19 -0400 Subject: [PATCH 12/33] User updatable name & description for images. --- nova/endpoint/cloud.py | 12 ++++++++++++ nova/endpoint/images.py | 6 ++++++ nova/tests/cloud_unittest.py | 26 +++++++++++++++++++++++++- nova/tests/objectstore_unittest.py | 6 ++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index e29d09a8..b1678ecc 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 4579cd81..a63c059b 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/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index c36d5a34..2e97180b 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 dece4b5d..82c6e2c4 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""" From 66e80a686d98fe80d12a187e344fc14467c3d562 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 22 Sep 2010 16:42:35 -0400 Subject: [PATCH 13/33] Add user display fields to instances & volumes. --- nova/endpoint/cloud.py | 32 +++++++++++++++++++++++++++ nova/tests/cloud_unittest.py | 43 +++++++++++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index b1678ecc..50fdee60 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 2e97180b..18ad19ed 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']) From 1a61de01f08cba95074e5225032fecd27d828f51 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 23 Sep 2010 11:23:32 -0700 Subject: [PATCH 14/33] renamed ipchains to iptables --- setup_iptables.sh | 94 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 setup_iptables.sh diff --git a/setup_iptables.sh b/setup_iptables.sh new file mode 100644 index 00000000..b1ab1c6f --- /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 + + From 1114f7f7c745c5f269b757767a231617d6b49a94 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 23 Sep 2010 11:58:33 -0700 Subject: [PATCH 15/33] put setup_iptables in the right dir --- setup_iptables.sh | 94 ----------------------------------------------- 1 file changed, 94 deletions(-) delete mode 100644 setup_iptables.sh diff --git a/setup_iptables.sh b/setup_iptables.sh deleted file mode 100644 index b1ab1c6f..00000000 --- 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 - - From 3607bd6f127687d641a8398b9cf533689c18144c Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 23 Sep 2010 18:00:27 -0700 Subject: [PATCH 16/33] 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 0ca32b1e..77a09e65 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. From 3616999e5223d4cc95c2839de288e1c180571fea Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Fri, 24 Sep 2010 17:42:55 -0700 Subject: [PATCH 17/33] 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 77a09e65..8db5d915 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 """ From f8db7d72f2b36c0d520a178bc386748a5d9cea4f Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 24 Sep 2010 23:32:00 -0400 Subject: [PATCH 18/33] 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 | 364 ++++++++++++++++++++++++------------ 3 files changed, 268 insertions(+), 118 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 021851eb..640ea169 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 55fbf42a..0bc12c80 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 3235dea3..7065105c 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_create_and_find_user(self): + with user_generator(self.manager): + self.assert_(self.manager.get_user('test1')) - def test_002_can_get_user(self): - user = 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_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_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_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_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_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_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_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_202_user1_is_project_member(self): - self.assertTrue(self.manager.get_user('test1').is_project_member('testproj')) + 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_203_user2_is_not_project_member(self): - self.assertFalse(self.manager.get_user('test2').is_project_member('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_204_user1_is_project_manager(self): - self.assertTrue(self.manager.get_user('test1').is_project_manager('testproj')) + 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_205_user2_is_not_project_manager(self): - self.assertFalse(self.manager.get_user('test2').is_project_manager('testproj')) + 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_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_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_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_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_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_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_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) + 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())) + # 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())) - 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_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_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_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_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_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_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_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_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_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_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_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_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_999_can_delete_users(self): + 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__": From 3c552cd6e8f5cc5a454df6539465abd7e5ccdc70 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sat, 25 Sep 2010 00:35:59 -0400 Subject: [PATCH 19/33] 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 7065105c..c3efc577 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): From a50caa240a8070b9b3433aca82273ede31d6ba34 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Sun, 26 Sep 2010 14:45:27 -0700 Subject: [PATCH 20/33] 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 8db5d915..fc9fcfde 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(':') From cf6ce49fc3cf75f91a1f53ae1bda486b1cf5270a Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 28 Sep 2010 14:09:53 -0400 Subject: [PATCH 22/33] Instance & Image renaming fixes. --- nova/endpoint/cloud.py | 24 ++++++++++-------------- nova/endpoint/images.py | 6 ++++-- nova/tests/cloud_unittest.py | 2 +- nova/tests/objectstore_unittest.py | 6 +++--- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 2c174bb8..9c96ced9 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 a63c059b..cb54cdda 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/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index cea2a67c..f7340f12 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 82c6e2c4..4216cf19 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): From 616b550ece88894cfba6fb6fa88cf91e6b835c11 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 28 Sep 2010 15:54:05 -0400 Subject: [PATCH 23/33] 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 baa1cb4d..da18e7d1 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.""" From 471b3c4ce95cca2ef8b5e21fbccbd9f4567906fd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 28 Sep 2010 14:59:32 -0700 Subject: [PATCH 24/33] 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 8db4aeb7..cce4a556 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({}, {}) From 6f9e833a542096f9a56be4233ecdcf17c71a90c5 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 28 Sep 2010 16:34:21 -0700 Subject: [PATCH 25/33] 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 f4d7b4b2..e12a28fb 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 From 26c4855883c61512c2daa9a6373c9da9359a0f88 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 28 Sep 2010 17:24:25 -0700 Subject: [PATCH 26/33] fix flag defaults --- nova/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/flags.py b/nova/flags.py index 5ed0f92e..92f6766c 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', From 0235513fcf482f82e951e54718769f171e9fd021 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 28 Sep 2010 17:53:27 -0700 Subject: [PATCH 27/33] 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 92f6766c..c32cdd7a 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', From 8f101f42aaa9128e45061235b4693ab8fda3685a Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Tue, 28 Sep 2010 18:38:19 -0700 Subject: [PATCH 28/33] Removed str_id from FixedIp references --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index baa1cb4d..d0fde6f1 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) From cb597776c26a29e514d08537ebed3c765c71e247 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Tue, 28 Sep 2010 18:43:36 -0700 Subject: [PATCH 29/33] Fixed tests --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index d0fde6f1..a5087bfe 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) From da2d64ee7962fb31f39653453a857f8eefff0e0b Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 29 Sep 2010 14:12:02 -0400 Subject: [PATCH 30/33] Remove debuggish print statement. --- bin/nova-manage | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 7a9a4c3d..bf3c6761 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -277,7 +277,6 @@ class UserCommands(object): 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):