From ba73d32d694a6fbbf180f39bfd5dc38ad33677a4 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 25 Feb 2011 13:01:32 -0800 Subject: [PATCH 001/129] add a caching layer to the has_role call to increase performance --- nova/auth/manager.py | 58 ++++++++++++++++++++++++++++++++------------ nova/flags.py | 2 ++ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 450ab803..90673479 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -214,6 +214,13 @@ class AuthManager(object): if driver or not getattr(self, 'driver', None): self.driver = utils.import_class(driver or FLAGS.auth_driver) + if FLAGS.memcached_servers: + import memcache + else: + from nova import fakememcache as memcache + self.mc = memcache.Client(FLAGS.memcached_servers, + debug=0) + def authenticate(self, access, signature, params, verb='GET', server_string='127.0.0.1:8773', path='/', check_type='ec2', headers=None): @@ -351,6 +358,25 @@ class AuthManager(object): if self.has_role(user, role): return True + def _build_mc_key(self, user, role, project=None): + return "rolecache-%s-%s-%s" % (User.safe_id(user), role, + (Project.safe_id(project) if project else 'None')) + + def _clear_mc_key(self, user, role, project=None): + # (anthony) it would be better to delete the key + self.mc.set(self._build_mc_key(user, role, project), None) + + def _has_role(self, user, role, project=None): + with self.driver() as drv: + mc_key = self._build_mc_key(user, role, project) + rslt = self.mc.get(mc_key) + if rslt == None: + rslt = drv.has_role(user, role, project) + self.mc.set(mc_key, rslt) + return rslt + else: + return rslt + def has_role(self, user, role, project=None): """Checks existence of role for user @@ -374,24 +400,24 @@ class AuthManager(object): @rtype: bool @return: True if the user has the role. """ - with self.driver() as drv: - if role == 'projectmanager': - if not project: - raise exception.Error(_("Must specify project")) - return self.is_project_manager(user, project) + if role == 'projectmanager': + if not project: + raise exception.Error(_("Must specify project")) + return self.is_project_manager(user, project) - global_role = drv.has_role(User.safe_id(user), - role, - None) - if not global_role: - return global_role + global_role = self._has_role(User.safe_id(user), + role, + None) - if not project or role in FLAGS.global_roles: - return global_role + if not global_role: + return global_role - return drv.has_role(User.safe_id(user), - role, - Project.safe_id(project)) + if not project or role in FLAGS.global_roles: + return global_role + + return self._has_role(User.safe_id(user), + role, + Project.safe_id(project)) def add_role(self, user, role, project=None): """Adds role for user @@ -423,6 +449,7 @@ class AuthManager(object): LOG.audit(_("Adding sitewide role %(role)s to user %(uid)s") % locals()) with self.driver() as drv: + self._clear_mc_key(uid, role, pid) drv.add_role(uid, role, pid) def remove_role(self, user, role, project=None): @@ -451,6 +478,7 @@ class AuthManager(object): LOG.audit(_("Removing sitewide role %(role)s" " from user %(uid)s") % locals()) with self.driver() as drv: + self._clear_mc_key(uid, role, pid) drv.remove_role(uid, role, pid) @staticmethod diff --git a/nova/flags.py b/nova/flags.py index 8cf199b2..f885de29 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -354,3 +354,5 @@ DEFINE_string('host', socket.gethostname(), DEFINE_string('node_availability_zone', 'nova', 'availability zone of this node') +DEFINE_list('memcached_servers', None, + 'Memcached servers or None for in process cache.') From f9ef50657527936e56915a6e972b424c6f11746e Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 25 Feb 2011 16:41:48 -0800 Subject: [PATCH 002/129] only create auth connection if cache misses --- nova/auth/manager.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 90673479..511bc3a6 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -367,15 +367,15 @@ class AuthManager(object): self.mc.set(self._build_mc_key(user, role, project), None) def _has_role(self, user, role, project=None): - with self.driver() as drv: - mc_key = self._build_mc_key(user, role, project) - rslt = self.mc.get(mc_key) - if rslt == None: + mc_key = self._build_mc_key(user, role, project) + rslt = self.mc.get(mc_key) + if rslt == None: + with self.driver() as drv: rslt = drv.has_role(user, role, project) self.mc.set(mc_key, rslt) return rslt - else: - return rslt + else: + return rslt def has_role(self, user, role, project=None): """Checks existence of role for user From 6ac8a9158f9e41a11921227e4403a5a63f266434 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 25 Feb 2011 17:18:41 -0800 Subject: [PATCH 003/129] force memcache key to be str --- nova/auth/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 511bc3a6..84c8a6cb 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -359,8 +359,8 @@ class AuthManager(object): return True def _build_mc_key(self, user, role, project=None): - return "rolecache-%s-%s-%s" % (User.safe_id(user), role, - (Project.safe_id(project) if project else 'None')) + return str("rolecache-%s-%s-%s" % (User.safe_id(user), role, + (Project.safe_id(project) if project else 'None'))) def _clear_mc_key(self, user, role, project=None): # (anthony) it would be better to delete the key From bfff03f74c2b4195986bf0c0d30250b97e11dbad Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 18 Mar 2011 11:49:11 -0700 Subject: [PATCH 004/129] created api endpoint to allow uploading of public key --- nova/tests/test_cloud.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index cf8ee7ef..03b1ad2f 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -279,6 +279,22 @@ class CloudTestCase(test.TestCase): self.assertTrue(filter(lambda k: k['keyName'] == 'test1', keys)) self.assertTrue(filter(lambda k: k['keyName'] == 'test2', keys)) + def test_import_public_key(self): + result = self.cloud.import_public_key(self.context, + 'testimportkey', 'mytestpubkey', 'mytestfprint') + self.assertTrue(result) + keydata = db.key_pair_get(self.context, + self.context.user.id, + 'testimportkey') + print "PUBLIC_KEY:" + file = open('/tmp/blah', 'w') + file.write(keydata['public_key']) + file.close() + print keydata['public_key'] + self.assertEqual('mytestpubkey', keydata['public_key']) + self.assertEqual('mytestfprint', keydata['fingerprint']) + self.assertTrue(1) + def test_delete_key_pair(self): self._create_key('test') self.cloud.delete_key_pair(self.context, 'test') From 4c8178a75a74e5c327a06824e1c7792f2e9ff0e1 Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 18 Mar 2011 12:17:40 -0700 Subject: [PATCH 005/129] cleaned up tests stubs that were accidentally checked in --- nova/tests/test_cloud.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 03b1ad2f..3a266c99 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -281,19 +281,15 @@ class CloudTestCase(test.TestCase): def test_import_public_key(self): result = self.cloud.import_public_key(self.context, - 'testimportkey', 'mytestpubkey', 'mytestfprint') + 'testimportkey', + 'mytestpubkey', + 'mytestfprint') self.assertTrue(result) keydata = db.key_pair_get(self.context, self.context.user.id, 'testimportkey') - print "PUBLIC_KEY:" - file = open('/tmp/blah', 'w') - file.write(keydata['public_key']) - file.close() - print keydata['public_key'] self.assertEqual('mytestpubkey', keydata['public_key']) self.assertEqual('mytestfprint', keydata['fingerprint']) - self.assertTrue(1) def test_delete_key_pair(self): self._create_key('test') From 0f22d5871d519a649455d3de642ad719b443f978 Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 21 Mar 2011 14:35:19 -0700 Subject: [PATCH 006/129] if fingerprint data not provided, added logic to calculate it using the pub key. --- nova/tests/public_key/dummy.fingerprint | 1 + nova/tests/public_key/dummy.pub | 1 + nova/tests/test_cloud.py | 32 +++++++++++++++++++------ 3 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 nova/tests/public_key/dummy.fingerprint create mode 100644 nova/tests/public_key/dummy.pub diff --git a/nova/tests/public_key/dummy.fingerprint b/nova/tests/public_key/dummy.fingerprint new file mode 100644 index 00000000..715bca27 --- /dev/null +++ b/nova/tests/public_key/dummy.fingerprint @@ -0,0 +1 @@ +1c:87:d1:d9:32:fd:62:3c:78:2b:c0:ad:c0:15:88:df diff --git a/nova/tests/public_key/dummy.pub b/nova/tests/public_key/dummy.pub new file mode 100644 index 00000000..d4cf2bc0 --- /dev/null +++ b/nova/tests/public_key/dummy.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAMGJlY9XEIm2X234pdO5yFWMp2JuOQx8U0E815IVXhmKxYCBK9ZakgZOIQmPbXoGYyV+mziDPp6HJ0wKYLQxkwLEFr51fAZjWQvRss0SinURRuLkockDfGFtD4pYJthekr/rlqMKlBSDUSpGq8jUWW60UJ18FGooFpxR7ESqQRx/AAAAFQC96LRglaUeeP+E8U/yblEJocuiWwAAAIA3XiMR8Skiz/0aBm5K50SeQznQuMJTyzt9S9uaz5QZWiFu69hOyGSFGw8fqgxEkXFJIuHobQQpGYQubLW0NdaYRqyE/Vud3JUJUb8Texld6dz8vGemyB5d1YvtSeHIo8/BGv2msOqR3u5AZTaGCBD9DhpSGOKHEdNjTtvpPd8S8gAAAIBociGZ5jf09iHLVENhyXujJbxfGRPsyNTyARJfCOGl0oFV6hEzcQyw8U/ePwjgvjc2UizMWLl8tsb2FXKHRdc2v+ND3Us+XqKQ33X3ADP4FZ/+Oj213gMyhCmvFTP0u5FmHog9My4CB7YcIWRuUR42WlhQ2IfPvKwUoTk3R+T6Og== www-data@mk diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 3a266c99..c49a39ed 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -280,16 +280,34 @@ class CloudTestCase(test.TestCase): self.assertTrue(filter(lambda k: k['keyName'] == 'test2', keys)) def test_import_public_key(self): - result = self.cloud.import_public_key(self.context, - 'testimportkey', - 'mytestpubkey', - 'mytestfprint') - self.assertTrue(result) + # test when user provides all values + result1 = self.cloud.import_public_key(self.context, + 'testimportkey1', + 'mytestpubkey', + 'mytestfprint') + self.assertTrue(result1) keydata = db.key_pair_get(self.context, - self.context.user.id, - 'testimportkey') + self.context.user.id, + 'testimportkey1') self.assertEqual('mytestpubkey', keydata['public_key']) self.assertEqual('mytestfprint', keydata['fingerprint']) + # test when user omits fingerprint + pubkey_path = os.path.join(os.path.dirname(__file__), 'public_key') + f = open(pubkey_path + '/dummy.pub', 'r') + dummypub = f.readline().rstrip() + f.close + f = open(pubkey_path + '/dummy.fingerprint', 'r') + dummyfprint = f.readline().rstrip() + f.close + result2 = self.cloud.import_public_key(self.context, + 'testimportkey2', + dummypub) + self.assertTrue(result2) + keydata = db.key_pair_get(self.context, + self.context.user.id, + 'testimportkey2') + self.assertEqual(dummypub, keydata['public_key']) + self.assertEqual(dummyfprint, keydata['fingerprint']) def test_delete_key_pair(self): self._create_key('test') From b3b03a75e02f015062de2ff9ad0c7f2ad42e5b6c Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 23 Mar 2011 11:16:22 -0700 Subject: [PATCH 007/129] added myself to authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 7993955e..c1e16489 100644 --- a/Authors +++ b/Authors @@ -28,6 +28,7 @@ Jesse Andrews Joe Heck Joel Moore John Dewey +John Tran Jonathan Bryce Jordan Rinke Josh Durgin From 1e29f2b16ac56191a145c4cd96d2f55875c485e6 Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 25 Mar 2011 13:17:51 -0700 Subject: [PATCH 008/129] added a simple test for describe_images with mock for detail funciton --- Authors | 1 + nova/tests/test_cloud.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Authors b/Authors index 1679d2de..39a85195 100644 --- a/Authors +++ b/Authors @@ -29,6 +29,7 @@ Jesse Andrews Joe Heck Joel Moore John Dewey +John Tran Jonathan Bryce Jordan Rinke Josh Durgin diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index cf8ee7ef..2f0571ca 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -81,7 +81,12 @@ class CloudTestCase(test.TestCase): def fake_show(meh, context, id): return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} + def fake_detail(meh, context): + return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type':'machine'}}] + self.stubs.Set(local.LocalImageService, 'show', fake_show) + self.stubs.Set(local.LocalImageService, 'detail', fake_detail) self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) def tearDown(self): @@ -224,6 +229,11 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp1['id']) db.service_destroy(self.context, comp2['id']) + def test_describe_images(self): + result = self.cloud.describe_images(self.context) + result = result['imagesSet'][0] + self.assertEqual(result['imageId'], 'ami-00000001') + def test_console_output(self): instance_type = FLAGS.default_instance_type max_count = 1 From 9aa2d409932f8e5fd2296255133fc1a274945762 Mon Sep 17 00:00:00 2001 From: Kevin Bringard Date: Mon, 28 Mar 2011 07:33:57 -0600 Subject: [PATCH 009/129] Updated Authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 09759ddc..298ba8e8 100644 --- a/Authors +++ b/Authors @@ -40,6 +40,7 @@ Joshua McKenty Justin Santa Barbara Kei Masumoto Ken Pepple +Kevin Bringard Kevin L. Mitchell Koji Iida Lorin Hochstein From 1222d65bf64531ee4dcbd25963f981395e70718f Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 28 Mar 2011 11:29:23 -0700 Subject: [PATCH 010/129] when image_id provided cannot be found, returns more informative error message. --- nova/tests/test_cloud.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 2f0571ca..8043d467 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -41,6 +41,7 @@ from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils from nova.image import local from nova.objectstore import image +from nova.exception import NotEmpty, NotFound FLAGS = flags.FLAGS @@ -85,8 +86,12 @@ class CloudTestCase(test.TestCase): return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, 'type':'machine'}}] + def fake_delete(meh, context, id): + return None + self.stubs.Set(local.LocalImageService, 'show', fake_show) self.stubs.Set(local.LocalImageService, 'detail', fake_detail) + self.stubs.Set(local.LocalImageService, 'delete', fake_delete) self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) def tearDown(self): @@ -234,6 +239,16 @@ class CloudTestCase(test.TestCase): result = result['imagesSet'][0] self.assertEqual(result['imageId'], 'ami-00000001') + def test_deregister_image(self): + deregister_image = self.cloud.deregister_image + """When provided a valid image, should be successful""" + result1 = deregister_image(self.context, 'ami-00000001') + self.assertEqual(result1['imageId'], 'ami-00000001') + """Invalid image should throw an NotFound exception""" + self.stubs.UnsetAll() + self.assertRaises(NotFound, deregister_image, + self.context, 'ami-bad001') + def test_console_output(self): instance_type = FLAGS.default_instance_type max_count = 1 From f2c3e5b66c272f526929f207ab4589599722ceaa Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 28 Mar 2011 18:16:55 -0700 Subject: [PATCH 011/129] made changes per code review: 1) removed import of image from objectstore 2) changed to comments instaed of triple quotes. --- nova/tests/test_cloud.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 20c85d79..b8a95e45 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -41,8 +41,7 @@ from nova.compute import power_state from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils from nova.image import local -from nova.objectstore import image -from nova.exception import NotEmpty, NotFound +from nova.exception import NotFound FLAGS = flags.FLAGS @@ -75,16 +74,7 @@ class CloudTestCase(test.TestCase): def fake_show(meh, context, id): return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} - def fake_detail(meh, context): - return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, - 'type':'machine'}}] - - def fake_delete(meh, context, id): - return None - self.stubs.Set(local.LocalImageService, 'show', fake_show) - self.stubs.Set(local.LocalImageService, 'detail', fake_detail) - self.stubs.Set(local.LocalImageService, 'delete', fake_delete) self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) def tearDown(self): @@ -228,17 +218,27 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp2['id']) def test_describe_images(self): + def fake_detail(meh, context): + return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type':'machine'}}] + self.stubs.Set(local.LocalImageService, 'detail', fake_detail) result = self.cloud.describe_images(self.context) result = result['imagesSet'][0] self.assertEqual(result['imageId'], 'ami-00000001') def test_deregister_image(self): deregister_image = self.cloud.deregister_image - """When provided a valid image, should be successful""" + def fake_delete(meh, context, id): + return None + self.stubs.Set(local.LocalImageService, 'delete', fake_delete) + # valid image result1 = deregister_image(self.context, 'ami-00000001') self.assertEqual(result1['imageId'], 'ami-00000001') - """Invalid image should throw an NotFound exception""" + # invalid image self.stubs.UnsetAll() + def fake_detail_empty(meh, context): + return [] + self.stubs.Set(local.LocalImageService, 'detail', fake_detail_empty) self.assertRaises(NotFound, deregister_image, self.context, 'ami-bad001') From 5b4be8d7e563914cabff3e137c50113190d16b83 Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 28 Mar 2011 18:19:56 -0700 Subject: [PATCH 012/129] cleaned up var name --- nova/tests/test_cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index b8a95e45..07e52a6b 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -232,8 +232,8 @@ class CloudTestCase(test.TestCase): return None self.stubs.Set(local.LocalImageService, 'delete', fake_delete) # valid image - result1 = deregister_image(self.context, 'ami-00000001') - self.assertEqual(result1['imageId'], 'ami-00000001') + result = deregister_image(self.context, 'ami-00000001') + self.assertEqual(result['imageId'], 'ami-00000001') # invalid image self.stubs.UnsetAll() def fake_detail_empty(meh, context): From 71aaab3876511858e05e64c4757b9d6397a13ff0 Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 29 Mar 2011 13:43:00 -0700 Subject: [PATCH 013/129] added blank lines in between functions & removed the test_describe_images (was meant for a diff bug lp682888) --- nova/tests/test_cloud.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 07e52a6b..582e40e0 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -217,27 +217,22 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp1['id']) db.service_destroy(self.context, comp2['id']) - def test_describe_images(self): - def fake_detail(meh, context): - return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, - 'type':'machine'}}] - self.stubs.Set(local.LocalImageService, 'detail', fake_detail) - result = self.cloud.describe_images(self.context) - result = result['imagesSet'][0] - self.assertEqual(result['imageId'], 'ami-00000001') - def test_deregister_image(self): deregister_image = self.cloud.deregister_image + def fake_delete(meh, context, id): return None + self.stubs.Set(local.LocalImageService, 'delete', fake_delete) # valid image result = deregister_image(self.context, 'ami-00000001') self.assertEqual(result['imageId'], 'ami-00000001') # invalid image self.stubs.UnsetAll() + def fake_detail_empty(meh, context): return [] + self.stubs.Set(local.LocalImageService, 'detail', fake_detail_empty) self.assertRaises(NotFound, deregister_image, self.context, 'ami-bad001') From 56f28d01c6a578a2e58a96e616363235556140a0 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 30 Mar 2011 12:37:56 -0700 Subject: [PATCH 015/129] updated per code review, replaced NotFound with exception.NotFound --- nova/tests/test_cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 582e40e0..145da8ad 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -36,12 +36,12 @@ from nova import rpc from nova import service from nova import test from nova import utils +from nova import exception from nova.auth import manager from nova.compute import power_state from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils from nova.image import local -from nova.exception import NotFound FLAGS = flags.FLAGS @@ -234,7 +234,7 @@ class CloudTestCase(test.TestCase): return [] self.stubs.Set(local.LocalImageService, 'detail', fake_detail_empty) - self.assertRaises(NotFound, deregister_image, + self.assertRaises(exception.NotFound, deregister_image, self.context, 'ami-bad001') def test_console_output(self): From 0c0d6b8a496e5b2dcb37c380460cb039dcf71f68 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 30 Mar 2011 12:44:22 -0700 Subject: [PATCH 016/129] removed trailing whitespace --- nova/tests/test_cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 145da8ad..cde8041f 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -36,7 +36,7 @@ from nova import rpc from nova import service from nova import test from nova import utils -from nova import exception +from nova import exception from nova.auth import manager from nova.compute import power_state from nova.api.ec2 import cloud From 1b46150130e51fc1acfb4020c91cb09cf152be8c Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sun, 3 Apr 2011 03:45:33 +0400 Subject: [PATCH 018/129] split up to_xml to creation xml_info and filling the template --- nova/tests/test_virt.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 958c8e3e..62afcd1f 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -31,9 +31,7 @@ from nova import test from nova import utils from nova.api.ec2 import cloud from nova.auth import manager -from nova.compute import manager as compute_manager from nova.compute import power_state -from nova.db.sqlalchemy import models from nova.virt import libvirt_conn libvirt = None @@ -269,7 +267,7 @@ class LibvirtConnTestCase(test.TestCase): self.assertTrue(len(target) > 0) def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, - rescue=False): + rescue=False, network_info=None): user_context = context.RequestContext(project=self.project, user=self.user) instance_ref = db.instance_create(user_context, instance) @@ -327,19 +325,13 @@ class LibvirtConnTestCase(test.TestCase): check = (lambda t: t.find('./os/initrd'), None) check_list.append(check) + parameter = './devices/interface/filterref/parameter' common_checks = [ (lambda t: t.find('.').tag, 'domain'), - (lambda t: t.find( - './devices/interface/filterref/parameter').get('name'), 'IP'), - (lambda t: t.find( - './devices/interface/filterref/parameter').get( - 'value'), '10.11.12.13'), - (lambda t: t.findall( - './devices/interface/filterref/parameter')[1].get( - 'name'), 'DHCPSERVER'), - (lambda t: t.findall( - './devices/interface/filterref/parameter')[1].get( - 'value'), '10.0.0.1'), + (lambda t: t.find(parameter).get('name'), 'IP'), + (lambda t: t.find(parameter).get('value'), '10.11.12.13'), + (lambda t: t.findall(parameter)[1].get('name'), 'DHCPSERVER'), + (lambda t: t.findall(parameter)[1].get('value'), '10.0.0.1'), (lambda t: t.find('./devices/serial/source').get( 'path').split('/')[1], 'console.log'), (lambda t: t.find('./memory').text, '2097152')] From 50a1fc2a6ac6b18e10967bc0c8930a724ff4e5fe Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sun, 3 Apr 2011 21:18:35 +0400 Subject: [PATCH 019/129] added preparing_xml test --- nova/tests/test_virt.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 62afcd1f..31954409 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -192,6 +192,47 @@ class LibvirtConnTestCase(test.TestCase): return db.service_create(context.get_admin_context(), service_ref) + + def _create_network_info(self, count=1): + fake = 'fake' + fake_ip = '0.0.0.0/0' + network = {'gateway': fake, + 'gateway_v6': fake, + 'bridge': fake, + 'cidr': fake_ip, + 'cidr_v6': fake_ip} + mapping = {'mac': fake, + 'ips': [{'ip': fake_ip}]} + + return [(network, mapping) for x in xrange(0, count)] + + def test_preparing_xml_info(self): + conn = libvirt_conn.LibvirtConnection(True) + instance_ref = db.instance_create(self.context, self.test_instance) + + result = conn._prepare_xml_info(instance_ref, False) + self.assertFalse(result['nics']) + + result = conn._prepare_xml_info(instance_ref, False, + self._create_network_info()) + self.assertTrue(len(result['nics']) == 1) + + result = conn._prepare_xml_info(instance_ref, False, + self._create_network_info(2)) + self.assertTrue(len(result['nics']) == 2) + + def test_get_nic_for_xml(self): + conn = libvirt_conn.LibvirtConnection(True) + network, mapping = self._create_network_info()[0] + FLAGS.use_ipv6 = False + params_1 = conn._get_nic_for_xml(network, mapping)['extra_params'] + FLAGS.use_ipv6 = True + params_2 = conn._get_nic_for_xml(network, mapping)['extra_params'] + self.assertTrue(params_1.find('PROJNETV6') == -1) + self.assertTrue(params_1.find('PROJMASKV6') == -1) + self.assertTrue(params_2.find('PROJNETV6') > -1) + self.assertTrue(params_2.find('PROJMASKV6') > -1) + def test_xml_and_uri_no_ramdisk_no_kernel(self): instance_data = dict(self.test_instance) self._check_xml_and_uri(instance_data, From 432d46f0eb09ae9460c35d986153ca4b85b62d03 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sun, 3 Apr 2011 22:50:38 +0400 Subject: [PATCH 020/129] add multi_nic_test --- nova/tests/test_virt.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 31954409..b6482503 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -192,7 +192,6 @@ class LibvirtConnTestCase(test.TestCase): return db.service_create(context.get_admin_context(), service_ref) - def _create_network_info(self, count=1): fake = 'fake' fake_ip = '0.0.0.0/0' @@ -224,6 +223,7 @@ class LibvirtConnTestCase(test.TestCase): def test_get_nic_for_xml(self): conn = libvirt_conn.LibvirtConnection(True) network, mapping = self._create_network_info()[0] + backup = FLAGS.use_ipv6 FLAGS.use_ipv6 = False params_1 = conn._get_nic_for_xml(network, mapping)['extra_params'] FLAGS.use_ipv6 = True @@ -232,6 +232,7 @@ class LibvirtConnTestCase(test.TestCase): self.assertTrue(params_1.find('PROJMASKV6') == -1) self.assertTrue(params_2.find('PROJNETV6') > -1) self.assertTrue(params_2.find('PROJMASKV6') > -1) + FLAGS.use_ipv6 = backup def test_xml_and_uri_no_ramdisk_no_kernel(self): instance_data = dict(self.test_instance) @@ -268,6 +269,15 @@ class LibvirtConnTestCase(test.TestCase): instance_data = dict(self.test_instance) self._check_xml_and_container(instance_data) + def test_multi_nic(self): + instance_data = dict(self.test_instance) + network_info = self._create_network_info(2) + conn = libvirt_conn.LibvirtConnection(True) + instance_ref = db.instance_create(self.context, instance_data) + xml = conn.to_xml(instance_ref, False, network_info) + tree = xml_to_tree(xml) + self.assertEquals(len(tree.findall("./devices/interface")), 2) + def _check_xml_and_container(self, instance): user_context = context.RequestContext(project=self.project, user=self.user) @@ -308,7 +318,7 @@ class LibvirtConnTestCase(test.TestCase): self.assertTrue(len(target) > 0) def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, - rescue=False, network_info=None): + rescue=False): user_context = context.RequestContext(project=self.project, user=self.user) instance_ref = db.instance_create(user_context, instance) From a476dbba6504bd8302b6d467a4d7e7f9218db5b6 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 4 Apr 2011 18:33:50 +0400 Subject: [PATCH 021/129] improving tests --- nova/tests/test_virt.py | 76 +++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index b6482503..ae813cb8 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -44,6 +44,22 @@ def _concurrency(wait, done, target): done.send() +def _create_network_info(count=1): + fake = 'fake' + fake_ip = '0.0.0.0/0' + fake_ip_2 = '0.0.0.1/0' + fake_ip_3 = '0.0.0.1/0' + network = {'gateway': fake, + 'gateway_v6': fake, + 'bridge': fake, + 'cidr': fake_ip, + 'cidr_v6': fake_ip} + mapping = {'mac': fake, + 'ips': [{'ip': fake_ip}, {'ip': fake_ip}], + 'ip6s': [{'ip': fake_ip}, {'ip': fake_ip_2}, {'ip': fake_ip_3}]} + return [(network, mapping) for x in xrange(0, count)] + + class CacheConcurrencyTestCase(test.TestCase): def setUp(self): super(CacheConcurrencyTestCase, self).setUp() @@ -192,19 +208,6 @@ class LibvirtConnTestCase(test.TestCase): return db.service_create(context.get_admin_context(), service_ref) - def _create_network_info(self, count=1): - fake = 'fake' - fake_ip = '0.0.0.0/0' - network = {'gateway': fake, - 'gateway_v6': fake, - 'bridge': fake, - 'cidr': fake_ip, - 'cidr_v6': fake_ip} - mapping = {'mac': fake, - 'ips': [{'ip': fake_ip}]} - - return [(network, mapping) for x in xrange(0, count)] - def test_preparing_xml_info(self): conn = libvirt_conn.LibvirtConnection(True) instance_ref = db.instance_create(self.context, self.test_instance) @@ -213,16 +216,16 @@ class LibvirtConnTestCase(test.TestCase): self.assertFalse(result['nics']) result = conn._prepare_xml_info(instance_ref, False, - self._create_network_info()) + _create_network_info()) self.assertTrue(len(result['nics']) == 1) result = conn._prepare_xml_info(instance_ref, False, - self._create_network_info(2)) + _create_network_info(2)) self.assertTrue(len(result['nics']) == 2) def test_get_nic_for_xml(self): conn = libvirt_conn.LibvirtConnection(True) - network, mapping = self._create_network_info()[0] + network, mapping = _create_network_info()[0] backup = FLAGS.use_ipv6 FLAGS.use_ipv6 = False params_1 = conn._get_nic_for_xml(network, mapping)['extra_params'] @@ -271,12 +274,19 @@ class LibvirtConnTestCase(test.TestCase): def test_multi_nic(self): instance_data = dict(self.test_instance) - network_info = self._create_network_info(2) + network_info = _create_network_info(2) conn = libvirt_conn.LibvirtConnection(True) instance_ref = db.instance_create(self.context, instance_data) xml = conn.to_xml(instance_ref, False, network_info) tree = xml_to_tree(xml) - self.assertEquals(len(tree.findall("./devices/interface")), 2) + interfaces = tree.findall("./devices/interface") + self.assertEquals(len(interfaces), 2) + parameters = interfaces[0].findall('./filterref/parameter') + self.assertEquals(interfaces[0].get('type'), 'bridge') + self.assertEquals(parameters[0].get('name'), 'IP') + self.assertEquals(parameters[0].get('value'), '0.0.0.0/0') + self.assertEquals(parameters[1].get('name'), 'DHCPSERVER') + self.assertEquals(parameters[1].get('value'), 'fake') def _check_xml_and_container(self, instance): user_context = context.RequestContext(project=self.project, @@ -656,11 +666,14 @@ class IptablesFirewallTestCase(test.TestCase): '# Completed on Tue Jan 18 23:47:56 2011', ] + def _create_instance_ref(self): + return db.instance_create(self.context, + {'user_id': 'fake', + 'project_id': 'fake', + 'mac_address': '56:12:12:12:12:12'}) + def test_static_filters(self): - instance_ref = db.instance_create(self.context, - {'user_id': 'fake', - 'project_id': 'fake', - 'mac_address': '56:12:12:12:12:12'}) + instance_ref = self._create_instance_ref() ip = '10.11.12.13' network_ref = db.project_get_network(self.context, @@ -771,6 +784,25 @@ class IptablesFirewallTestCase(test.TestCase): "TCP port 80/81 acceptance rule wasn't added") db.instance_destroy(admin_ctxt, instance_ref['id']) + def test_filters_for_instance(self): + network_info = _create_network_info() + rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) + self.assertEquals(len(rulesv4), 2) + self.assertEquals(len(rulesv6), 3) + + def multinic_iptables_test(self): + instance_ref = self._create_instance_ref() + network_info = _create_network_info() + ipv4_len = len(self.fw.iptables.ipv4['filter'].rules) + ipv6_len = len(self.fw.iptables.ipv6['filter'].rules) + inst_ipv4, inst_ipv6 = self.fw.instance_rules(instance_ref, + network_info) + self.fw.add_filters_for_instance(instance_ref, network_info) + ipv4 = self.fw.iptables.ipv4['filter'].rules + ipv6 = self.fw.iptables.ipv6['filter'].rules + self.assertEquals(len(ipv4) - len(inst_ipv4) - ipv4_len, 2) + self.assertEquals(len(ipv6) - len(inst_ipv6) - ipv6_len, 3) + class NWFilterTestCase(test.TestCase): def setUp(self): From f99b40761a4a1857f38b68e9e76e9101ca32ac5d Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 4 Apr 2011 22:22:27 +0400 Subject: [PATCH 022/129] add test for NWFilterFirewall --- nova/tests/test_virt.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index ae813cb8..b3d701ef 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -884,6 +884,12 @@ class NWFilterTestCase(test.TestCase): return db.security_group_get_by_name(self.context, 'fake', 'testgroup') + def _create_instance(self): + return db.instance_create(self.context, + {'user_id': 'fake', + 'project_id': 'fake', + 'mac_address': '00:A0:C9:14:C8:29'}) + def test_creates_base_rule_first(self): # These come pre-defined by libvirt self.defined_filters = ['no-mac-spoofing', @@ -912,10 +918,7 @@ class NWFilterTestCase(test.TestCase): self.fake_libvirt_connection.nwfilterDefineXML = _filterDefineXMLMock - instance_ref = db.instance_create(self.context, - {'user_id': 'fake', - 'project_id': 'fake', - 'mac_address': '00:A0:C9:14:C8:29'}) + instance_ref = self._create_instance() inst_id = instance_ref['id'] ip = '10.11.12.13' @@ -955,3 +958,11 @@ class NWFilterTestCase(test.TestCase): _ensure_all_called() self.teardown_security_group() db.instance_destroy(admin_ctxt, instance_ref['id']) + + + def test_create_network_filters(self): + instance_ref = self._create_instance() + network_info = _create_network_info(3) + result = \ + self.fw._create_network_filters(instance_ref, network_info, "fake") + self.assertEquals(len(result), 3) From acf928255517ef81ce3b8d7a6019ace041eacf28 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 4 Apr 2011 23:43:26 +0400 Subject: [PATCH 023/129] splitting test_get_nic_for_xml into two functions --- nova/tests/test_virt.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index b3d701ef..061797b0 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -226,16 +226,18 @@ class LibvirtConnTestCase(test.TestCase): def test_get_nic_for_xml(self): conn = libvirt_conn.LibvirtConnection(True) network, mapping = _create_network_info()[0] - backup = FLAGS.use_ipv6 - FLAGS.use_ipv6 = False - params_1 = conn._get_nic_for_xml(network, mapping)['extra_params'] - FLAGS.use_ipv6 = True - params_2 = conn._get_nic_for_xml(network, mapping)['extra_params'] - self.assertTrue(params_1.find('PROJNETV6') == -1) - self.assertTrue(params_1.find('PROJMASKV6') == -1) - self.assertTrue(params_2.find('PROJNETV6') > -1) - self.assertTrue(params_2.find('PROJMASKV6') > -1) - FLAGS.use_ipv6 = backup + self.flags(use_ipv6=False) + params = conn._get_nic_for_xml(network, mapping)['extra_params'] + self.assertTrue(params.find('PROJNETV6') == -1) + self.assertTrue(params.find('PROJMASKV6') == -1) + + def test_get_nic_for_xml_v6(self): + conn = libvirt_conn.LibvirtConnection(True) + network, mapping = _create_network_info()[0] + self.flags(use_ipv6=True) + params = conn._get_nic_for_xml(network, mapping)['extra_params'] + self.assertTrue(params.find('PROJNETV6') > -1) + self.assertTrue(params.find('PROJMASKV6') > -1) def test_xml_and_uri_no_ramdisk_no_kernel(self): instance_data = dict(self.test_instance) @@ -959,7 +961,6 @@ class NWFilterTestCase(test.TestCase): self.teardown_security_group() db.instance_destroy(admin_ctxt, instance_ref['id']) - def test_create_network_filters(self): instance_ref = self._create_instance() network_info = _create_network_info(3) From ff183cff2a595fc5a3296430505e75c21ef7f1d1 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 12:56:25 -0700 Subject: [PATCH 024/129] fixed comment --- nova/auth/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 12ded120..f2451702 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -372,7 +372,7 @@ class AuthManager(object): (Project.safe_id(project) if project else 'None'))) def _clear_mc_key(self, user, role, project=None): - # (anthony) it would be better to delete the key + # NOTE(anthony): it would be better to delete the key self.mc.set(self._build_mc_key(user, role, project), None) def _has_role(self, user, role, project=None): From 8c82e513817a548d9af0389168d9b76ea0cfd9b5 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 15:58:19 -0700 Subject: [PATCH 025/129] remove -None for user roles --- nova/auth/manager.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index f2451702..3de2ceff 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -368,8 +368,10 @@ class AuthManager(object): return True def _build_mc_key(self, user, role, project=None): - return str("rolecache-%s-%s-%s" % (User.safe_id(user), role, - (Project.safe_id(project) if project else 'None'))) + role_key = str("rolecache-%s-%s" % (User.safe_id(user), role)) + if project: + return "%s-%s" % (role_key, Project.safe_id(project)) + return role_key def _clear_mc_key(self, user, role, project=None): # NOTE(anthony): it would be better to delete the key From 18f3684046256529baf1d6eb9ce100c9ac3f8cac Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Thu, 7 Apr 2011 02:07:19 +0900 Subject: [PATCH 026/129] Enable RightAWS style signing on server_string without port number portion. --- nova/auth/manager.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 48684539..b51cc7f4 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -315,6 +315,15 @@ class AuthManager(object): LOG.debug('expected_signature: %s', expected_signature) LOG.debug('signature: %s', signature) if signature != expected_signature: + secondary = utils.get_secondary_server_string(server_string) + if secondary is not '': + secondary_signature = signer.Signer( + user.secret.encode()).generate(params, verb, + secondary, path) + LOG.debug('secondary_signature: %s', secondary_signature) + if signature == secondary_signature: + return (user, project) + # NOTE(itoumsn): RightAWS success case. LOG.audit(_("Invalid signature for user %s"), user.name) raise exception.NotAuthorized(_('Signature does not match')) return (user, project) From e3845e65efb06d777a89e76a4fea8a616fe908a8 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Thu, 7 Apr 2011 11:42:02 +0900 Subject: [PATCH 027/129] pep8 cleanup. --- nova/auth/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index b51cc7f4..f1d4a1e3 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -315,7 +315,7 @@ class AuthManager(object): LOG.debug('expected_signature: %s', expected_signature) LOG.debug('signature: %s', signature) if signature != expected_signature: - secondary = utils.get_secondary_server_string(server_string) + secondary = utils.get_secondary_server_string(server_string) if secondary is not '': secondary_signature = signer.Signer( user.secret.encode()).generate(params, verb, From 1283424a7ba0ea5c2a32ecd5b13b5fe62bbf329c Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Thu, 7 Apr 2011 23:48:00 +0900 Subject: [PATCH 028/129] Blush up a bit. --- nova/auth/manager.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index f1d4a1e3..c8a3a46a 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -315,15 +315,15 @@ class AuthManager(object): LOG.debug('expected_signature: %s', expected_signature) LOG.debug('signature: %s', signature) if signature != expected_signature: - secondary = utils.get_secondary_server_string(server_string) - if secondary is not '': - secondary_signature = signer.Signer( + host_only = utils.get_host_only_server_string(server_string) + # If the given server_string contains port num, try without it. + if host_only is not '': + host_only_signature = signer.Signer( user.secret.encode()).generate(params, verb, - secondary, path) - LOG.debug('secondary_signature: %s', secondary_signature) - if signature == secondary_signature: + host_only, path) + LOG.debug('host_only_signature: %s', host_only_signature) + if signature == host_only_signature: return (user, project) - # NOTE(itoumsn): RightAWS success case. LOG.audit(_("Invalid signature for user %s"), user.name) raise exception.NotAuthorized(_('Signature does not match')) return (user, project) From 8e030164de8c1582eee961af0fce1bb74d7a9b2f Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Wed, 13 Apr 2011 02:11:36 +0900 Subject: [PATCH 029/129] Blushed up a little bit. --- nova/auth/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index c8a3a46a..01aa87e3 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -317,7 +317,7 @@ class AuthManager(object): if signature != expected_signature: host_only = utils.get_host_only_server_string(server_string) # If the given server_string contains port num, try without it. - if host_only is not '': + if host_only != '': host_only_signature = signer.Signer( user.secret.encode()).generate(params, verb, host_only, path) From d924c7392021c7d3bff038b8b0163fc8e4175fd2 Mon Sep 17 00:00:00 2001 From: Renuka Apte Date: Tue, 12 Apr 2011 15:20:30 -0700 Subject: [PATCH 030/129] Minor fixes --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index eccf38a4..b6da7a43 100644 --- a/Authors +++ b/Authors @@ -56,6 +56,7 @@ Nachi Ueno Naveed Massjouni Nirmal Ranganathan Paul Voccio +Renuka Apte Ricardo Carrillo Cruz Rick Clark Rick Harris From 3f8c4d8eef9f1efc577798ce814a22bbc762387a Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Thu, 14 Apr 2011 10:47:37 +0900 Subject: [PATCH 031/129] Updated following to RIck's comments. --- nova/auth/manager.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 01aa87e3..dc37ae06 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -300,9 +300,9 @@ class AuthManager(object): if check_type == 's3': sign = signer.Signer(user.secret.encode()) expected_signature = sign.s3_authorization(headers, verb, path) - LOG.debug('user.secret: %s', user.secret) - LOG.debug('expected_signature: %s', expected_signature) - LOG.debug('signature: %s', signature) + LOG.debug(_('user.secret: %s'), user.secret) + LOG.debug(_('expected_signature: %s'), expected_signature) + LOG.debug(_('signature: %s'), signature) if signature != expected_signature: LOG.audit(_("Invalid signature for user %s"), user.name) raise exception.NotAuthorized(_('Signature does not match')) @@ -311,9 +311,9 @@ class AuthManager(object): # secret isn't unicode expected_signature = signer.Signer(user.secret.encode()).generate( params, verb, server_string, path) - LOG.debug('user.secret: %s', user.secret) - LOG.debug('expected_signature: %s', expected_signature) - LOG.debug('signature: %s', signature) + LOG.debug(_('user.secret: %s'), user.secret) + LOG.debug(_('expected_signature: %s'), expected_signature) + LOG.debug(_('signature: %s'), signature) if signature != expected_signature: host_only = utils.get_host_only_server_string(server_string) # If the given server_string contains port num, try without it. @@ -321,7 +321,8 @@ class AuthManager(object): host_only_signature = signer.Signer( user.secret.encode()).generate(params, verb, host_only, path) - LOG.debug('host_only_signature: %s', host_only_signature) + LOG.debug(_('host_only_signature: %s'), + host_only_signature) if signature == host_only_signature: return (user, project) LOG.audit(_("Invalid signature for user %s"), user.name) From baff6ab70c58ae61b64efc2dcee836f279e70e3a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 15 Apr 2011 14:24:17 -0400 Subject: [PATCH 032/129] correcting tests; pep8 --- nova/tests/test_scheduler.py | 69 +++++++++++------------------------- nova/tests/test_virt.py | 2 +- 2 files changed, 22 insertions(+), 49 deletions(-) diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index ae56a1a1..4a2b15f5 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -698,14 +698,10 @@ class SimpleDriverTestCase(test.TestCase): 'topic': 'volume', 'report_count': 0} s_ref = db.service_create(self.context, dic) - try: - self.scheduler.driver.schedule_live_migration(self.context, - instance_id, - i_ref['host']) - except exception.Invalid, e: - c = (e.message.find('volume node is not alive') >= 0) + self.assertRaises(exception.VolumeServiceUnavailable, + self.scheduler.driver.schedule_live_migration, + self.context, instance_id, i_ref['host']) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) db.volume_destroy(self.context, v_ref['id']) @@ -718,13 +714,10 @@ class SimpleDriverTestCase(test.TestCase): s_ref = self._create_compute_service(created_at=t, updated_at=t, host=i_ref['host']) - try: - self.scheduler.driver._live_migration_src_check(self.context, - i_ref) - except exception.Invalid, e: - c = (e.message.find('is not alive') >= 0) + self.assertRaises(exception.ComputeServiceUnavailable, + self.scheduler.driver._live_migration_src_check, + self.context, i_ref) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) @@ -749,14 +742,10 @@ class SimpleDriverTestCase(test.TestCase): s_ref = self._create_compute_service(created_at=t, updated_at=t, host=i_ref['host']) - try: - self.scheduler.driver._live_migration_dest_check(self.context, - i_ref, - i_ref['host']) - except exception.Invalid, e: - c = (e.message.find('is not alive') >= 0) + self.assertRaises(exception.ComputeServiceUnavailable, + self.scheduler.driver._live_migration_dest_check, + self.context, i_ref, i_ref['host']) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) @@ -766,14 +755,10 @@ class SimpleDriverTestCase(test.TestCase): i_ref = db.instance_get(self.context, instance_id) s_ref = self._create_compute_service(host=i_ref['host']) - try: - self.scheduler.driver._live_migration_dest_check(self.context, - i_ref, - i_ref['host']) - except exception.Invalid, e: - c = (e.message.find('choose other host') >= 0) + self.assertRaises(exception.UnableToMigrateToSelf, + self.scheduler.driver._live_migration_dest_check, + self.context, i_ref, i_ref['host']) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) @@ -837,14 +822,10 @@ class SimpleDriverTestCase(test.TestCase): "args": {'filename': fpath}}) self.mox.ReplayAll() - try: - self.scheduler.driver._live_migration_common_check(self.context, - i_ref, - dest) - except exception.Invalid, e: - c = (e.message.find('does not exist') >= 0) + self.assertRaises(exception.SourceHostUnavailable, + self.scheduler.driver._live_migration_common_check, + self.context, i_ref, dest) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) @@ -865,14 +846,10 @@ class SimpleDriverTestCase(test.TestCase): driver.mounted_on_same_shared_storage(mox.IgnoreArg(), i_ref, dest) self.mox.ReplayAll() - try: - self.scheduler.driver._live_migration_common_check(self.context, - i_ref, - dest) - except exception.Invalid, e: - c = (e.message.find(_('Different hypervisor type')) >= 0) + self.assertRaises(exception.InvalidHypervisorType, + self.scheduler.driver._live_migration_common_check, + self.context, i_ref, dest) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) db.service_destroy(self.context, s_ref2['id']) @@ -895,14 +872,10 @@ class SimpleDriverTestCase(test.TestCase): driver.mounted_on_same_shared_storage(mox.IgnoreArg(), i_ref, dest) self.mox.ReplayAll() - try: - self.scheduler.driver._live_migration_common_check(self.context, - i_ref, - dest) - except exception.Invalid, e: - c = (e.message.find(_('Older hypervisor version')) >= 0) + self.assertRaises(exception.DestinationHypervisorTooOld, + self.scheduler.driver._live_migration_common_check, + self.context, i_ref, dest) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) db.service_destroy(self.context, s_ref2['id']) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index aeaea91c..fe0ea5d6 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -451,7 +451,7 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_conn.LibvirtConnection(False) - self.assertRaises(exception.Invalid, + self.assertRaises(exception.ComputeServiceUnavailable, conn.update_available_resource, self.context, 'dummy') From d7e5ac2be911d3a8e3229a75dc77deb316c506b3 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 18 Apr 2011 22:02:54 -0400 Subject: [PATCH 033/129] Implement get_host_ip_addr in the libvirt compute driver. --- nova/tests/test_virt.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index aeaea91c..d9780148 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -18,6 +18,7 @@ import eventlet import mox import os import re +import socket import sys from xml.etree.ElementTree import fromstring as xml_to_tree @@ -549,6 +550,17 @@ class LibvirtConnTestCase(test.TestCase): db.volume_destroy(self.context, volume_ref['id']) db.instance_destroy(self.context, instance_ref['id']) + def test_get_host_ip_addr(self): + + def getHostname(): + return socket.gethostname() + + self.create_fake_libvirt_mock(getHostname=getHostname) + self.mox.ReplayAll() + conn = libvirt_conn.LibvirtConnection(False) + ip = conn.get_host_ip_addr() + self.assertTrue(ip is not None) + def tearDown(self): self.manager.delete_project(self.project) self.manager.delete_user(self.user) From 098d969299541efa2ed16588d79591fde8fbaf22 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Apr 2011 09:48:07 -0400 Subject: [PATCH 034/129] moving dynamic i18n to static --- nova/tests/test_localization.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/test_localization.py b/nova/tests/test_localization.py index a25809a7..37cc22b1 100644 --- a/nova/tests/test_localization.py +++ b/nova/tests/test_localization.py @@ -59,6 +59,7 @@ class LocalizationTestCase(test.TestCase): pos = 0 while parenCount > 0: char = txt[pos] + print char if char == "(": parenCount += 1 elif char == ")": From 6f4c83b3ee53299c877db020d50577e1fb4fe1e7 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Apr 2011 09:48:44 -0400 Subject: [PATCH 035/129] removing rogue print --- nova/tests/test_localization.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/test_localization.py b/nova/tests/test_localization.py index 37cc22b1..a25809a7 100644 --- a/nova/tests/test_localization.py +++ b/nova/tests/test_localization.py @@ -59,7 +59,6 @@ class LocalizationTestCase(test.TestCase): pos = 0 while parenCount > 0: char = txt[pos] - print char if char == "(": parenCount += 1 elif char == ")": From 917ccf509b3bd12e2818c32acfed7c389b84a7e4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Apr 2011 13:17:21 -0400 Subject: [PATCH 036/129] refactoring usage of exception.Duplicate errors --- nova/auth/dbdriver.py | 5 ++--- nova/auth/ldapdriver.py | 11 ++++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/nova/auth/dbdriver.py b/nova/auth/dbdriver.py index b2c580d8..4f5022b9 100644 --- a/nova/auth/dbdriver.py +++ b/nova/auth/dbdriver.py @@ -81,7 +81,7 @@ class DbDriver(object): user_ref = db.user_create(context.get_admin_context(), values) return self._db_user_to_auth_user(user_ref) except exception.Duplicate, e: - raise exception.Duplicate(_('User %s already exists') % name) + raise exception.UserExists(user=name) def _db_user_to_auth_user(self, user_ref): return {'id': user_ref['id'], @@ -132,8 +132,7 @@ class DbDriver(object): try: project = db.project_create(context.get_admin_context(), values) except exception.Duplicate: - raise exception.Duplicate(_("Project can't be created because " - "project %s already exists") % name) + raise exception.ProjectExists(project=name) for member in members: db.project_add_member(context.get_admin_context(), diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index fcac5551..1feb7762 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -171,7 +171,7 @@ class LdapDriver(object): def create_user(self, name, access_key, secret_key, is_admin): """Create a user""" if self.__user_exists(name): - raise exception.Duplicate(_("LDAP user %s already exists") % name) + raise exception.LDAPUserExists(user=name) if FLAGS.ldap_user_modify_only: if self.__ldap_user_exists(name): # Retrieve user by name @@ -226,8 +226,7 @@ class LdapDriver(object): description=None, member_uids=None): """Create a project""" if self.__project_exists(name): - raise exception.Duplicate(_("Project can't be created because " - "project %s already exists") % name) + raise exception.ProjectExists(project=name) if not self.__user_exists(manager_uid): raise exception.NotFound(_("Project can't be created because " "manager %s doesn't exist") @@ -471,8 +470,7 @@ class LdapDriver(object): description, member_uids=None): """Create a group""" if self.__group_exists(group_dn): - raise exception.Duplicate(_("Group can't be created because " - "group %s already exists") % name) + raise exception.LDAPGroupExists(group=name) members = [] if member_uids is not None: for member_uid in member_uids: @@ -512,8 +510,7 @@ class LdapDriver(object): raise exception.NotFound(_("The group at dn %s doesn't exist") % group_dn) if self.__is_in_group(uid, group_dn): - raise exception.Duplicate(_("User %(uid)s is already a member of " - "the group %(group_dn)s") % locals()) + raise exception.LDAPMembershipExists(uid=uid, group_dn=group_dn) attr = [(self.ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))] self.conn.modify_s(group_dn, attr) From 04000346f19f75cd2e55a335d66b1f6391943275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jason=20K=C3=B6lker?= Date: Tue, 19 Apr 2011 15:52:32 -0500 Subject: [PATCH 037/129] add support for git checking and a default of failing if the history can't be read --- nova/tests/test_misc.py | 51 ++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/nova/tests/test_misc.py b/nova/tests/test_misc.py index 4e17e1ce..ad62b48b 100644 --- a/nova/tests/test_misc.py +++ b/nova/tests/test_misc.py @@ -29,11 +29,12 @@ from nova.utils import parse_mailmap, str_dict_replace class ProjectTestCase(test.TestCase): def test_authors_up_to_date(self): topdir = os.path.normpath(os.path.dirname(__file__) + '/../../') + missing = set() + contributors = set() + mailmap = parse_mailmap(os.path.join(topdir, '.mailmap')) + authors_file = open(os.path.join(topdir, 'Authors'), 'r').read() + if os.path.exists(os.path.join(topdir, '.bzr')): - contributors = set() - - mailmap = parse_mailmap(os.path.join(topdir, '.mailmap')) - import bzrlib.workingtree tree = bzrlib.workingtree.WorkingTree.open(topdir) tree.lock_read() @@ -47,23 +48,37 @@ class ProjectTestCase(test.TestCase): for r in revs: for author in r.get_apparent_authors(): email = author.split(' ')[-1] - contributors.add(str_dict_replace(email, mailmap)) - - authors_file = open(os.path.join(topdir, 'Authors'), - 'r').read() - - missing = set() - for contributor in contributors: - if contributor == 'nova-core': - continue - if not contributor in authors_file: - missing.add(contributor) - - self.assertTrue(len(missing) == 0, - '%r not listed in Authors' % missing) + contributors.add(str_dict_replace(email, + mailmap)) finally: tree.unlock() + elif os.path.exists(os.path.join(topdir, '.git')): + import git + repo = git.Repo(topdir) + for commit in repo.head.commit.iter_parents(): + email = commit.author.email + if email is None: + email = commit.author.name + if 'nova-core' in email: + continue + if email.split(' ')[-1] == '<>': + email = email.split(' ')[-2] + email = '<' + email + '>' + contributors.add(str_dict_replace(email, mailmap)) + + else: + self.assertTrue(False, 'Cannot read commit history') + + for contributor in contributors: + if contributor == 'nova-core': + continue + if not contributor in authors_file: + missing.add(contributor) + + self.assertTrue(len(missing) == 0, + '%r not listed in Authors' % missing) + class LockTestCase(test.TestCase): def test_synchronized_wrapped_function_metadata(self): From a202a23979bf6829ac576072a83c0b60fcaf6312 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 20 Apr 2011 21:34:55 +0400 Subject: [PATCH 038/129] fix after review: style, improving tests, replacing underscore --- nova/tests/test_virt.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 19e4d542..2e6fae6c 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -223,7 +223,7 @@ class LibvirtConnTestCase(test.TestCase): _create_network_info(2)) self.assertTrue(len(result['nics']) == 2) - def test_get_nic_for_xml(self): + def test_get_nic_for_xml_v4(self): conn = libvirt_conn.LibvirtConnection(True) network, mapping = _create_network_info()[0] self.flags(use_ipv6=False) @@ -794,8 +794,11 @@ class IptablesFirewallTestCase(test.TestCase): self.assertEquals(len(rulesv6), 3) def multinic_iptables_test(self): + ipv4_rules_per_network = 2 + ipv6_rules_per_network = 3 + networks_count = 5 instance_ref = self._create_instance_ref() - network_info = _create_network_info() + network_info = _create_network_info(networks_count) ipv4_len = len(self.fw.iptables.ipv4['filter'].rules) ipv6_len = len(self.fw.iptables.ipv6['filter'].rules) inst_ipv4, inst_ipv6 = self.fw.instance_rules(instance_ref, @@ -803,8 +806,12 @@ class IptablesFirewallTestCase(test.TestCase): self.fw.add_filters_for_instance(instance_ref, network_info) ipv4 = self.fw.iptables.ipv4['filter'].rules ipv6 = self.fw.iptables.ipv6['filter'].rules - self.assertEquals(len(ipv4) - len(inst_ipv4) - ipv4_len, 2) - self.assertEquals(len(ipv6) - len(inst_ipv6) - ipv6_len, 3) + ipv4_network_rules = len(ipv4) - len(inst_ipv4) - ipv4_len + ipv6_network_rules = len(ipv6) - len(inst_ipv6) - ipv6_len + self.assertEquals(ipv4_network_rules, + ipv4_rules_per_network * networks_count) + self.assertEquals(ipv6_network_rules, + ipv6_rules_per_network * networks_count) class NWFilterTestCase(test.TestCase): @@ -965,6 +972,7 @@ class NWFilterTestCase(test.TestCase): def test_create_network_filters(self): instance_ref = self._create_instance() network_info = _create_network_info(3) - result = \ - self.fw._create_network_filters(instance_ref, network_info, "fake") + result = self.fw._create_network_filters(instance_ref, + network_info, + "fake") self.assertEquals(len(result), 3) From 8991268596fc24c433b96a218da39dfa01848438 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 20 Apr 2011 13:37:21 -0700 Subject: [PATCH 039/129] fix display of vpn instance id and add output rule so it can be tested from network host --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index b2308bc0..55e275e7 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -151,7 +151,7 @@ class VpnCommands(object): state = 'up' print address, print vpn['host'], - print vpn['ec2_id'], + print ec2utils.id_to_ec2_id(vpn['id']), print vpn['state_description'], print state else: From 8b1a9ecadff1d18f551c167a1f9144e9767cb9ef Mon Sep 17 00:00:00 2001 From: Yoshiaki Tamura Date: Thu, 21 Apr 2011 16:54:37 +0900 Subject: [PATCH 040/129] Add a test checking spawn() works when network_info is set, which currently doesn't. The following patch would fix it. --- nova/tests/test_virt.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index aeaea91c..fdde6ed9 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -549,6 +549,43 @@ class LibvirtConnTestCase(test.TestCase): db.volume_destroy(self.context, volume_ref['id']) db.instance_destroy(self.context, instance_ref['id']) + def test_spawn_with_network_info(self): + # Skip if non-libvirt environment + if not self.lazy_load_library_exists(): + return + + # Preparing mocks + def fake_none(self, instance): + return + + self.create_fake_libvirt_mock() + instance = db.instance_create(self.context, self.test_instance) + + # Start test + self.mox.ReplayAll() + conn = libvirt_conn.LibvirtConnection(False) + conn.firewall_driver.setattr('setup_basic_filtering', fake_none) + conn.firewall_driver.setattr('prepare_instance_filter', fake_none) + + network = db.project_get_network(context.get_admin_context(), + self.project.id) + ip_dict = {'ip': self.test_ip, + 'netmask': network['netmask'], + 'enabled': '1'} + mapping = {'label': network['label'], + 'gateway': network['gateway'], + 'mac': instance['mac_address'], + 'dns': [network['dns']], + 'ips': [ip_dict]} + network_info = [(network, mapping)] + + try: + conn.spawn(instance, network_info) + except Exception, e: + count = (0 <= e.message.find('Unexpected method call')) + + self.assertTrue(count) + def tearDown(self): self.manager.delete_project(self.project) self.manager.delete_user(self.user) From 27662ff3eddc64f69feb69c733fdb2e6d081864b Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Fri, 22 Apr 2011 01:26:59 +0900 Subject: [PATCH 041/129] Utility method reworked, etc. --- nova/auth/authutils.py | 48 +++++++++++++++++++++++++++++++++++++++++ nova/auth/manager.py | 4 +++- nova/tests/test_auth.py | 24 +++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 nova/auth/authutils.py diff --git a/nova/auth/authutils.py b/nova/auth/authutils.py new file mode 100644 index 00000000..429e86ef --- /dev/null +++ b/nova/auth/authutils.py @@ -0,0 +1,48 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 NTT DATA CORPORATION. +# Copyright 2011 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. + +""" +Auth module specific utilities and helper functions. +""" + +import netaddr +import string + + +def get_host_only_server_string(server_str): + """ + Returns host part only of the given server_string if it's a combination + of host part and port. Otherwise, return null string. + """ + + # First of all, exclude pure IPv6 address (w/o port). + if netaddr.valid_ipv6(server_str): + return '' + + # Next, check if this is IPv6 address with port number combination. + if server_str.find("]:") != -1: + [address, sep, port] = server_str.replace('[', '', 1).partition(']:') + return address + + # Third, check if this is a combination of general address and port + if server_str.find(':') == -1: + return '' + + # This must be a combination of host part and port + (address, port) = server_str.split(':') + return address diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 06def220..775b38af 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -35,6 +35,7 @@ from nova import flags from nova import log as logging from nova import utils from nova.auth import signer +from nova.auth import authutils FLAGS = flags.FLAGS @@ -315,7 +316,8 @@ class AuthManager(object): LOG.debug(_('expected_signature: %s'), expected_signature) LOG.debug(_('signature: %s'), signature) if signature != expected_signature: - host_only = utils.get_host_only_server_string(server_string) + host_only = authutils.get_host_only_server_string( + server_string) # If the given server_string contains port num, try without it. if host_only != '': host_only_signature = signer.Signer( diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index f8a1b156..3886e9e6 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -25,6 +25,7 @@ from nova import log as logging from nova import test from nova.auth import manager from nova.api.ec2 import cloud +from nova.auth import authutils FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.auth_unittest') @@ -339,6 +340,29 @@ class AuthManagerDbTestCase(_AuthManagerBaseTestCase): auth_driver = 'nova.auth.dbdriver.DbDriver' +class AuthManagerUtilTestCase(test.TestCase): + def test_get_host_only_server_string(self): + result = authutils.get_host_only_server_string('::1') + self.assertEqual('', result) + result = authutils.get_host_only_server_string('[::1]:8773') + self.assertEqual('::1', result) + result = authutils.get_host_only_server_string('2001:db8::192.168.1.1') + self.assertEqual('', result) + result = authutils.get_host_only_server_string( + '[2001:db8::192.168.1.1]:8773') + self.assertEqual('2001:db8::192.168.1.1', result) + result = authutils.get_host_only_server_string('192.168.1.1') + self.assertEqual('', result) + result = authutils.get_host_only_server_string('192.168.1.2:8773') + self.assertEqual('192.168.1.2', result) + result = authutils.get_host_only_server_string('192.168.1.3') + self.assertEqual('', result) + result = authutils.get_host_only_server_string('www.example.com:8443') + self.assertEqual('www.example.com', result) + result = authutils.get_host_only_server_string('www.example.com') + self.assertEqual('', result) + + if __name__ == "__main__": # TODO: Implement use_fake as an option unittest.main() From 6aa3e4618a2767926013d56087a4ce721a275e97 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Apr 2011 14:12:54 -0400 Subject: [PATCH 042/129] Modified instance status for shutdown power state in OS api --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index ce280749..d3ba23fb 100644 --- a/Authors +++ b/Authors @@ -1,3 +1,4 @@ +Alex Meade Andy Smith Andy Southgate Anne Gentle From 731084a9d4c5be69c1863a603a6a50e8b2953d42 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 21 Apr 2011 15:50:04 -0400 Subject: [PATCH 043/129] Addressing exception.NotFound across the project --- nova/auth/dbdriver.py | 16 ++++-------- nova/auth/ldapdriver.py | 48 +++++++++++------------------------- nova/auth/manager.py | 14 +++++------ nova/tests/test_scheduler.py | 20 ++++++--------- nova/tests/test_volume.py | 2 +- 5 files changed, 35 insertions(+), 65 deletions(-) diff --git a/nova/auth/dbdriver.py b/nova/auth/dbdriver.py index b2c580d8..813cba36 100644 --- a/nova/auth/dbdriver.py +++ b/nova/auth/dbdriver.py @@ -103,9 +103,7 @@ class DbDriver(object): """Create a project""" manager = db.user_get(context.get_admin_context(), manager_uid) if not manager: - raise exception.NotFound(_("Project can't be created because " - "manager %s doesn't exist") - % manager_uid) + raise exception.UserNotFound(user_id=manager_uid) # description is a required attribute if description is None: @@ -119,9 +117,7 @@ class DbDriver(object): for member_uid in member_uids: member = db.user_get(context.get_admin_context(), member_uid) if not member: - raise exception.NotFound(_("Project can't be created " - "because user %s doesn't exist") - % member_uid) + raise exception.UserNotFound(user_id=member_uid) members.add(member) values = {'id': name, @@ -154,9 +150,7 @@ class DbDriver(object): if manager_uid: manager = db.user_get(context.get_admin_context(), manager_uid) if not manager: - raise exception.NotFound(_("Project can't be modified because " - "manager %s doesn't exist") % - manager_uid) + raise exception.UserNotFound(user_id=manager_uid) values['project_manager'] = manager['id'] if description: values['description'] = description @@ -244,8 +238,8 @@ class DbDriver(object): def _validate_user_and_project(self, user_id, project_id): user = db.user_get(context.get_admin_context(), user_id) if not user: - raise exception.NotFound(_('User "%s" not found') % user_id) + raise exception.UserNotFound(user_id=user_id) project = db.project_get(context.get_admin_context(), project_id) if not project: - raise exception.NotFound(_('Project "%s" not found') % project_id) + raise exception.ProjectNotFound(project_id=project_id) return user, project diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index fcac5551..093462b9 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -202,8 +202,7 @@ class LdapDriver(object): self.conn.modify_s(self.__uid_to_dn(name), attr) return self.get_user(name) else: - raise exception.NotFound(_("LDAP object for %s doesn't exist") - % name) + raise exception.LDAPUserNotFound(user_id=name) else: attr = [ ('objectclass', ['person', @@ -229,9 +228,7 @@ class LdapDriver(object): raise exception.Duplicate(_("Project can't be created because " "project %s already exists") % name) if not self.__user_exists(manager_uid): - raise exception.NotFound(_("Project can't be created because " - "manager %s doesn't exist") - % manager_uid) + raise exception.LDAPUserNotFound(user_id=manager_uid) manager_dn = self.__uid_to_dn(manager_uid) # description is a required attribute if description is None: @@ -240,9 +237,7 @@ class LdapDriver(object): if member_uids is not None: for member_uid in member_uids: if not self.__user_exists(member_uid): - raise exception.NotFound(_("Project can't be created " - "because user %s doesn't exist") - % member_uid) + raise exception.LDAPUserNotFound(user_id=member_uid) members.append(self.__uid_to_dn(member_uid)) # always add the manager as a member because members is required if not manager_dn in members: @@ -265,9 +260,7 @@ class LdapDriver(object): attr = [] if manager_uid: if not self.__user_exists(manager_uid): - raise exception.NotFound(_("Project can't be modified because " - "manager %s doesn't exist") - % manager_uid) + raise exception.LDAPUserNotFound(user_id=manager_uid) manager_dn = self.__uid_to_dn(manager_uid) attr.append((self.ldap.MOD_REPLACE, LdapDriver.project_attribute, manager_dn)) @@ -347,7 +340,7 @@ class LdapDriver(object): def delete_user(self, uid): """Delete a user""" if not self.__user_exists(uid): - raise exception.NotFound(_("User %s doesn't exist") % uid) + raise exception.LDAPUserNotFound(user_id=uid) self.__remove_from_all(uid) if FLAGS.ldap_user_modify_only: # Delete attributes @@ -477,9 +470,7 @@ class LdapDriver(object): if member_uids is not None: for member_uid in member_uids: if not self.__user_exists(member_uid): - raise exception.NotFound(_("Group can't be created " - "because user %s doesn't exist") - % member_uid) + raise exception.LDAPUserNotFound(user_id=member_uid) members.append(self.__uid_to_dn(member_uid)) dn = self.__uid_to_dn(uid) if not dn in members: @@ -494,8 +485,7 @@ class LdapDriver(object): def __is_in_group(self, uid, group_dn): """Check if user is in group""" if not self.__user_exists(uid): - raise exception.NotFound(_("User %s can't be searched in group " - "because the user doesn't exist") % uid) + raise exception.LDAPUserNotFound(user_id=uid) if not self.__group_exists(group_dn): return False res = self.__find_object(group_dn, @@ -506,11 +496,9 @@ class LdapDriver(object): def __add_to_group(self, uid, group_dn): """Add user to group""" if not self.__user_exists(uid): - raise exception.NotFound(_("User %s can't be added to the group " - "because the user doesn't exist") % uid) + raise exception.LDAPUserNotFound(user_id=uid) if not self.__group_exists(group_dn): - raise exception.NotFound(_("The group at dn %s doesn't exist") % - group_dn) + raise exception.LDAPGroupNotFound(group_id=group_dn) if self.__is_in_group(uid, group_dn): raise exception.Duplicate(_("User %(uid)s is already a member of " "the group %(group_dn)s") % locals()) @@ -520,15 +508,12 @@ class LdapDriver(object): def __remove_from_group(self, uid, group_dn): """Remove user from group""" if not self.__group_exists(group_dn): - raise exception.NotFound(_("The group at dn %s doesn't exist") - % group_dn) + raise exception.LDAPGroupNotFound(group_id=group_dn) if not self.__user_exists(uid): - raise exception.NotFound(_("User %s can't be removed from the " - "group because the user doesn't exist") - % uid) + raise exception.LDAPUserNotFound(user_id=uid) if not self.__is_in_group(uid, group_dn): - raise exception.NotFound(_("User %s is not a member of the group") - % uid) + raise exception.LDAPGroupMembershipNotFound(user_id=uid, + group_id=group_dn) # NOTE(vish): remove user from group and any sub_groups sub_dns = self.__find_group_dns_with_member(group_dn, uid) for sub_dn in sub_dns: @@ -548,9 +533,7 @@ class LdapDriver(object): def __remove_from_all(self, uid): """Remove user from all roles and projects""" if not self.__user_exists(uid): - raise exception.NotFound(_("User %s can't be removed from all " - "because the user doesn't exist") - % uid) + raise exception.LDAPUserNotFound(user_id=uid) role_dns = self.__find_group_dns_with_member( FLAGS.role_project_subtree, uid) for role_dn in role_dns: @@ -563,8 +546,7 @@ class LdapDriver(object): def __delete_group(self, group_dn): """Delete Group""" if not self.__group_exists(group_dn): - raise exception.NotFound(_("Group at dn %s doesn't exist") - % group_dn) + raise exception.LDAPGroupNotFound(group_id=group_dn) self.conn.delete_s(group_dn) def __delete_roles(self, project_dn): diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 8479c95a..b719a0db 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -270,8 +270,7 @@ class AuthManager(object): LOG.debug('user: %r', user) if user is None: LOG.audit(_("Failed authorization for access key %s"), access_key) - raise exception.NotFound(_('No user found for access key %s') - % access_key) + raise exception.AccessKeyNotFound(access_key=access_key) # NOTE(vish): if we stop using project name as id we need better # logic to find a default project for user @@ -285,8 +284,7 @@ class AuthManager(object): uname = user.name LOG.audit(_("failed authorization: no project named %(pjid)s" " (user=%(uname)s)") % locals()) - raise exception.NotFound(_('No project called %s could be found') - % project_id) + raise exception.ProjectNotFound(project_id=project_id) if not self.is_admin(user) and not self.is_project_member(user, project): uname = user.name @@ -295,8 +293,8 @@ class AuthManager(object): pjid = project.id LOG.audit(_("Failed authorization: user %(uname)s not admin" " and not member of project %(pjname)s") % locals()) - raise exception.NotFound(_('User %(uid)s is not a member of' - ' project %(pjid)s') % locals()) + raise exception.ProjectMembershipNotFound(project_id=pjid, + user_id=uid) if check_type == 's3': sign = signer.Signer(user.secret.encode()) expected_signature = sign.s3_authorization(headers, verb, path) @@ -420,9 +418,9 @@ class AuthManager(object): @param project: Project in which to add local role. """ if role not in FLAGS.allowed_roles: - raise exception.NotFound(_("The %s role can not be found") % role) + raise exception.UserRoleNotFound(role_id=role) if project is not None and role in FLAGS.global_roles: - raise exception.NotFound(_("The %s role is global only") % role) + raise exception.GlobalRoleNotAllowed(role_id=role) uid = User.safe_id(user) pid = Project.safe_id(project) if project: diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 42ea19d6..efd12f93 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -120,12 +120,11 @@ class SchedulerTestCase(test.TestCase): dest = 'dummydest' ctxt = context.get_admin_context() - try: - scheduler.show_host_resources(ctxt, dest) - except exception.NotFound, e: - c1 = (e.message.find(_("does not exist or is not a " - "compute node.")) >= 0) - self.assertTrue(c1) + self.assertRaises(exception.NotFound, scheduler.show_host_resources, + ctxt, dest) + #TODO(bcwaldon): reimplement this functionality + #c1 = (e.message.find(_("does not exist or is not a " + # "compute node.")) >= 0) def _dic_is_equal(self, dic1, dic2, keys=None): """Compares 2 dictionary contents(Helper method)""" @@ -941,7 +940,7 @@ class FakeRerouteCompute(api.reroute_compute): def go_boom(self, context, instance): - raise exception.InstanceNotFound("boom message", instance) + raise exception.InstanceNotFound(instance_id=instance) def found_instance(self, context, instance): @@ -990,11 +989,8 @@ class ZoneRedirectTest(test.TestCase): def test_routing_flags(self): FLAGS.enable_zone_routing = False decorator = FakeRerouteCompute("foo") - try: - result = decorator(go_boom)(None, None, 1) - self.assertFail(_("Should have thrown exception.")) - except exception.InstanceNotFound, e: - self.assertEquals(e.message, 'boom message') + self.assertRaises(exception.InstanceNotFound, decorator(go_boom), + None, None, 1) def test_get_collection_context_and_id(self): decorator = api.reroute_compute("foo") diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py index e9d8289a..236d1243 100644 --- a/nova/tests/test_volume.py +++ b/nova/tests/test_volume.py @@ -142,7 +142,7 @@ class VolumeTestCase(test.TestCase): self.assertEqual(vol['status'], "available") self.volume.delete_volume(self.context, volume_id) - self.assertRaises(exception.Error, + self.assertRaises(exception.VolumeNotFound, db.volume_get, self.context, volume_id) From 5590a640a64d0ceabffb9657984092cc46d957fa Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 21 Apr 2011 23:45:02 -0700 Subject: [PATCH 044/129] Fixes per review --- nova/auth/manager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 3de2ceff..4d4bdbce 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -368,10 +368,10 @@ class AuthManager(object): return True def _build_mc_key(self, user, role, project=None): - role_key = str("rolecache-%s-%s" % (User.safe_id(user), role)) + key_parts = ['rolecache', User.safe_id(user), str(role)] if project: - return "%s-%s" % (role_key, Project.safe_id(project)) - return role_key + key_parts.append(Project.safe_id(project)) + return '-'.join(key_parts) def _clear_mc_key(self, user, role, project=None): # NOTE(anthony): it would be better to delete the key @@ -380,7 +380,7 @@ class AuthManager(object): def _has_role(self, user, role, project=None): mc_key = self._build_mc_key(user, role, project) rslt = self.mc.get(mc_key) - if rslt == None: + if rslt is None: with self.driver() as drv: rslt = drv.has_role(user, role, project) self.mc.set(mc_key, rslt) From 4683480df0a22e5ddb351dfd6a51df0e93395051 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Fri, 22 Apr 2011 21:35:54 +0900 Subject: [PATCH 045/129] Rework completed. Added test cases, changed helper method name, etc. --- nova/auth/authutils.py | 48 ------------------------------- nova/auth/manager.py | 8 ++---- nova/tests/test_auth.py | 64 ++++++++++++++++++++++++----------------- 3 files changed, 40 insertions(+), 80 deletions(-) delete mode 100644 nova/auth/authutils.py diff --git a/nova/auth/authutils.py b/nova/auth/authutils.py deleted file mode 100644 index 429e86ef..00000000 --- a/nova/auth/authutils.py +++ /dev/null @@ -1,48 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 NTT DATA CORPORATION. -# Copyright 2011 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. - -""" -Auth module specific utilities and helper functions. -""" - -import netaddr -import string - - -def get_host_only_server_string(server_str): - """ - Returns host part only of the given server_string if it's a combination - of host part and port. Otherwise, return null string. - """ - - # First of all, exclude pure IPv6 address (w/o port). - if netaddr.valid_ipv6(server_str): - return '' - - # Next, check if this is IPv6 address with port number combination. - if server_str.find("]:") != -1: - [address, sep, port] = server_str.replace('[', '', 1).partition(']:') - return address - - # Third, check if this is a combination of general address and port - if server_str.find(':') == -1: - return '' - - # This must be a combination of host part and port - (address, port) = server_str.split(':') - return address diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 775b38af..d42594c8 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -35,7 +35,6 @@ from nova import flags from nova import log as logging from nova import utils from nova.auth import signer -from nova.auth import authutils FLAGS = flags.FLAGS @@ -316,13 +315,12 @@ class AuthManager(object): LOG.debug(_('expected_signature: %s'), expected_signature) LOG.debug(_('signature: %s'), signature) if signature != expected_signature: - host_only = authutils.get_host_only_server_string( - server_string) + (addr_str, port_str) = utils.parse_server_string(server_string) # If the given server_string contains port num, try without it. - if host_only != '': + if port_str != '': host_only_signature = signer.Signer( user.secret.encode()).generate(params, verb, - host_only, path) + addr_str, path) LOG.debug(_('host_only_signature: %s'), host_only_signature) if signature == host_only_signature: diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index 3886e9e6..f02dd94b 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -25,7 +25,6 @@ from nova import log as logging from nova import test from nova.auth import manager from nova.api.ec2 import cloud -from nova.auth import authutils FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.auth_unittest') @@ -102,9 +101,43 @@ class _AuthManagerBaseTestCase(test.TestCase): self.assertEqual('private-party', u.access) def test_004_signature_is_valid(self): - #self.assertTrue(self.manager.authenticate(**boto.generate_url ...? )) - pass - #raise NotImplementedError + with user_generator(self.manager, name='admin', secret='admin', + access='admin'): + with project_generator(self.manager, name="admin", + manager_user='admin'): + accesskey = 'admin:admin' + expected_result = (self.manager.get_user('admin'), + self.manager.get_project('admin')) + # captured sig and query string using boto 1.9b/euca2ools 1.2 + sig = 'd67Wzd9Bwz8xid9QU+lzWXcF2Y3tRicYABPJgrqfrwM=' + auth_params = {'AWSAccessKeyId': 'admin:admin', + 'Action': 'DescribeAvailabilityZones', + 'SignatureMethod': 'HmacSHA256', + 'SignatureVersion': '2', + 'Timestamp': '2011-04-22T11:29:29', + 'Version': '2009-11-30'} + self.assertTrue(expected_result, self.manager.authenticate( + accesskey, + sig, + auth_params, + 'GET', + '127.0.0.1:8773', + '/services/Cloud/')) + # captured sig and query string using RightAWS 1.10.0 + sig = 'ECYLU6xdFG0ZqRVhQybPJQNJ5W4B9n8fGs6+/fuGD2c=' + auth_params = {'AWSAccessKeyId': 'admin:admin', + 'Action': 'DescribeAvailabilityZones', + 'SignatureMethod': 'HmacSHA256', + 'SignatureVersion': '2', + 'Timestamp': '2011-04-22T11:29:49.000Z', + 'Version': '2008-12-01'} + self.assertTrue(expected_result, self.manager.authenticate( + accesskey, + sig, + auth_params, + 'GET', + '127.0.0.1', + '/services/Cloud')) def test_005_can_get_credentials(self): return @@ -340,29 +373,6 @@ class AuthManagerDbTestCase(_AuthManagerBaseTestCase): auth_driver = 'nova.auth.dbdriver.DbDriver' -class AuthManagerUtilTestCase(test.TestCase): - def test_get_host_only_server_string(self): - result = authutils.get_host_only_server_string('::1') - self.assertEqual('', result) - result = authutils.get_host_only_server_string('[::1]:8773') - self.assertEqual('::1', result) - result = authutils.get_host_only_server_string('2001:db8::192.168.1.1') - self.assertEqual('', result) - result = authutils.get_host_only_server_string( - '[2001:db8::192.168.1.1]:8773') - self.assertEqual('2001:db8::192.168.1.1', result) - result = authutils.get_host_only_server_string('192.168.1.1') - self.assertEqual('', result) - result = authutils.get_host_only_server_string('192.168.1.2:8773') - self.assertEqual('192.168.1.2', result) - result = authutils.get_host_only_server_string('192.168.1.3') - self.assertEqual('', result) - result = authutils.get_host_only_server_string('www.example.com:8443') - self.assertEqual('www.example.com', result) - result = authutils.get_host_only_server_string('www.example.com') - self.assertEqual('', result) - - if __name__ == "__main__": # TODO: Implement use_fake as an option unittest.main() From 79a4cc37643a6458e66af6484a72a70b3699ced9 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 26 Apr 2011 18:55:13 -0400 Subject: [PATCH 046/129] Let nova-mange limit project list by user. --- bin/nova-manage | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index c8230670..820b10e2 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -385,10 +385,10 @@ class ProjectCommands(object): with open(filename, 'w') as f: f.write(rc) - def list(self): + def list(self, username=None): """Lists all projects - arguments: """ - for project in self.manager.get_projects(): + arguments: [username]""" + for project in self.manager.get_projects(username): print project.name def quota(self, project_id, key=None, value=None): From 2ba6ad424923f68b7e6c9132d1a6e611ba840587 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 27 Apr 2011 00:53:07 -0400 Subject: [PATCH 047/129] Added myself to authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index eccf38a4..cef2c761 100644 --- a/Authors +++ b/Authors @@ -1,3 +1,4 @@ +Alex Meade Andy Smith Andy Southgate Anne Gentle From ef381873f6ab13eaff91c40e6a02521338292e5a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Apr 2011 14:03:05 -0700 Subject: [PATCH 048/129] further cleanup of nova/exceptions.py --- nova/auth/manager.py | 6 ++++-- nova/tests/test_instance_types.py | 6 +++--- nova/tests/test_scheduler.py | 10 +++------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index b719a0db..72566717 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -303,7 +303,8 @@ class AuthManager(object): LOG.debug('signature: %s', signature) if signature != expected_signature: LOG.audit(_("Invalid signature for user %s"), user.name) - raise exception.NotAuthorized(_('Signature does not match')) + raise exception.InvalidSignature(signature=signature, + user=user) elif check_type == 'ec2': # NOTE(vish): hmac can't handle unicode, so encode ensures that # secret isn't unicode @@ -314,7 +315,8 @@ class AuthManager(object): LOG.debug('signature: %s', signature) if signature != expected_signature: LOG.audit(_("Invalid signature for user %s"), user.name) - raise exception.NotAuthorized(_('Signature does not match')) + raise exception.InvalidSignature(signature=signature, + user=user) return (user, project) def get_access_key(self, user, project): diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py index dd7d0737..ef271518 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -75,13 +75,13 @@ class InstanceTypeTestCase(test.TestCase): def test_invalid_create_args_should_fail(self): """Ensures that instance type creation fails with invalid args""" self.assertRaises( - exception.InvalidInputException, + exception.InvalidInput, instance_types.create, self.name, 0, 1, 120, self.flavorid) self.assertRaises( - exception.InvalidInputException, + exception.InvalidInput, instance_types.create, self.name, 256, -1, 120, self.flavorid) self.assertRaises( - exception.InvalidInputException, + exception.InvalidInput, instance_types.create, self.name, 256, 1, "aa", self.flavorid) def test_non_existant_inst_type_shouldnt_delete(self): diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index efd12f93..968ef9d6 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -768,14 +768,10 @@ class SimpleDriverTestCase(test.TestCase): s_ref = self._create_compute_service(host='somewhere', memory_mb_used=12) - try: - self.scheduler.driver._live_migration_dest_check(self.context, - i_ref, - 'somewhere') - except exception.NotEmpty, e: - c = (e.message.find('Unable to migrate') >= 0) + self.assertRaises(exception.MigrationError, + self.scheduler.driver._live_migration_dest_check, + self.context, i_ref, 'somewhere') - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) From 732a237fdc365a3dc3e5aaebf385235fd5e006be Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Wed, 27 Apr 2011 20:33:55 -0700 Subject: [PATCH 049/129] added nova version output to usage printout for nova-manage --- bin/nova-manage | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/nova-manage b/bin/nova-manage index c8230670..898255fb 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -82,6 +82,7 @@ from nova import log as logging from nova import quota from nova import rpc from nova import utils +from nova import version from nova.api.ec2 import ec2utils from nova.auth import manager from nova.cloudpipe import pipelib @@ -1091,6 +1092,8 @@ def main(): script_name = argv.pop(0) if len(argv) < 1: + print _("\nOpenStack Nova version: %s (%s)\n") %\ + (version.version_string(), version.version_string_with_vcs()) print script_name + " category action []" print _("Available categories:") for k, _v in CATEGORIES: From f6ac541ad798e2c61acb2fee1d754be1cf3a15fe Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 28 Apr 2011 10:26:43 -0700 Subject: [PATCH 050/129] added version list command to nova-manage --- bin/nova-manage | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 898255fb..ad2960d1 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -759,6 +759,16 @@ class DbCommands(object): print migration.db_version() +class VersionCommands(object): + """Class for managing the database.""" + + def __init__(self): + pass + + def list(self): + print _("%s (%s)") %\ + (version.version_string(), version.version_string_with_vcs()) + class VolumeCommands(object): """Methods for dealing with a cloud in an odd state""" @@ -1050,7 +1060,8 @@ CATEGORIES = [ ('volume', VolumeCommands), ('instance_type', InstanceTypeCommands), ('image', ImageCommands), - ('flavor', InstanceTypeCommands)] + ('flavor', InstanceTypeCommands), + ('version', VersionCommands)] def lazy_match(name, key_value_tuples): From 0927299ac1301699c2915b48bb076d1eac7b84a1 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 28 Apr 2011 12:49:07 -0700 Subject: [PATCH 051/129] fixed docstring per jsb --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index ad2960d1..d2d81e5a 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -760,7 +760,7 @@ class DbCommands(object): class VersionCommands(object): - """Class for managing the database.""" + """Class for exposing the codebase version.""" def __init__(self): pass From d97c349cbd0b15e3c86650943f07b06f13ca704f Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 28 Apr 2011 12:49:48 -0700 Subject: [PATCH 052/129] pep8 --- bin/nova-manage | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/nova-manage b/bin/nova-manage index d2d81e5a..8122e674 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -769,6 +769,7 @@ class VersionCommands(object): print _("%s (%s)") %\ (version.version_string(), version.version_string_with_vcs()) + class VolumeCommands(object): """Methods for dealing with a cloud in an odd state""" From 1ac621a37910cd83536e0839d7a74ecca0ad5ccc Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 29 Apr 2011 13:20:31 +0400 Subject: [PATCH 053/129] Changed test_cloud and fake virt driver to show out the fix. --- nova/tests/test_cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index c45bdd12..f271c03f 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -290,7 +290,7 @@ class CloudTestCase(test.TestCase): instance_id = rv['instancesSet'][0]['instanceId'] output = self.cloud.get_console_output(context=self.context, instance_id=[instance_id]) - self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE OUTPUT') + self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE?OUTPUT') # TODO(soren): We need this until we can stop polling in the rpc code # for unit tests. greenthread.sleep(0.3) From cb694435080f83e6dd223c69cbe39586c6901559 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sun, 1 May 2011 01:01:01 -0700 Subject: [PATCH 055/129] Added checking ip_v6 flag and test for it --- nova/tests/test_virt.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 462cf5aa..b8a2b5c7 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -44,7 +44,7 @@ def _concurrency(wait, done, target): done.send() -def _create_network_info(count=1): +def _create_network_info(count=1, ipv6=True): fake = 'fake' fake_ip = '0.0.0.0/0' fake_ip_2 = '0.0.0.1/0' @@ -55,8 +55,11 @@ def _create_network_info(count=1): 'cidr': fake_ip, 'cidr_v6': fake_ip} mapping = {'mac': fake, - 'ips': [{'ip': fake_ip}, {'ip': fake_ip}], - 'ip6s': [{'ip': fake_ip}, {'ip': fake_ip_2}, {'ip': fake_ip_3}]} + 'ips': [{'ip': fake_ip}, {'ip': fake_ip}]} + if ipv6: + mapping['ip6s'] = [{'ip': fake_ip}, + {'ip': fake_ip_2}, + {'ip': fake_ip_3}] return [(network, mapping) for x in xrange(0, count)] @@ -825,12 +828,20 @@ class IptablesFirewallTestCase(test.TestCase): "TCP port 80/81 acceptance rule wasn't added") db.instance_destroy(admin_ctxt, instance_ref['id']) - def test_filters_for_instance(self): - network_info = _create_network_info() + def test_filters_for_instance_with_ip_v6(self): + self.flags(use_ipv6=True) + network_info = _create_network_info(ipv6=FLAGS.use_ipv6) rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) self.assertEquals(len(rulesv4), 2) self.assertEquals(len(rulesv6), 3) + def test_filters_for_instance_without_ip_v6(self): + self.flags(use_ipv6=False) + network_info = _create_network_info(ipv6=FLAGS.use_ipv6) + rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) + self.assertEquals(len(rulesv4), 2) + self.assertEquals(len(rulesv6), 0) + def multinic_iptables_test(self): ipv4_rules_per_network = 2 ipv6_rules_per_network = 3 From e7071f190d51759f71d2ca156f2d63f1e9ae0ed1 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sun, 1 May 2011 20:43:06 -0700 Subject: [PATCH 056/129] small changes in libvirt tests --- nova/tests/test_virt.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index b8a2b5c7..47432bae 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -44,7 +44,9 @@ def _concurrency(wait, done, target): done.send() -def _create_network_info(count=1, ipv6=True): +def _create_network_info(count=1, ipv6=None): + if ipv6 is None: + ipv6 = FLAGS.use_ipv6 fake = 'fake' fake_ip = '0.0.0.0/0' fake_ip_2 = '0.0.0.1/0' @@ -830,14 +832,14 @@ class IptablesFirewallTestCase(test.TestCase): def test_filters_for_instance_with_ip_v6(self): self.flags(use_ipv6=True) - network_info = _create_network_info(ipv6=FLAGS.use_ipv6) + network_info = _create_network_info() rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) self.assertEquals(len(rulesv4), 2) self.assertEquals(len(rulesv6), 3) def test_filters_for_instance_without_ip_v6(self): self.flags(use_ipv6=False) - network_info = _create_network_info(ipv6=FLAGS.use_ipv6) + network_info = _create_network_info() rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) self.assertEquals(len(rulesv4), 2) self.assertEquals(len(rulesv6), 0) From b9d6d497aac62c3aeeab58bbaf2e2002177c9b72 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 2 May 2011 10:17:51 -0400 Subject: [PATCH 057/129] Use my_ip for libvirt version of get_host_ip_addr. --- nova/tests/test_virt.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 462cf5aa..9305de4c 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -641,6 +641,11 @@ class LibvirtConnTestCase(test.TestCase): self.assertTrue(count) + def test_get_host_ip_addr(self): + conn = libvirt_conn.LibvirtConnection(False) + ip = conn.get_host_ip_addr() + self.assertEquals(ip, FLAGS.my_ip) + def tearDown(self): self.manager.delete_project(self.project) self.manager.delete_user(self.user) From 19a4d66df07329324b459a25928b3cacabffbffe Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 2 May 2011 12:49:10 -0700 Subject: [PATCH 058/129] initial pass --- nova/scheduler/query.py | 164 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 nova/scheduler/query.py diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py new file mode 100644 index 00000000..5379b3d4 --- /dev/null +++ b/nova/scheduler/query.py @@ -0,0 +1,164 @@ +# Copyright (c) 2011 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. + +""" +Query is a plug-in mechanism for requesting instance resources. +Three plug-ins are included: PassThru, Flavor & JSON. PassThru just +returns the full, unfiltered list of hosts. Flavor is a hard coded +matching mechanism based on flavor criteria and JSON is an ad-hoc +query grammar. +""" + +from nova import exception +from nova import utils + +class Query: + """Base class for query plug-ins.""" + + def instance_type_to_query(self, instance_type): + """Convert instance_type into a query for most common use-case.""" + raise exception.Error(_("Query driver not specified.")) + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts that fulfill the query.""" + raise exception.Error(_("Query driver not specified.")) + + +def load_driver(driver_name): + resource = utils.import_class(driver_name) + if type(resource) != types.ClassType: + continue + cls = resource + if not issubclass(cls, Query): + raise exception.Error(_("Query driver does not derive " + "from nova.scheduler.query.Query.")) + return cls + + +class PassThruQuery: + """NOP query plug-in. Returns all hosts in ZoneManager. + This essentially does what the old Scheduler+Chance used + to give us.""" + + def instance_type_to_query(self, instance_type): + """Return anything to prevent base-class from raising + exception.""" + return (str(self.__class__), instance_type) + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts from ZoneManager list.""" + hosts = zone_manager.service_state.get('compute', {}) + return [(host, capabilities) + for host, capabilities in hosts.iteritems()] + + +class FlavorQuery: + """Query plug-in hard-coded to work with flavors.""" + + def instance_type_to_query(self, instance_type): + """Use instance_type to filter hosts.""" + return (str(self.__class__), instance_type) + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts that can create instance_type.""" + hosts = zone_manager.service_state.get('compute', {}) + selected_hosts = [] + instance_type = query + for host, capabilities in hosts.iteritems(): + host_ram_mb = capabilities.get['host_memory']['free'] + disk_bytes = capabilities.get['disk']['available'] + if host_ram_mb >= instance_type['memory_mb'] and \ + disk_bytes >= instance_type['local_gb']: + selected_hosts.append((host, capabilities)) + return selected_hosts + +#host entries (currently) are like: +# {'host_name-description': 'Default install of XenServer', +# 'host_hostname': 'xs-mini', +# 'host_memory': {'total': 8244539392, +# 'overhead': 184225792, +# 'free': 3868327936, +# 'free-computed': 3840843776}, +# 'host_other-config': {}, +# 'host_ip_address': '192.168.1.109', +# 'host_cpu_info': {}, +# 'disk': {'available': 32954957824, +# 'total': 50394562560, +# 'used': 17439604736}, +# 'host_uuid': 'cedb9b39-9388-41df-8891-c5c9a0c0fe5f', +# 'host_name-label': 'xs-mini'} + +# instance_type table has: +#name = Column(String(255), unique=True) +#memory_mb = Column(Integer) +#vcpus = Column(Integer) +#local_gb = Column(Integer) +#flavorid = Column(Integer, unique=True) +#swap = Column(Integer, nullable=False, default=0) +#rxtx_quota = Column(Integer, nullable=False, default=0) +#rxtx_cap = Column(Integer, nullable=False, default=0) + +class JsonQuery: + """Query plug-in to allow simple JSON-based grammar for selecting hosts.""" + + def _equals(self, args): + pass + + def _less_than(self, args): + pass + + def _greater_than(self, args): + pass + + def _in(self, args): + pass + + def _less_than_equal(self, args): + pass + + def _greater_than_equal(self, args): + pass + + def _not(self, args): + pass + + def _must(self, args): + pass + + def _or(self, args): + pass + + commands = { + '=': _equals, + '<': _less_than, + '>': _greater_than, + 'in': _in, + '<=': _less_than_equal, + '>=': _greater_than_equal, + 'not': _not, + 'must', _must, + 'or', _or, + } + + def instance_type_to_query(self, instance_type): + """Convert instance_type into JSON query object.""" + return (str(self.__class__), instance_type) + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts that can fulfill query.""" + return [] + + + From 324f2cf4b9b9122b5aa969c3746faf4869b9a066 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Tue, 3 May 2011 11:05:45 +0400 Subject: [PATCH 059/129] Moved reencoding logic to compute manager and cloud EC2 API. --- nova/tests/test_cloud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index f271c03f..311adfd5 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -290,7 +290,8 @@ class CloudTestCase(test.TestCase): instance_id = rv['instancesSet'][0]['instanceId'] output = self.cloud.get_console_output(context=self.context, instance_id=[instance_id]) - self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE?OUTPUT') + self.assertEquals(b64decode(output['output']).decode('utf-8'), + u'FAKE CONSOLE\ufffdOUTPUT') # TODO(soren): We need this until we can stop polling in the rpc code # for unit tests. greenthread.sleep(0.3) From 68977a27abc6d881bffefcf7077e41365dc32041 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 3 May 2011 15:02:55 -0700 Subject: [PATCH 060/129] tests and better driver loading --- nova/scheduler/query.py | 50 +++++++++++++--------- nova/tests/test_query.py | 89 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 nova/tests/test_query.py diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 5379b3d4..4971bc7e 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -15,39 +15,38 @@ """ Query is a plug-in mechanism for requesting instance resources. -Three plug-ins are included: PassThru, Flavor & JSON. PassThru just +Three plug-ins are included: AllHosts, Flavor & JSON. AllHosts just returns the full, unfiltered list of hosts. Flavor is a hard coded matching mechanism based on flavor criteria and JSON is an ad-hoc query grammar. """ from nova import exception +from nova import flags +from nova import log as logging from nova import utils +LOG = logging.getLogger('nova.scheduler.query') + +FLAGS = flags.FLAGS +flags.DEFINE_string('default_query_engine', + 'nova.scheduler.query.AllHostsQuery', + 'Which query engine to use for filtering hosts.') + + class Query: """Base class for query plug-ins.""" def instance_type_to_query(self, instance_type): """Convert instance_type into a query for most common use-case.""" - raise exception.Error(_("Query driver not specified.")) + raise exception.BadSchedulerQueryDriver() def filter_hosts(self, zone_manager, query): """Return a list of hosts that fulfill the query.""" - raise exception.Error(_("Query driver not specified.")) + raise exception.BadSchedulerQueryDriver() -def load_driver(driver_name): - resource = utils.import_class(driver_name) - if type(resource) != types.ClassType: - continue - cls = resource - if not issubclass(cls, Query): - raise exception.Error(_("Query driver does not derive " - "from nova.scheduler.query.Query.")) - return cls - - -class PassThruQuery: +class AllHostsQuery: """NOP query plug-in. Returns all hosts in ZoneManager. This essentially does what the old Scheduler+Chance used to give us.""" @@ -59,7 +58,7 @@ class PassThruQuery: def filter_hosts(self, zone_manager, query): """Return a list of hosts from ZoneManager list.""" - hosts = zone_manager.service_state.get('compute', {}) + hosts = zone_manager.service_states.get('compute', {}) return [(host, capabilities) for host, capabilities in hosts.iteritems()] @@ -73,7 +72,7 @@ class FlavorQuery: def filter_hosts(self, zone_manager, query): """Return a list of hosts that can create instance_type.""" - hosts = zone_manager.service_state.get('compute', {}) + hosts = zone_manager.service_states.get('compute', {}) selected_hosts = [] instance_type = query for host, capabilities in hosts.iteritems(): @@ -148,8 +147,8 @@ class JsonQuery: '<=': _less_than_equal, '>=': _greater_than_equal, 'not': _not, - 'must', _must, - 'or', _or, + 'must': _must, + 'or': _or, } def instance_type_to_query(self, instance_type): @@ -161,4 +160,15 @@ class JsonQuery: return [] - +# Since the caller may specify which driver to use we need +# to have an authoritative list of what is permissible. +DRIVERS = [AllHostsQuery, FlavorQuery, JsonQuery] + + +def choose_driver(driver_name=None): + if not driver_name: + driver_name = FLAGS.default_query_engine + for driver in DRIVERS: + if str(driver) == driver_name: + return driver + raise exception.SchedulerQueryDriverNotFound(driver_name=driver_name) diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py new file mode 100644 index 00000000..0fab9193 --- /dev/null +++ b/nova/tests/test_query.py @@ -0,0 +1,89 @@ +# Copyright 2011 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. +""" +Tests For Scheduler Query Drivers +""" + +from nova import exception +from nova import flags +from nova import test +from nova.scheduler import query + +FLAGS = flags.FLAGS + +class FakeZoneManager: + pass + +class QueryTestCase(test.TestCase): + """Test case for query drivers.""" + + def _host_caps(self, multiplier): + return {'host_name-description':'XenServer %s' % multiplier, + 'host_hostname':'xs-%s' % multiplier, + 'host_memory':{'total': 100, + 'overhead': 5, + 'free': 5 + multiplier * 5, + 'free-computed': 5 + multiplier * 5}, + 'host_other-config':{}, + 'host_ip_address':'192.168.1.%d' % (100 + multiplier), + 'host_cpu_info':{}, + 'disk':{'available': 100 + multiplier * 100, + 'total': 1000, + 'used': 50 + multiplier * 50}, + 'host_uuid':'xxx-%d' % multiplier, + 'host_name-label':'xs-%s' % multiplier} + + def setUp(self): + self.old_flag = FLAGS.default_query_engine + FLAGS.default_query_engine = 'nova.scheduler.query.AllHostsQuery' + self.instance_type = dict(name='tiny', + memory_mb=500, + vcpus=10, + local_gb=50, + flavorid=1, + swap=500, + rxtx_quota=30000, + rxtx_cap=200) + + hosts = {} + for x in xrange(10): + hosts['host%s' % x] = self._host_caps(x) + + self.zone_manager = FakeZoneManager() + self.zone_manager.service_states = {} + self.zone_manager.service_states['compute'] = hosts + + def tearDown(self): + FLAGS.default_query_engine = self.old_flag + + def test_choose_driver(self): + driver = query.choose_driver() + self.assertEquals(str(driver), 'nova.scheduler.query.AllHostsQuery') + driver = query.choose_driver('nova.scheduler.query.FlavorQuery') + self.assertEquals(str(driver), 'nova.scheduler.query.FlavorQuery') + try: + query.choose_driver('does not exist') + self.fail("Should not find driver") + except exception.SchedulerQueryDriverNotFound: + pass + + def test_all_host_driver(self): + driver = query.AllHostsQuery() + cooked = driver.instance_type_to_query(self.instance_type) + hosts = driver.filter_hosts(self.zone_manager, cooked) + self.assertEquals(10, len(hosts)) + for host, capabilities in hosts: + self.assertTrue(host.startswith('host')) + From c384e7f4f696dd2050442ae23c307dd9ea7b04dc Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 4 May 2011 06:16:33 -0700 Subject: [PATCH 061/129] flavor test --- nova/scheduler/query.py | 6 +++--- nova/tests/test_query.py | 44 +++++++++++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 4971bc7e..1c593f1b 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -74,10 +74,10 @@ class FlavorQuery: """Return a list of hosts that can create instance_type.""" hosts = zone_manager.service_states.get('compute', {}) selected_hosts = [] - instance_type = query + query_type, instance_type = query for host, capabilities in hosts.iteritems(): - host_ram_mb = capabilities.get['host_memory']['free'] - disk_bytes = capabilities.get['disk']['available'] + host_ram_mb = capabilities['host_memory']['free'] + disk_bytes = capabilities['disk']['available'] if host_ram_mb >= instance_type['memory_mb'] and \ disk_bytes >= instance_type['local_gb']: selected_hosts.append((host, capabilities)) diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 0fab9193..d89183e9 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -30,32 +30,39 @@ class QueryTestCase(test.TestCase): """Test case for query drivers.""" def _host_caps(self, multiplier): + # Returns host capabilities in the following way: + # host0 = memory:free 10 (100max) + # disk:available 100 (1000max) + # hostN = memory:free 10 + 10N + # disk:available 100 + 100N + # in other words: hostN has more resources than host0 + # which means ... don't go above 10 hosts. return {'host_name-description':'XenServer %s' % multiplier, 'host_hostname':'xs-%s' % multiplier, 'host_memory':{'total': 100, - 'overhead': 5, - 'free': 5 + multiplier * 5, - 'free-computed': 5 + multiplier * 5}, + 'overhead': 10, + 'free': 10 + multiplier * 10, + 'free-computed': 10 + multiplier * 10}, 'host_other-config':{}, 'host_ip_address':'192.168.1.%d' % (100 + multiplier), 'host_cpu_info':{}, 'disk':{'available': 100 + multiplier * 100, 'total': 1000, - 'used': 50 + multiplier * 50}, + 'used': 0}, 'host_uuid':'xxx-%d' % multiplier, 'host_name-label':'xs-%s' % multiplier} def setUp(self): self.old_flag = FLAGS.default_query_engine FLAGS.default_query_engine = 'nova.scheduler.query.AllHostsQuery' - self.instance_type = dict(name='tiny', - memory_mb=500, - vcpus=10, - local_gb=50, - flavorid=1, - swap=500, - rxtx_quota=30000, - rxtx_cap=200) + self.instance_type = dict(name= 'tiny', + memory_mb= 50, + vcpus= 10, + local_gb= 500, + flavorid= 1, + swap= 500, + rxtx_quota= 30000, + rxtx_cap= 200) hosts = {} for x in xrange(10): @@ -69,10 +76,13 @@ class QueryTestCase(test.TestCase): FLAGS.default_query_engine = self.old_flag def test_choose_driver(self): + # Test default driver ... driver = query.choose_driver() self.assertEquals(str(driver), 'nova.scheduler.query.AllHostsQuery') + # Test valid driver ... driver = query.choose_driver('nova.scheduler.query.FlavorQuery') self.assertEquals(str(driver), 'nova.scheduler.query.FlavorQuery') + # Test invalid driver ... try: query.choose_driver('does not exist') self.fail("Should not find driver") @@ -87,3 +97,13 @@ class QueryTestCase(test.TestCase): for host, capabilities in hosts: self.assertTrue(host.startswith('host')) + def test_flavor_driver(self): + driver = query.FlavorQuery() + # filter all hosts that can support 50 ram and 500 disk + cooked = driver.instance_type_to_query(self.instance_type) + hosts = driver.filter_hosts(self.zone_manager, cooked) + self.assertEquals(6, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + self.assertEquals('host4', just_hosts[0]) + self.assertEquals('host9', just_hosts[5]) From a99caae5352e6da81b2854eeefad97e95e216873 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 4 May 2011 23:09:06 +0200 Subject: [PATCH 062/129] It's ok if there's no commit history. Otherwise the test suite in the tarball will fail. --- nova/tests/test_misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_misc.py b/nova/tests/test_misc.py index ad62b48b..cf8f4c05 100644 --- a/nova/tests/test_misc.py +++ b/nova/tests/test_misc.py @@ -68,7 +68,7 @@ class ProjectTestCase(test.TestCase): contributors.add(str_dict_replace(email, mailmap)) else: - self.assertTrue(False, 'Cannot read commit history') + return for contributor in contributors: if contributor == 'nova-core': From c47c40b5bc4111b4fa80583b926c7888fef49762 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 4 May 2011 14:48:23 -0700 Subject: [PATCH 063/129] json parser --- nova/scheduler/query.py | 123 +++++++++++++++++++++++++++++++++------ nova/tests/test_query.py | 25 +++++--- 2 files changed, 124 insertions(+), 24 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 1c593f1b..1b84e6b1 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -21,6 +21,8 @@ matching mechanism based on flavor criteria and JSON is an ad-hoc query grammar. """ +import json + from nova import exception from nova import flags from nova import log as logging @@ -58,9 +60,8 @@ class AllHostsQuery: def filter_hosts(self, zone_manager, query): """Return a list of hosts from ZoneManager list.""" - hosts = zone_manager.service_states.get('compute', {}) - return [(host, capabilities) - for host, capabilities in hosts.iteritems()] + return [(host, services) + for host, services in zone_manager.service_state.iteritems()] class FlavorQuery: @@ -72,10 +73,10 @@ class FlavorQuery: def filter_hosts(self, zone_manager, query): """Return a list of hosts that can create instance_type.""" - hosts = zone_manager.service_states.get('compute', {}) + instance_type = query selected_hosts = [] - query_type, instance_type = query - for host, capabilities in hosts.iteritems(): + for host, services in zone_manager.service_states.iteritems(): + capabilities = services.get('compute', {}) host_ram_mb = capabilities['host_memory']['free'] disk_bytes = capabilities['disk']['available'] if host_ram_mb >= instance_type['memory_mb'] and \ @@ -113,31 +114,74 @@ class JsonQuery: """Query plug-in to allow simple JSON-based grammar for selecting hosts.""" def _equals(self, args): - pass + """First term is == all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs != rhs: + return False + return True def _less_than(self, args): - pass + """First term is < all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs >= rhs: + return False + return True def _greater_than(self, args): - pass + """First term is > all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs <= rhs: + return False + return True def _in(self, args): - pass + """First term is in set of remaining terms""" + if len(args) < 2: + return False + return args[0] in args[1:] def _less_than_equal(self, args): - pass + """First term is <= all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs > rhs: + return False + return True def _greater_than_equal(self, args): - pass + """First term is >= all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs < rhs: + return False + return True def _not(self, args): - pass + if len(args) == 0: + return False + return not args[0] def _must(self, args): - pass + return True def _or(self, args): - pass + return True in args + + def _and(self, args): + return False not in args commands = { '=': _equals, @@ -149,15 +193,60 @@ class JsonQuery: 'not': _not, 'must': _must, 'or': _or, + 'and': _and, } def instance_type_to_query(self, instance_type): """Convert instance_type into JSON query object.""" - return (str(self.__class__), instance_type) + required_ram = instance_type['memory_mb'] + required_disk = instance_type['local_gb'] + query = ['and', + ['>=', '$compute.host_memory.free', required_ram], + ['>=', '$compute.disk.available', required_disk] + ] + return (str(self.__class__), json.dumps(query)) + + def _parse_string(self, string, host, services): + """Strings prefixed with $ are capability lookups in the + form '$service.capability[.subcap*]'""" + if not string: + return None + if string[0] != '$': + return string + + path = string[1:].split('.') + for item in path: + services = services.get(item, None) + if not services: + return None + return services + + def _process_query(self, zone_manager, query, host, services): + if len(query) == 0: + return True + cmd = query[0] + method = self.commands[cmd] # Let exception fly. + cooked_args = [] + for arg in query[1:]: + if isinstance(arg, list): + arg = self._process_query(zone_manager, arg, host, services) + elif isinstance(arg, basestring): + arg = self._parse_string(arg, host, services) + if arg != None: + cooked_args.append(arg) + result = method(self, cooked_args) + print "*** %s %s = %s" % (cmd, cooked_args, result) + return result def filter_hosts(self, zone_manager, query): """Return a list of hosts that can fulfill query.""" - return [] + expanded = json.loads(query) + hosts = [] + for host, services in zone_manager.service_states.iteritems(): + print "-----" + if self._process_query(zone_manager, expanded, host, services): + hosts.append((host, services)) + return hosts # Since the caller may specify which driver to use we need diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index d89183e9..7a036a4d 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -64,13 +64,11 @@ class QueryTestCase(test.TestCase): rxtx_quota= 30000, rxtx_cap= 200) - hosts = {} - for x in xrange(10): - hosts['host%s' % x] = self._host_caps(x) - self.zone_manager = FakeZoneManager() - self.zone_manager.service_states = {} - self.zone_manager.service_states['compute'] = hosts + states = {} + for x in xrange(10): + states['host%s' % x] = {'compute': self._host_caps(x)} + self.zone_manager.service_states = states def tearDown(self): FLAGS.default_query_engine = self.old_flag @@ -100,7 +98,20 @@ class QueryTestCase(test.TestCase): def test_flavor_driver(self): driver = query.FlavorQuery() # filter all hosts that can support 50 ram and 500 disk - cooked = driver.instance_type_to_query(self.instance_type) + name, cooked = driver.instance_type_to_query(self.instance_type) + self.assertEquals('nova.scheduler.query.FlavorQuery', name) + hosts = driver.filter_hosts(self.zone_manager, cooked) + self.assertEquals(6, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + self.assertEquals('host4', just_hosts[0]) + self.assertEquals('host9', just_hosts[5]) + + def test_json_driver(self): + driver = query.JsonQuery() + # filter all hosts that can support 50 ram and 500 disk + name, cooked = driver.instance_type_to_query(self.instance_type) + self.assertEquals('nova.scheduler.query.JsonQuery', name) hosts = driver.filter_hosts(self.zone_manager, cooked) self.assertEquals(6, len(hosts)) just_hosts = [host for host, caps in hosts] From 84598a77da26ec2e7694281925301ba94c3135aa Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 4 May 2011 21:50:54 -0400 Subject: [PATCH 064/129] added myself to Authors --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 1cdeeff9..99c07f7c 100644 --- a/Authors +++ b/Authors @@ -78,6 +78,7 @@ Trey Morris Tushar Patil Vasiliy Shlykov Vishvananda Ishaya +William Wolf Yoshiaki Tamura Youcef Laribi Zhixue Wu From e45823d51b53e92a7195f69bbb156b77483fd932 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Thu, 5 May 2011 07:50:58 +0400 Subject: [PATCH 065/129] Moved all reencoding to compute manager to satisfy both Direct API and internal cloud call. --- nova/tests/test_cloud.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 311adfd5..f271c03f 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -290,8 +290,7 @@ class CloudTestCase(test.TestCase): instance_id = rv['instancesSet'][0]['instanceId'] output = self.cloud.get_console_output(context=self.context, instance_id=[instance_id]) - self.assertEquals(b64decode(output['output']).decode('utf-8'), - u'FAKE CONSOLE\ufffdOUTPUT') + self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE?OUTPUT') # TODO(soren): We need this until we can stop polling in the rpc code # for unit tests. greenthread.sleep(0.3) From 083b65bb2ecc1fc2e2021d76e04ea6cd860763cf Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 04:57:25 -0700 Subject: [PATCH 066/129] and or test --- nova/scheduler/query.py | 10 +++++----- nova/tests/test_query.py | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 1b84e6b1..3233cc0d 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -19,6 +19,10 @@ Three plug-ins are included: AllHosts, Flavor & JSON. AllHosts just returns the full, unfiltered list of hosts. Flavor is a hard coded matching mechanism based on flavor criteria and JSON is an ad-hoc query grammar. + +Note: These are hard filters. All capabilities used must be present +or the host will excluded. If you want soft filters use the weighting +mechanism which is intended for the more touchy-feely capabilities. """ import json @@ -61,7 +65,7 @@ class AllHostsQuery: def filter_hosts(self, zone_manager, query): """Return a list of hosts from ZoneManager list.""" return [(host, services) - for host, services in zone_manager.service_state.iteritems()] + for host, services in zone_manager.service_states.iteritems()] class FlavorQuery: @@ -174,9 +178,6 @@ class JsonQuery: return False return not args[0] - def _must(self, args): - return True - def _or(self, args): return True in args @@ -191,7 +192,6 @@ class JsonQuery: '<=': _less_than_equal, '>=': _greater_than_equal, 'not': _not, - 'must': _must, 'or': _or, 'and': _and, } diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 7a036a4d..896b2364 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -16,6 +16,8 @@ Tests For Scheduler Query Drivers """ +import json + from nova import exception from nova import flags from nova import test @@ -118,3 +120,27 @@ class QueryTestCase(test.TestCase): just_hosts.sort() self.assertEquals('host4', just_hosts[0]) self.assertEquals('host9', just_hosts[5]) + + # Try some custom queries + + raw = ['or', + ['and', + ['<', '$compute.host_memory.free', 30], + ['<', '$compute.disk.available', 300] + ], + ['and', + ['>', '$compute.host_memory.free', 70], + ['>', '$compute.disk.available', 700] + ] + ] + cooked = json.dumps(raw) + hosts = driver.filter_hosts(self.zone_manager, cooked) + + self.assertEquals(5, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + self.assertEquals('host0', just_hosts[0]) + self.assertEquals('host1', just_hosts[1]) + self.assertEquals('host7', just_hosts[2]) + self.assertEquals('host8', just_hosts[3]) + self.assertEquals('host9', just_hosts[4]) From 66c26e2849ec672061841241c271d61e11e4546f Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 04:59:26 -0700 Subject: [PATCH 067/129] and or test --- nova/tests/test_query.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 896b2364..90ae80dc 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -139,8 +139,5 @@ class QueryTestCase(test.TestCase): self.assertEquals(5, len(hosts)) just_hosts = [host for host, caps in hosts] just_hosts.sort() - self.assertEquals('host0', just_hosts[0]) - self.assertEquals('host1', just_hosts[1]) - self.assertEquals('host7', just_hosts[2]) - self.assertEquals('host8', just_hosts[3]) - self.assertEquals('host9', just_hosts[4]) + for index, host in zip([0, 1, 7, 8, 9], just_hosts): + self.assertEquals('host%d' % index, host) From a7e02d013b4843085d7c6ea45f1e97797fd563f1 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 05:29:31 -0700 Subject: [PATCH 068/129] not = --- nova/scheduler/query.py | 7 +++++-- nova/tests/test_query.py | 29 +++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 3233cc0d..0279efc9 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -176,7 +176,7 @@ class JsonQuery: def _not(self, args): if len(args) == 0: return False - return not args[0] + return [not arg for arg in args] def _or(self, args): return True in args @@ -244,7 +244,10 @@ class JsonQuery: hosts = [] for host, services in zone_manager.service_states.iteritems(): print "-----" - if self._process_query(zone_manager, expanded, host, services): + r = self._process_query(zone_manager, expanded, host, services) + if isinstance(r, list): + r = True in r + if r: hosts.append((host, services)) return hosts diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 90ae80dc..9bfc83b7 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -33,7 +33,7 @@ class QueryTestCase(test.TestCase): def _host_caps(self, multiplier): # Returns host capabilities in the following way: - # host0 = memory:free 10 (100max) + # host1 = memory:free 10 (100max) # disk:available 100 (1000max) # hostN = memory:free 10 + 10N # disk:available 100 + 100N @@ -69,7 +69,7 @@ class QueryTestCase(test.TestCase): self.zone_manager = FakeZoneManager() states = {} for x in xrange(10): - states['host%s' % x] = {'compute': self._host_caps(x)} + states['host%02d' % (x + 1)] = {'compute': self._host_caps(x)} self.zone_manager.service_states = states def tearDown(self): @@ -106,8 +106,8 @@ class QueryTestCase(test.TestCase): self.assertEquals(6, len(hosts)) just_hosts = [host for host, caps in hosts] just_hosts.sort() - self.assertEquals('host4', just_hosts[0]) - self.assertEquals('host9', just_hosts[5]) + self.assertEquals('host05', just_hosts[0]) + self.assertEquals('host10', just_hosts[5]) def test_json_driver(self): driver = query.JsonQuery() @@ -118,8 +118,8 @@ class QueryTestCase(test.TestCase): self.assertEquals(6, len(hosts)) just_hosts = [host for host, caps in hosts] just_hosts.sort() - self.assertEquals('host4', just_hosts[0]) - self.assertEquals('host9', just_hosts[5]) + self.assertEquals('host05', just_hosts[0]) + self.assertEquals('host10', just_hosts[5]) # Try some custom queries @@ -139,5 +139,18 @@ class QueryTestCase(test.TestCase): self.assertEquals(5, len(hosts)) just_hosts = [host for host, caps in hosts] just_hosts.sort() - for index, host in zip([0, 1, 7, 8, 9], just_hosts): - self.assertEquals('host%d' % index, host) + for index, host in zip([1, 2, 8, 9, 10], just_hosts): + self.assertEquals('host%02d' % index, host) + + raw = ['not', + ['=', '$compute.host_memory.free', 30], + ] + cooked = json.dumps(raw) + hosts = driver.filter_hosts(self.zone_manager, cooked) + + self.assertEquals(9, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts): + self.assertEquals('host%02d' % index, host) + From 9e7ca7a1164f1b1984368d86e5eda9e616f9d037 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 05:30:58 -0700 Subject: [PATCH 069/129] not = --- nova/tests/test_query.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 9bfc83b7..78e8ee9d 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -153,4 +153,14 @@ class QueryTestCase(test.TestCase): just_hosts.sort() for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts): self.assertEquals('host%02d' % index, host) + + raw = ['in', '$compute.host_memory.free', 20, 40, 60, 80, 100]] + cooked = json.dumps(raw) + hosts = driver.filter_hosts(self.zone_manager, cooked) + + self.assertEquals(5, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + for index, host in zip([2, 4, 6, 8, 10], just_hosts): + self.assertEquals('host%02d' % index, host) From cef477c10abc381e1b2b4054d89d099a102dbcff Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 06:01:56 -0700 Subject: [PATCH 070/129] pep8 --- nova/scheduler/query.py | 43 ++++++++++++------------ nova/tests/test_query.py | 71 ++++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 56 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 0279efc9..4332f43b 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -114,6 +114,7 @@ class FlavorQuery: #rxtx_quota = Column(Integer, nullable=False, default=0) #rxtx_cap = Column(Integer, nullable=False, default=0) + class JsonQuery: """Query plug-in to allow simple JSON-based grammar for selecting hosts.""" @@ -162,7 +163,7 @@ class JsonQuery: if lhs > rhs: return False return True - + def _greater_than_equal(self, args): """First term is >= all the other terms.""" if len(args) < 2: @@ -180,10 +181,10 @@ class JsonQuery: def _or(self, args): return True in args - + def _and(self, args): return False not in args - + commands = { '=': _equals, '<': _less_than, @@ -200,7 +201,7 @@ class JsonQuery: """Convert instance_type into JSON query object.""" required_ram = instance_type['memory_mb'] required_disk = instance_type['local_gb'] - query = ['and', + query = ['and', ['>=', '$compute.host_memory.free', required_ram], ['>=', '$compute.disk.available', required_disk] ] @@ -219,24 +220,24 @@ class JsonQuery: services = services.get(item, None) if not services: return None - return services + return services def _process_query(self, zone_manager, query, host, services): - if len(query) == 0: - return True - cmd = query[0] - method = self.commands[cmd] # Let exception fly. - cooked_args = [] - for arg in query[1:]: - if isinstance(arg, list): - arg = self._process_query(zone_manager, arg, host, services) - elif isinstance(arg, basestring): - arg = self._parse_string(arg, host, services) - if arg != None: - cooked_args.append(arg) - result = method(self, cooked_args) - print "*** %s %s = %s" % (cmd, cooked_args, result) - return result + if len(query) == 0: + return True + cmd = query[0] + method = self.commands[cmd] # Let exception fly. + cooked_args = [] + for arg in query[1:]: + if isinstance(arg, list): + arg = self._process_query(zone_manager, arg, host, services) + elif isinstance(arg, basestring): + arg = self._parse_string(arg, host, services) + if arg != None: + cooked_args.append(arg) + result = method(self, cooked_args) + print "*** %s %s = %s" % (cmd, cooked_args, result) + return result def filter_hosts(self, zone_manager, query): """Return a list of hosts that can fulfill query.""" @@ -253,7 +254,7 @@ class JsonQuery: # Since the caller may specify which driver to use we need -# to have an authoritative list of what is permissible. +# to have an authoritative list of what is permissible. DRIVERS = [AllHostsQuery, FlavorQuery, JsonQuery] diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 78e8ee9d..8b894a4e 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -1,4 +1,4 @@ -# Copyright 2011 OpenStack LLC. +# Copyright 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -25,9 +25,11 @@ from nova.scheduler import query FLAGS = flags.FLAGS + class FakeZoneManager: pass + class QueryTestCase(test.TestCase): """Test case for query drivers.""" @@ -39,32 +41,32 @@ class QueryTestCase(test.TestCase): # disk:available 100 + 100N # in other words: hostN has more resources than host0 # which means ... don't go above 10 hosts. - return {'host_name-description':'XenServer %s' % multiplier, - 'host_hostname':'xs-%s' % multiplier, - 'host_memory':{'total': 100, + return {'host_name-description': 'XenServer %s' % multiplier, + 'host_hostname': 'xs-%s' % multiplier, + 'host_memory': {'total': 100, 'overhead': 10, 'free': 10 + multiplier * 10, 'free-computed': 10 + multiplier * 10}, - 'host_other-config':{}, - 'host_ip_address':'192.168.1.%d' % (100 + multiplier), - 'host_cpu_info':{}, - 'disk':{'available': 100 + multiplier * 100, + 'host_other-config': {}, + 'host_ip_address': '192.168.1.%d' % (100 + multiplier), + 'host_cpu_info': {}, + 'disk': {'available': 100 + multiplier * 100, 'total': 1000, 'used': 0}, - 'host_uuid':'xxx-%d' % multiplier, - 'host_name-label':'xs-%s' % multiplier} + 'host_uuid': 'xxx-%d' % multiplier, + 'host_name-label': 'xs-%s' % multiplier} def setUp(self): self.old_flag = FLAGS.default_query_engine FLAGS.default_query_engine = 'nova.scheduler.query.AllHostsQuery' - self.instance_type = dict(name= 'tiny', - memory_mb= 50, - vcpus= 10, - local_gb= 500, - flavorid= 1, - swap= 500, - rxtx_quota= 30000, - rxtx_cap= 200) + self.instance_type = dict(name='tiny', + memory_mb=50, + vcpus=10, + local_gb=500, + flavorid=1, + swap=500, + rxtx_quota=30000, + rxtx_cap=200) self.zone_manager = FakeZoneManager() states = {} @@ -123,16 +125,16 @@ class QueryTestCase(test.TestCase): # Try some custom queries - raw = ['or', - ['and', - ['<', '$compute.host_memory.free', 30], - ['<', '$compute.disk.available', 300] - ], - ['and', - ['>', '$compute.host_memory.free', 70], - ['>', '$compute.disk.available', 700] - ] - ] + raw = ['or', + ['and', + ['<', '$compute.host_memory.free', 30], + ['<', '$compute.disk.available', 300] + ], + ['and', + ['>', '$compute.host_memory.free', 70], + ['>', '$compute.disk.available', 700] + ] + ] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) @@ -141,10 +143,10 @@ class QueryTestCase(test.TestCase): just_hosts.sort() for index, host in zip([1, 2, 8, 9, 10], just_hosts): self.assertEquals('host%02d' % index, host) - - raw = ['not', - ['=', '$compute.host_memory.free', 30], - ] + + raw = ['not', + ['=', '$compute.host_memory.free', 30], + ] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) @@ -153,8 +155,8 @@ class QueryTestCase(test.TestCase): just_hosts.sort() for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts): self.assertEquals('host%02d' % index, host) - - raw = ['in', '$compute.host_memory.free', 20, 40, 60, 80, 100]] + + raw = ['in', '$compute.host_memory.free', 20, 40, 60, 80, 100] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) @@ -163,4 +165,3 @@ class QueryTestCase(test.TestCase): just_hosts.sort() for index, host in zip([2, 4, 6, 8, 10], just_hosts): self.assertEquals('host%02d' % index, host) - From 9d91d098fc80e445470c57bb6e603489b1ca0eb3 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 07:35:44 -0700 Subject: [PATCH 071/129] flipped service_state in ZoneManager and fixed tests --- nova/scheduler/api.py | 8 +++----- nova/scheduler/zone_manager.py | 20 +++++++++----------- nova/tests/test_zones.py | 18 ++++++------------ 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 6bb3bf3c..816ae551 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -76,11 +76,9 @@ def zone_update(context, zone_id, data): return db.zone_update(context, zone_id, data) -def get_zone_capabilities(context, service=None): - """Returns a dict of key, value capabilities for this zone, - or for a particular class of services running in this zone.""" - return _call_scheduler('get_zone_capabilities', context=context, - params=dict(service=service)) +def get_zone_capabilities(context): + """Returns a dict of key, value capabilities for this zone.""" + return _call_scheduler('get_zone_capabilities', context=context) def update_service_capabilities(context, service_name, host, capabilities): diff --git a/nova/scheduler/zone_manager.py b/nova/scheduler/zone_manager.py index 198f9d4c..3ddf6f3c 100644 --- a/nova/scheduler/zone_manager.py +++ b/nova/scheduler/zone_manager.py @@ -106,28 +106,26 @@ class ZoneManager(object): def __init__(self): self.last_zone_db_check = datetime.min self.zone_states = {} # { : ZoneState } - self.service_states = {} # { : { : { cap k : v }}} + self.service_states = {} # { : { : { cap k : v }}} self.green_pool = greenpool.GreenPool() def get_zone_list(self): """Return the list of zones we know about.""" return [zone.to_dict() for zone in self.zone_states.values()] - def get_zone_capabilities(self, context, service=None): + def get_zone_capabilities(self, context): """Roll up all the individual host info to generic 'service' capabilities. Each capability is aggregated into _min and _max values.""" - service_dict = self.service_states - if service: - service_dict = {service: self.service_states.get(service, {})} + hosts_dict = self.service_states # TODO(sandy) - be smarter about fabricating this structure. # But it's likely to change once we understand what the Best-Match # code will need better. combined = {} # { _ : (min, max), ... } - for service_name, host_dict in service_dict.iteritems(): - for host, caps_dict in host_dict.iteritems(): - for cap, value in caps_dict.iteritems(): + for host, host_dict in hosts_dict.iteritems(): + for service_name, service_dict in host_dict.iteritems(): + for cap, value in service_dict.iteritems(): key = "%s_%s" % (service_name, cap) min_value, max_value = combined.get(key, (value, value)) min_value = min(min_value, value) @@ -171,6 +169,6 @@ class ZoneManager(object): """Update the per-service capabilities based on this notification.""" logging.debug(_("Received %(service_name)s service update from " "%(host)s: %(capabilities)s") % locals()) - service_caps = self.service_states.get(service_name, {}) - service_caps[host] = capabilities - self.service_states[service_name] = service_caps + service_caps = self.service_states.get(host, {}) + service_caps[service_name] = capabilities + self.service_states[host] = service_caps diff --git a/nova/tests/test_zones.py b/nova/tests/test_zones.py index 688dc704..e132809d 100644 --- a/nova/tests/test_zones.py +++ b/nova/tests/test_zones.py @@ -78,38 +78,32 @@ class ZoneManagerTestCase(test.TestCase): def test_service_capabilities(self): zm = zone_manager.ZoneManager() - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, {}) zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2)) - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, dict(svc1_a=(1, 1), svc1_b=(2, 2))) zm.update_service_capabilities("svc1", "host1", dict(a=2, b=3)) - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, dict(svc1_a=(2, 2), svc1_b=(3, 3))) zm.update_service_capabilities("svc1", "host2", dict(a=20, b=30)) - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30))) zm.update_service_capabilities("svc10", "host1", dict(a=99, b=99)) - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30), svc10_a=(99, 99), svc10_b=(99, 99))) zm.update_service_capabilities("svc1", "host3", dict(c=5)) - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30), svc1_c=(5, 5), svc10_a=(99, 99), svc10_b=(99, 99))) - caps = zm.get_zone_capabilities(self, 'svc1') - self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30), - svc1_c=(5, 5))) - caps = zm.get_zone_capabilities(self, 'svc10') - self.assertEquals(caps, dict(svc10_a=(99, 99), svc10_b=(99, 99))) - def test_refresh_from_db_replace_existing(self): zm = zone_manager.ZoneManager() zone_state = zone_manager.ZoneState() From 314541ae34290651b6a9020e60f47d0adf30e0d8 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 08:13:22 -0700 Subject: [PATCH 072/129] print statements removed --- nova/scheduler/query.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 4332f43b..1a6705b4 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -236,7 +236,6 @@ class JsonQuery: if arg != None: cooked_args.append(arg) result = method(self, cooked_args) - print "*** %s %s = %s" % (cmd, cooked_args, result) return result def filter_hosts(self, zone_manager, query): @@ -244,7 +243,6 @@ class JsonQuery: expanded = json.loads(query) hosts = [] for host, services in zone_manager.service_states.iteritems(): - print "-----" r = self._process_query(zone_manager, expanded, host, services) if isinstance(r, list): r = True in r From eb2efc7b40d891470b005f5144b3f90e539f3956 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 10:50:59 -0700 Subject: [PATCH 073/129] merge prop fixes --- nova/scheduler/query.py | 37 ++++++++++++++++++++++------------ nova/tests/test_query.py | 43 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 1a6705b4..1e294b59 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -40,19 +40,23 @@ flags.DEFINE_string('default_query_engine', 'Which query engine to use for filtering hosts.') -class Query: +class Query(object): """Base class for query plug-ins.""" def instance_type_to_query(self, instance_type): """Convert instance_type into a query for most common use-case.""" - raise exception.BadSchedulerQueryDriver() + raise NotImplementedError() def filter_hosts(self, zone_manager, query): """Return a list of hosts that fulfill the query.""" - raise exception.BadSchedulerQueryDriver() + raise NotImplementedError() + + def _full_name(self): + """module.classname of the Query object""" + return "%s.%s" % (self.__module__, self.__class__.__name__) -class AllHostsQuery: +class AllHostsQuery(Query): """NOP query plug-in. Returns all hosts in ZoneManager. This essentially does what the old Scheduler+Chance used to give us.""" @@ -60,7 +64,7 @@ class AllHostsQuery: def instance_type_to_query(self, instance_type): """Return anything to prevent base-class from raising exception.""" - return (str(self.__class__), instance_type) + return (self._full_name(), instance_type) def filter_hosts(self, zone_manager, query): """Return a list of hosts from ZoneManager list.""" @@ -68,12 +72,12 @@ class AllHostsQuery: for host, services in zone_manager.service_states.iteritems()] -class FlavorQuery: +class FlavorQuery(Query): """Query plug-in hard-coded to work with flavors.""" def instance_type_to_query(self, instance_type): """Use instance_type to filter hosts.""" - return (str(self.__class__), instance_type) + return (self._full_name(), instance_type) def filter_hosts(self, zone_manager, query): """Return a list of hosts that can create instance_type.""" @@ -115,7 +119,7 @@ class FlavorQuery: #rxtx_cap = Column(Integer, nullable=False, default=0) -class JsonQuery: +class JsonQuery(Query): """Query plug-in to allow simple JSON-based grammar for selecting hosts.""" def _equals(self, args): @@ -175,14 +179,17 @@ class JsonQuery: return True def _not(self, args): + """Flip each of the arguments.""" if len(args) == 0: return False return [not arg for arg in args] def _or(self, args): + """True if any arg is True.""" return True in args def _and(self, args): + """True if all args are True.""" return False not in args commands = { @@ -205,7 +212,7 @@ class JsonQuery: ['>=', '$compute.host_memory.free', required_ram], ['>=', '$compute.disk.available', required_disk] ] - return (str(self.__class__), json.dumps(query)) + return (self._full_name(), json.dumps(query)) def _parse_string(self, string, host, services): """Strings prefixed with $ are capability lookups in the @@ -223,6 +230,7 @@ class JsonQuery: return services def _process_query(self, zone_manager, query, host, services): + """Recursively parse the query structure.""" if len(query) == 0: return True cmd = query[0] @@ -251,15 +259,18 @@ class JsonQuery: return hosts -# Since the caller may specify which driver to use we need -# to have an authoritative list of what is permissible. DRIVERS = [AllHostsQuery, FlavorQuery, JsonQuery] def choose_driver(driver_name=None): + """Since the caller may specify which driver to use we need + to have an authoritative list of what is permissible. This + function checks the driver name against a predefined set + of acceptable drivers.""" + if not driver_name: driver_name = FLAGS.default_query_engine for driver in DRIVERS: - if str(driver) == driver_name: - return driver + if "%s.%s" % (driver.__module__, driver.__name__) == driver_name: + return driver() raise exception.SchedulerQueryDriverNotFound(driver_name=driver_name) diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 8b894a4e..9497a8c9 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -80,10 +80,12 @@ class QueryTestCase(test.TestCase): def test_choose_driver(self): # Test default driver ... driver = query.choose_driver() - self.assertEquals(str(driver), 'nova.scheduler.query.AllHostsQuery') + self.assertEquals(driver._full_name(), + 'nova.scheduler.query.AllHostsQuery') # Test valid driver ... driver = query.choose_driver('nova.scheduler.query.FlavorQuery') - self.assertEquals(str(driver), 'nova.scheduler.query.FlavorQuery') + self.assertEquals(driver._full_name(), + 'nova.scheduler.query.FlavorQuery') # Test invalid driver ... try: query.choose_driver('does not exist') @@ -165,3 +167,40 @@ class QueryTestCase(test.TestCase): just_hosts.sort() for index, host in zip([2, 4, 6, 8, 10], just_hosts): self.assertEquals('host%02d' % index, host) + + # Try some bogus input ... + raw = ['unknown command', ] + cooked = json.dumps(raw) + try: + driver.filter_hosts(self.zone_manager, cooked) + self.fail("Should give KeyError") + except KeyError, e: + pass + + self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps([]))) + self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps({}))) + self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps( + ['not', True, False, True, False] + ))) + + try: + driver.filter_hosts(self.zone_manager, json.dumps( + 'not', True, False, True, False + )) + self.fail("Should give KeyError") + except KeyError, e: + pass + + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['=', '$foo', 100] + ))) + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['=', '$.....', 100] + ))) + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['>', ['and', ['or', ['not', ['<', ['>=', ['<=', ['in', ]]]]]]]] + ))) + + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['=', {}, ['>', '$missing....foo']] + ))) From 09e06a341f7deb04c0d6997eef9bd2d16d1e801d Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 5 May 2011 15:38:45 -0400 Subject: [PATCH 074/129] added self to authors --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 1cdeeff9..8adcde4d 100644 --- a/Authors +++ b/Authors @@ -78,6 +78,7 @@ Trey Morris Tushar Patil Vasiliy Shlykov Vishvananda Ishaya +William Wolf Yoshiaki Tamura Youcef Laribi Zhixue Wu From fd94dbb2d54a8ec745fd4044dd1560d9486ecf69 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 18:09:11 -0700 Subject: [PATCH 075/129] terminology: no more plug-ins or queries. They are host filters and drivers. --- nova/scheduler/{query.py => host_filter.py} | 76 +++++++++++-------- .../{test_query.py => test_host_filter.py} | 44 ++++++----- 2 files changed, 66 insertions(+), 54 deletions(-) rename nova/scheduler/{query.py => host_filter.py} (76%) rename nova/tests/{test_query.py => test_host_filter.py} (83%) diff --git a/nova/scheduler/query.py b/nova/scheduler/host_filter.py similarity index 76% rename from nova/scheduler/query.py rename to nova/scheduler/host_filter.py index 1e294b59..aa6101c9 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/host_filter.py @@ -14,14 +14,23 @@ # under the License. """ -Query is a plug-in mechanism for requesting instance resources. -Three plug-ins are included: AllHosts, Flavor & JSON. AllHosts just +Host Filter is a driver mechanism for requesting instance resources. +Three drivers are included: AllHosts, Flavor & JSON. AllHosts just returns the full, unfiltered list of hosts. Flavor is a hard coded matching mechanism based on flavor criteria and JSON is an ad-hoc -query grammar. +filter grammar. -Note: These are hard filters. All capabilities used must be present -or the host will excluded. If you want soft filters use the weighting +Why JSON? The requests for instances may come in through the +REST interface from a user or a parent Zone. +Currently Flavors and/or InstanceTypes are used for +specifing the type of instance desired. Specific Nova users have +noted a need for a more expressive way of specifying instances. +Since we don't want to get into building full DSL this is a simple +form as an example of how this could be done. In reality, most +consumers will use the more rigid filters such as FlavorFilter. + +Note: These are hard filters. All capabilities used must be present +or the host will be excluded. If you want soft filters use the weighting mechanism which is intended for the more touchy-feely capabilities. """ @@ -32,36 +41,36 @@ from nova import flags from nova import log as logging from nova import utils -LOG = logging.getLogger('nova.scheduler.query') +LOG = logging.getLogger('nova.scheduler.host_filter') FLAGS = flags.FLAGS -flags.DEFINE_string('default_query_engine', - 'nova.scheduler.query.AllHostsQuery', - 'Which query engine to use for filtering hosts.') +flags.DEFINE_string('default_host_filter_driver', + 'nova.scheduler.host_filter.AllHostsFilter', + 'Which driver to use for filtering hosts.') -class Query(object): - """Base class for query plug-ins.""" +class HostFilter(object): + """Base class for host filter drivers.""" - def instance_type_to_query(self, instance_type): - """Convert instance_type into a query for most common use-case.""" + def instance_type_to_filter(self, instance_type): + """Convert instance_type into a filter for most common use-case.""" raise NotImplementedError() def filter_hosts(self, zone_manager, query): - """Return a list of hosts that fulfill the query.""" + """Return a list of hosts that fulfill the filter.""" raise NotImplementedError() def _full_name(self): - """module.classname of the Query object""" + """module.classname of the filter driver""" return "%s.%s" % (self.__module__, self.__class__.__name__) -class AllHostsQuery(Query): - """NOP query plug-in. Returns all hosts in ZoneManager. +class AllHostsFilter(HostFilter): + """NOP host filter driver. Returns all hosts in ZoneManager. This essentially does what the old Scheduler+Chance used to give us.""" - def instance_type_to_query(self, instance_type): + def instance_type_to_filter(self, instance_type): """Return anything to prevent base-class from raising exception.""" return (self._full_name(), instance_type) @@ -72,10 +81,10 @@ class AllHostsQuery(Query): for host, services in zone_manager.service_states.iteritems()] -class FlavorQuery(Query): - """Query plug-in hard-coded to work with flavors.""" +class FlavorFilter(HostFilter): + """HostFilter driver hard-coded to work with flavors.""" - def instance_type_to_query(self, instance_type): + def instance_type_to_filter(self, instance_type): """Use instance_type to filter hosts.""" return (self._full_name(), instance_type) @@ -119,8 +128,9 @@ class FlavorQuery(Query): #rxtx_cap = Column(Integer, nullable=False, default=0) -class JsonQuery(Query): - """Query plug-in to allow simple JSON-based grammar for selecting hosts.""" +class JsonFilter(HostFilter): + """Host Filter driver to allow simple JSON-based grammar for + selecting hosts.""" def _equals(self, args): """First term is == all the other terms.""" @@ -204,8 +214,8 @@ class JsonQuery(Query): 'and': _and, } - def instance_type_to_query(self, instance_type): - """Convert instance_type into JSON query object.""" + def instance_type_to_filter(self, instance_type): + """Convert instance_type into JSON filter object.""" required_ram = instance_type['memory_mb'] required_disk = instance_type['local_gb'] query = ['and', @@ -229,7 +239,7 @@ class JsonQuery(Query): return None return services - def _process_query(self, zone_manager, query, host, services): + def _process_filter(self, zone_manager, query, host, services): """Recursively parse the query structure.""" if len(query) == 0: return True @@ -238,7 +248,7 @@ class JsonQuery(Query): cooked_args = [] for arg in query[1:]: if isinstance(arg, list): - arg = self._process_query(zone_manager, arg, host, services) + arg = self._process_filter(zone_manager, arg, host, services) elif isinstance(arg, basestring): arg = self._parse_string(arg, host, services) if arg != None: @@ -247,11 +257,11 @@ class JsonQuery(Query): return result def filter_hosts(self, zone_manager, query): - """Return a list of hosts that can fulfill query.""" + """Return a list of hosts that can fulfill filter.""" expanded = json.loads(query) hosts = [] for host, services in zone_manager.service_states.iteritems(): - r = self._process_query(zone_manager, expanded, host, services) + r = self._process_filter(zone_manager, expanded, host, services) if isinstance(r, list): r = True in r if r: @@ -259,7 +269,7 @@ class JsonQuery(Query): return hosts -DRIVERS = [AllHostsQuery, FlavorQuery, JsonQuery] +DRIVERS = [AllHostsFilter, FlavorFilter, JsonFilter] def choose_driver(driver_name=None): @@ -267,10 +277,10 @@ def choose_driver(driver_name=None): to have an authoritative list of what is permissible. This function checks the driver name against a predefined set of acceptable drivers.""" - + if not driver_name: - driver_name = FLAGS.default_query_engine + driver_name = FLAGS.default_host_filter_driver for driver in DRIVERS: if "%s.%s" % (driver.__module__, driver.__name__) == driver_name: return driver() - raise exception.SchedulerQueryDriverNotFound(driver_name=driver_name) + raise exception.SchedulerHostFilterDriverNotFound(driver_name=driver_name) diff --git a/nova/tests/test_query.py b/nova/tests/test_host_filter.py similarity index 83% rename from nova/tests/test_query.py rename to nova/tests/test_host_filter.py index 9497a8c9..31e40ae1 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_host_filter.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. """ -Tests For Scheduler Query Drivers +Tests For Scheduler Host Filter Drivers. """ import json @@ -21,7 +21,7 @@ import json from nova import exception from nova import flags from nova import test -from nova.scheduler import query +from nova.scheduler import host_filter FLAGS = flags.FLAGS @@ -30,8 +30,8 @@ class FakeZoneManager: pass -class QueryTestCase(test.TestCase): - """Test case for query drivers.""" +class HostFilterTestCase(test.TestCase): + """Test case for host filter drivers.""" def _host_caps(self, multiplier): # Returns host capabilities in the following way: @@ -57,8 +57,9 @@ class QueryTestCase(test.TestCase): 'host_name-label': 'xs-%s' % multiplier} def setUp(self): - self.old_flag = FLAGS.default_query_engine - FLAGS.default_query_engine = 'nova.scheduler.query.AllHostsQuery' + self.old_flag = FLAGS.default_host_filter_driver + FLAGS.default_host_filter_driver = \ + 'nova.scheduler.host_filter.AllHostsFilter' self.instance_type = dict(name='tiny', memory_mb=50, vcpus=10, @@ -75,37 +76,38 @@ class QueryTestCase(test.TestCase): self.zone_manager.service_states = states def tearDown(self): - FLAGS.default_query_engine = self.old_flag + FLAGS.default_host_filter_driver = self.old_flag def test_choose_driver(self): # Test default driver ... - driver = query.choose_driver() + driver = host_filter.choose_driver() self.assertEquals(driver._full_name(), - 'nova.scheduler.query.AllHostsQuery') + 'nova.scheduler.host_filter.AllHostsFilter') # Test valid driver ... - driver = query.choose_driver('nova.scheduler.query.FlavorQuery') + driver = host_filter.choose_driver( + 'nova.scheduler.host_filter.FlavorFilter') self.assertEquals(driver._full_name(), - 'nova.scheduler.query.FlavorQuery') + 'nova.scheduler.host_filter.FlavorFilter') # Test invalid driver ... try: - query.choose_driver('does not exist') + host_filter.choose_driver('does not exist') self.fail("Should not find driver") - except exception.SchedulerQueryDriverNotFound: + except exception.SchedulerHostFilterDriverNotFound: pass def test_all_host_driver(self): - driver = query.AllHostsQuery() - cooked = driver.instance_type_to_query(self.instance_type) + driver = host_filter.AllHostsFilter() + cooked = driver.instance_type_to_filter(self.instance_type) hosts = driver.filter_hosts(self.zone_manager, cooked) self.assertEquals(10, len(hosts)) for host, capabilities in hosts: self.assertTrue(host.startswith('host')) def test_flavor_driver(self): - driver = query.FlavorQuery() + driver = host_filter.FlavorFilter() # filter all hosts that can support 50 ram and 500 disk - name, cooked = driver.instance_type_to_query(self.instance_type) - self.assertEquals('nova.scheduler.query.FlavorQuery', name) + name, cooked = driver.instance_type_to_filter(self.instance_type) + self.assertEquals('nova.scheduler.host_filter.FlavorFilter', name) hosts = driver.filter_hosts(self.zone_manager, cooked) self.assertEquals(6, len(hosts)) just_hosts = [host for host, caps in hosts] @@ -114,10 +116,10 @@ class QueryTestCase(test.TestCase): self.assertEquals('host10', just_hosts[5]) def test_json_driver(self): - driver = query.JsonQuery() + driver = host_filter.JsonFilter() # filter all hosts that can support 50 ram and 500 disk - name, cooked = driver.instance_type_to_query(self.instance_type) - self.assertEquals('nova.scheduler.query.JsonQuery', name) + name, cooked = driver.instance_type_to_filter(self.instance_type) + self.assertEquals('nova.scheduler.host_filter.JsonFilter', name) hosts = driver.filter_hosts(self.zone_manager, cooked) self.assertEquals(6, len(hosts)) just_hosts = [host for host, caps in hosts] From f8d1ccd787b96b60ad887fbf8696258924b773fe Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 6 May 2011 06:50:48 +0400 Subject: [PATCH 076/129] Added myself to Authors file. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 1cdeeff9..8a1571a0 100644 --- a/Authors +++ b/Authors @@ -80,4 +80,5 @@ Vasiliy Shlykov Vishvananda Ishaya Yoshiaki Tamura Youcef Laribi +Yuriy Taraday Zhixue Wu From bd94819705ebb456f01cdaf6e85f8a61a28647c8 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 5 May 2011 22:44:08 -0700 Subject: [PATCH 077/129] Improved error notification in network create --- bin/nova-manage | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 2f6af6e2..51a77b0e 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -523,8 +523,10 @@ class NetworkCommands(object): [network_size=FLAG], [vlan_start=FLAG], [vpn_start=FLAG], [fixed_range_v6=FLAG]""" if not fixed_range: - raise TypeError(_('Fixed range in the form of 10.0.0.0/8 is ' - 'required to create networks.')) + msg = _('Fixed range in the form of 10.0.0.0/8 is ' + 'required to create networks.') + print msg + raise TypeError(msg) if not num_networks: num_networks = FLAGS.num_networks if not network_size: @@ -536,14 +538,19 @@ class NetworkCommands(object): if not fixed_range_v6: fixed_range_v6 = FLAGS.fixed_range_v6 net_manager = utils.import_object(FLAGS.network_manager) - net_manager.create_networks(context.get_admin_context(), - cidr=fixed_range, - num_networks=int(num_networks), - network_size=int(network_size), - vlan_start=int(vlan_start), - vpn_start=int(vpn_start), - cidr_v6=fixed_range_v6, - label=label) + try: + net_manager.create_networks(context.get_admin_context(), + cidr=fixed_range, + num_networks=int(num_networks), + network_size=int(network_size), + vlan_start=int(vlan_start), + vpn_start=int(vpn_start), + cidr_v6=fixed_range_v6, + label=label) + except ValueError, e: + print e + raise e + def list(self): """List all created networks""" From d53665700d223ac6fe4aae2f524ab81d4057b547 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 5 May 2011 23:23:09 -0700 Subject: [PATCH 078/129] pep8 fix --- bin/nova-manage | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 51a77b0e..ccd2d7ed 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -550,7 +550,6 @@ class NetworkCommands(object): except ValueError, e: print e raise e - def list(self): """List all created networks""" From 3115f947f27eab5db149e186af878d50e9452a7d Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 5 May 2011 23:25:15 -0700 Subject: [PATCH 079/129] spacing fix --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index ccd2d7ed..bc5df148 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -524,7 +524,7 @@ class NetworkCommands(object): [vpn_start=FLAG], [fixed_range_v6=FLAG]""" if not fixed_range: msg = _('Fixed range in the form of 10.0.0.0/8 is ' - 'required to create networks.') + 'required to create networks.') print msg raise TypeError(msg) if not num_networks: From 987b90be1e0cf0c541f27d6c63d2157cd29491fc Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 6 May 2011 10:02:21 -0400 Subject: [PATCH 080/129] Explicitly casted a str to a str to please pylint --- nova/tests/test_virt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 1311ba36..d743f94f 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -642,7 +642,7 @@ class LibvirtConnTestCase(test.TestCase): try: conn.spawn(instance, network_info) except Exception, e: - count = (0 <= e.message.find('Unexpected method call')) + count = (0 <= str(e.message).find('Unexpected method call')) self.assertTrue(count) From 4a89cfc4898cd189ee65a13787d124bbf67accfc Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 6 May 2011 09:06:46 -0700 Subject: [PATCH 081/129] revised file docs --- nova/scheduler/host_filter.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index aa6101c9..ed8e65c7 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -29,9 +29,11 @@ Since we don't want to get into building full DSL this is a simple form as an example of how this could be done. In reality, most consumers will use the more rigid filters such as FlavorFilter. -Note: These are hard filters. All capabilities used must be present -or the host will be excluded. If you want soft filters use the weighting -mechanism which is intended for the more touchy-feely capabilities. +Note: These are "required" capability filters. These capabilities +used must be present or the host will be excluded. The hosts +returned are then weighed by the Weighted Scheduler. Weights +can take the more esoteric factors into consideration (such as +server affinity and customer separation). """ import json From 96cb4fae342cf4ef618e85bcb3a5070efd9df71e Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 6 May 2011 11:04:00 -0700 Subject: [PATCH 082/129] tests pass again --- nova/tests/test_compute.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 39311079..55e7ae0c 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -21,6 +21,7 @@ Tests For Compute import datetime import mox +import stubout from nova import compute from nova import context @@ -52,6 +53,10 @@ class FakeTime(object): self.counter += t +def nop_report_driver_status(self): + pass + + class ComputeTestCase(test.TestCase): """Test case for compute""" def setUp(self): @@ -649,6 +654,10 @@ class ComputeTestCase(test.TestCase): def test_run_kill_vm(self): """Detect when a vm is terminated behind the scenes""" + self.stubs = stubout.StubOutForTesting() + self.stubs.Set(compute_manager.ComputeManager, + '_report_driver_status', nop_report_driver_status) + instance_id = self._create_instance() self.compute.run_instance(self.context, instance_id) From 26e50116b22de3e2eedf5abd4e355170a5adadda Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Fri, 6 May 2011 17:37:35 -0400 Subject: [PATCH 083/129] convert quota table to key-value --- bin/nova-manage | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 2f6af6e2..c1144e3a 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -397,11 +397,10 @@ class ProjectCommands(object): arguments: project_id [key] [value]""" ctxt = context.get_admin_context() if key: - quo = {'project_id': project_id, key: value} try: - db.quota_update(ctxt, project_id, quo) + db.quota_update(ctxt, project_id, key, value) except exception.NotFound: - db.quota_create(ctxt, quo) + db.quota_create(ctxt, project_id, key, value) project_quota = quota.get_quota(ctxt, project_id) for key, value in project_quota.iteritems(): print '%s: %s' % (key, value) From dc3fcffb182ef659b08503af19f640de6f3a571b Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 9 May 2011 08:23:25 -0700 Subject: [PATCH 084/129] basic test working --- nova/tests/test_xenapi.py | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 375480a2..756a289b 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -17,6 +17,7 @@ """Test suite for XenAPI.""" import functools +import json import os import re import stubout @@ -665,3 +666,42 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_VHD self.fake_instance.kernel_id = None self.assert_disk_type(vm_utils.ImageType.DISK_VHD) + + +class FakeXenApi(object): + """Fake XenApi for testing HostState.""" + + class FakeSR(object): + def get_record(self, ref): + return {'virtual_allocation':10000, + 'physical_utilisation':20000} + + SR = FakeSR() + + +class FakeSession(object): + """Fake Session class for HostState testing.""" + + def async_call_plugin(self, *args): + return None + + def wait_for_task(self, *args): + return json.dumps({}) + + def get_xenapi(self): + return FakeXenApi() + + +class HostStateTestCase(test.TestCase): + """Tests HostState, which holds metrics from XenServer that get + reported back to the Schedulers.""" + + def _fake_safe_find_sr(self, session): + """None SR ref since we're ignoring it in FakeSR.""" + return None + + def test_host_state(self): + self.stubs = stubout.StubOutForTesting() + self.stubs.Set(vm_utils, 'safe_find_sr', self._fake_safe_find_sr) + host_state = xenapi_conn.HostState(FakeSession()) + From cf65ae85a9ba5eb410987c6b57772c0d61013e4e Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 9 May 2011 09:08:56 -0700 Subject: [PATCH 085/129] capabilities flattened and tests fixed --- nova/scheduler/host_filter.py | 16 ++++++++-------- nova/tests/test_host_filter.py | 26 +++++++++++++------------- nova/tests/test_xenapi.py | 12 ++++++------ 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 3e831b76..885878e1 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -96,8 +96,8 @@ class FlavorFilter(HostFilter): selected_hosts = [] for host, services in zone_manager.service_states.iteritems(): capabilities = services.get('compute', {}) - host_ram_mb = capabilities['host_memory']['free'] - disk_bytes = capabilities['disk']['available'] + host_ram_mb = capabilities['host_memory_free'] + disk_bytes = capabilities['disk_available'] if host_ram_mb >= instance_type['memory_mb'] and \ disk_bytes >= instance_type['local_gb']: selected_hosts.append((host, capabilities)) @@ -106,10 +106,10 @@ class FlavorFilter(HostFilter): #host entries (currently) are like: # {'host_name-description': 'Default install of XenServer', # 'host_hostname': 'xs-mini', -# 'host_memory': {'total': 8244539392, -# 'overhead': 184225792, -# 'free': 3868327936, -# 'free-computed': 3840843776}, +# 'host_memory_total': 8244539392, +# 'host_memory_overhead': 184225792, +# 'host_memory_free': 3868327936, +# 'host_memory_free-computed': 3840843776}, # 'host_other-config': {}, # 'host_ip_address': '192.168.1.109', # 'host_cpu_info': {}, @@ -221,8 +221,8 @@ class JsonFilter(HostFilter): required_ram = instance_type['memory_mb'] required_disk = instance_type['local_gb'] query = ['and', - ['>=', '$compute.host_memory.free', required_ram], - ['>=', '$compute.disk.available', required_disk] + ['>=', '$compute.host_memory_free', required_ram], + ['>=', '$compute.disk_available', required_disk] ] return (self._full_name(), json.dumps(query)) diff --git a/nova/tests/test_host_filter.py b/nova/tests/test_host_filter.py index 31e40ae1..c029d41e 100644 --- a/nova/tests/test_host_filter.py +++ b/nova/tests/test_host_filter.py @@ -43,16 +43,16 @@ class HostFilterTestCase(test.TestCase): # which means ... don't go above 10 hosts. return {'host_name-description': 'XenServer %s' % multiplier, 'host_hostname': 'xs-%s' % multiplier, - 'host_memory': {'total': 100, - 'overhead': 10, - 'free': 10 + multiplier * 10, - 'free-computed': 10 + multiplier * 10}, + 'host_memory_total': 100, + 'host_memory_overhead': 10, + 'host_memory_free': 10 + multiplier * 10, + 'host_memory_free-computed': 10 + multiplier * 10, 'host_other-config': {}, 'host_ip_address': '192.168.1.%d' % (100 + multiplier), 'host_cpu_info': {}, - 'disk': {'available': 100 + multiplier * 100, - 'total': 1000, - 'used': 0}, + 'disk_available': 100 + multiplier * 100, + 'disk_total': 1000, + 'disk_used': 0, 'host_uuid': 'xxx-%d' % multiplier, 'host_name-label': 'xs-%s' % multiplier} @@ -131,12 +131,12 @@ class HostFilterTestCase(test.TestCase): raw = ['or', ['and', - ['<', '$compute.host_memory.free', 30], - ['<', '$compute.disk.available', 300] + ['<', '$compute.host_memory_free', 30], + ['<', '$compute.disk_available', 300] ], ['and', - ['>', '$compute.host_memory.free', 70], - ['>', '$compute.disk.available', 700] + ['>', '$compute.host_memory_free', 70], + ['>', '$compute.disk_available', 700] ] ] cooked = json.dumps(raw) @@ -149,7 +149,7 @@ class HostFilterTestCase(test.TestCase): self.assertEquals('host%02d' % index, host) raw = ['not', - ['=', '$compute.host_memory.free', 30], + ['=', '$compute.host_memory_free', 30], ] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) @@ -160,7 +160,7 @@ class HostFilterTestCase(test.TestCase): for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts): self.assertEquals('host%02d' % index, host) - raw = ['in', '$compute.host_memory.free', 20, 40, 60, 80, 100] + raw = ['in', '$compute.host_memory_free', 20, 40, 60, 80, 100] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 0f1b2aa4..67829157 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -709,9 +709,9 @@ class HostStateTestCase(test.TestCase): self.stubs.Set(vm_utils, 'safe_find_sr', self._fake_safe_find_sr) host_state = xenapi_conn.HostState(FakeSession()) stats = host_state._stats - self.assertEquals('disk_total', 10000) - self.assertEquals('disk_used', 20000) - self.assertEquals('host_memory_total', 10) - self.assertEquals('host_memory_overhead', 20) - self.assertEquals('host_memory_free', 30) - self.assertEquals('host_memory_free-computed', 40) + self.assertEquals(stats['disk_total'], 10000) + self.assertEquals(stats['disk_used'], 20000) + self.assertEquals(stats['host_memory_total'], 10) + self.assertEquals(stats['host_memory_overhead'], 20) + self.assertEquals(stats['host_memory_free'], 30) + self.assertEquals(stats['host_memory_free-computed'], 40) From b1aa6e3c712dc84b58de21d1b823d2ca6c8815be Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 9 May 2011 09:10:22 -0700 Subject: [PATCH 086/129] pep8 --- nova/tests/test_xenapi.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 67829157..6dbd1aee 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -673,24 +673,24 @@ class FakeXenApi(object): class FakeSR(object): def get_record(self, ref): - return {'virtual_allocation':10000, - 'physical_utilisation':20000} + return {'virtual_allocation': 10000, + 'physical_utilisation': 20000} SR = FakeSR() class FakeSession(object): """Fake Session class for HostState testing.""" - + def async_call_plugin(self, *args): return None def wait_for_task(self, *args): - vm = {'total':10, - 'overhead':20, - 'free':30, - 'free-computed':40} - return json.dumps({'host_memory':vm}) + vm = {'total': 10, + 'overhead': 20, + 'free': 30, + 'free-computed': 40} + return json.dumps({'host_memory': vm}) def get_xenapi(self): return FakeXenApi() From f25040fe825a3c28831a4a27b78ced02ccaf44cc Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 9 May 2011 12:57:56 -0700 Subject: [PATCH 087/129] unified underscore/dash issue --- nova/scheduler/host_filter.py | 2 +- nova/tests/test_xenapi.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 885878e1..483f3225 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -109,7 +109,7 @@ class FlavorFilter(HostFilter): # 'host_memory_total': 8244539392, # 'host_memory_overhead': 184225792, # 'host_memory_free': 3868327936, -# 'host_memory_free-computed': 3840843776}, +# 'host_memory_free_computed': 3840843776}, # 'host_other-config': {}, # 'host_ip_address': '192.168.1.109', # 'host_cpu_info': {}, diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 6dbd1aee..6072f545 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -714,4 +714,4 @@ class HostStateTestCase(test.TestCase): self.assertEquals(stats['host_memory_total'], 10) self.assertEquals(stats['host_memory_overhead'], 20) self.assertEquals(stats['host_memory_free'], 30) - self.assertEquals(stats['host_memory_free-computed'], 40) + self.assertEquals(stats['host_memory_free_computed'], 40) From 8f003df06d75f5bad671efebcfc86619fefc6770 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 9 May 2011 16:52:52 -0500 Subject: [PATCH 088/129] Better message format description --- nova/notifier/__init__.py | 40 +++++++++++++++++++++++++++++--- nova/notifier/rabbit_notifier.py | 6 ++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index 8053b8a0..e6a4a016 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -13,12 +13,46 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime +import json + from nova import flags from nova import utils FLAGS = flags.FLAGS -def notify(event_name, model): - """Sends a notification using the specified driver""" +flags.DEFINE_string('default_notification_level', 'info', + 'Default notification level for outgoing notifications') + +WARN = 'WARN' +INFO = 'INFO' +ERROR = 'ERROR' +CRITICAL = 'CRITICAL' +DEBUG = 'DEBUG' + +log_levels = (DEBUG, WARN, INFO, ERROR, CRITICAL) + +def notify(event_name, publisher_id, event_type, priority, payload): + """ + Sends a notification using the specified driver + + Message format is as follows: + + publisher_id - the source worker_type.host of the message + timestamp - the GMT timestamp the notification was sent at + event_type - the literal type of event (ex. Instance Creation) + priority - patterned after the enumeration of Python logging levels in + the set (DEBUG, WARN, INFO, ERROR, CRITICAL) + payload - A python dictionary of attributes + + The payload will be constructed as a dictionary of the above attributes, + and converted into a JSON dump, which will then be sent via the transport + mechanism defined by the driver. + """ driver = utils.import_class(FLAGS.notification_driver)() - driver.notify(event_name, model) + message = dict(publisher_id=publisher_id, + event_type=event_type, + priority=priority, + payload=payload, + time=datetime.datetime.utcnow()) + driver.notify(json.dumps(message)) diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index 33cf0656..e4bd8539 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -25,13 +25,13 @@ FLAGS = flags.FLAGS flags.DEFINE_string('notification_topic', 'notifications', 'RabbitMQ topic used for Nova notifications') + class RabbitNotifier(object): """Sends notifications to a specific RabbitMQ server and topic""" pass - def notify(self, event_name, model): + def notify(self, payload): """Sends a notification to the RabbitMQ""" context = nova.context.get_admin_context() topic = FLAGS.notification_topic - msg = { 'event_name': event_name, 'model': model.__dict__ } - rpc.cast(context, topic, json.dumps(msg)) + rpc.cast(context, topic, msg) From 50acab28f279c6266711cf4ef81b66ad88c48d62 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Mon, 9 May 2011 22:36:01 -0500 Subject: [PATCH 089/129] Added GitPython to [install_dir]/tools/pip-requires. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 60e1d2da..d7f70f41 100644 --- a/Authors +++ b/Authors @@ -44,6 +44,7 @@ Josh Kearney Josh Kleinpeter Joshua McKenty Justin Santa Barbara +Justin Shepherd Kei Masumoto Ken Pepple Kevin Bringard From c5d77455e93bf5e8d51e023d342a654ea0d13882 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 10 May 2011 18:55:07 +0000 Subject: [PATCH 090/129] remove stubbing of XenAPISession.wait_for_task for xenapi tests as it doesn't need to be faked. Also removed duplicate code that stubbed xenapi_conn._parse_xmlrpc_value. --- nova/tests/xenapi/stubs.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 205f6c90..6db06144 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -28,29 +28,6 @@ def stubout_instance_snapshot(stubs): @classmethod def fake_fetch_image(cls, session, instance_id, image, user, project, type): - # Stubout wait_for_task - def fake_wait_for_task(self, task, id): - class FakeEvent: - - def send(self, value): - self.rv = value - - def wait(self): - return self.rv - - done = FakeEvent() - self._poll_task(id, task, done) - rv = done.wait() - return rv - - def fake_loop(self): - pass - - stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task', - fake_wait_for_task) - - stubs.Set(xenapi_conn.XenAPISession, '_stop_loop', fake_loop) - from nova.virt.xenapi.fake import create_vdi name_label = "instance-%s" % instance_id #TODO: create fake SR record @@ -63,11 +40,6 @@ def stubout_instance_snapshot(stubs): stubs.Set(vm_utils.VMHelper, 'fetch_image', fake_fetch_image) - def fake_parse_xmlrpc_value(val): - return val - - stubs.Set(xenapi_conn, '_parse_xmlrpc_value', fake_parse_xmlrpc_value) - def fake_wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, original_parent_uuid): from nova.virt.xenapi.fake import create_vdi From a01836f6a43216637c7592f1c73b00b5095725d0 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 10 May 2011 14:40:28 -0500 Subject: [PATCH 091/129] Test --- nova/tests/test_compute.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 39311079..a3513242 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -329,6 +329,32 @@ class ComputeTestCase(test.TestCase): self.compute.terminate_instance(self.context, instance_id) + def test_finish_resize(self): + """Contrived test to ensure finish_resize doesn't raise anything""" + + def fake(*args, **kwargs): pass + + self.stubs.Set(self.compute.driver, 'finish_resize', fake) + self.stubs.Set(self.compute.driver, 'finish_resize', fake) + context = self.context.elevated() + instance_id = self._create_instance() + self.compute.prep_resize(context, instance_id, 1) + migration_ref = db.migration_get_by_instance_and_status(context, + instance_id, 'pre-migrating') + try: + self.compute.finish_resize(context, instance_id, + int(migration_ref['id']), {}) + except KeyError, e: + # Only catch key errors. We want other reasons for the test to + # fail to actually error out so we don't obscure anything + self.fail() + + self.compute.terminate_instance(self.context, instance_id) + + def test_resize_instance(self): + """Ensure instance can be migrated/resized""" + instance_id = self._create_instance() + def test_resize_instance(self): """Ensure instance can be migrated/resized""" instance_id = self._create_instance() From 25a7687cb99fe47b7ca9e3fe6428e23dce677163 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 10 May 2011 14:53:03 -0500 Subject: [PATCH 092/129] Pep8 --- nova/tests/test_compute.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index a3513242..9926e1ca 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -332,7 +332,8 @@ class ComputeTestCase(test.TestCase): def test_finish_resize(self): """Contrived test to ensure finish_resize doesn't raise anything""" - def fake(*args, **kwargs): pass + def fake(*args, **kwargs): + pass self.stubs.Set(self.compute.driver, 'finish_resize', fake) self.stubs.Set(self.compute.driver, 'finish_resize', fake) @@ -350,10 +351,10 @@ class ComputeTestCase(test.TestCase): self.fail() self.compute.terminate_instance(self.context, instance_id) - - def test_resize_instance(self): - """Ensure instance can be migrated/resized""" - instance_id = self._create_instance() + + def test_resize_instance(self): + """Ensure instance can be migrated/resized""" + instance_id = self._create_instance() def test_resize_instance(self): """Ensure instance can be migrated/resized""" From 84e1444cd36fe91999798fc663ed3a6e5ea11905 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 10 May 2011 14:57:44 -0500 Subject: [PATCH 093/129] Whoops --- nova/tests/test_compute.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 9926e1ca..1b0e66be 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -352,10 +352,6 @@ class ComputeTestCase(test.TestCase): self.compute.terminate_instance(self.context, instance_id) - def test_resize_instance(self): - """Ensure instance can be migrated/resized""" - instance_id = self._create_instance() - def test_resize_instance(self): """Ensure instance can be migrated/resized""" instance_id = self._create_instance() From 66ca16b006e30d0437caca69350d281d0bc60f89 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 10 May 2011 15:42:00 -0500 Subject: [PATCH 094/129] Add example --- nova/notifier/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index e6a4a016..aacbf8ac 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -48,11 +48,20 @@ def notify(event_name, publisher_id, event_type, priority, payload): The payload will be constructed as a dictionary of the above attributes, and converted into a JSON dump, which will then be sent via the transport mechanism defined by the driver. + + Message example: + + { 'publisher_id': 'compute.host1', + 'timestamp': '2011-05-09 22:00:14.621831', + 'priority': 'WARN', + 'event_type': 'compute.create_instance', + 'payload': {'instance_id': 12, ... }} + """ driver = utils.import_class(FLAGS.notification_driver)() message = dict(publisher_id=publisher_id, event_type=event_type, priority=priority, payload=payload, - time=datetime.datetime.utcnow()) + time=str(datetime.datetime.utcnow())) driver.notify(json.dumps(message)) From ab016fa19bcf3a781606e41e6c50b0ee60fd20e4 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 10 May 2011 16:40:47 -0500 Subject: [PATCH 095/129] Better tests --- nova/notifier/__init__.py | 5 ++++ nova/notifier/rabbit_notifier.py | 2 +- nova/tests/test_notifier.py | 40 ++++++++++++++++++++++++++++++-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index aacbf8ac..942c1a1a 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -32,6 +32,9 @@ DEBUG = 'DEBUG' log_levels = (DEBUG, WARN, INFO, ERROR, CRITICAL) +class BadPriorityException(Exception): + pass + def notify(event_name, publisher_id, event_type, priority, payload): """ Sends a notification using the specified driver @@ -58,6 +61,8 @@ def notify(event_name, publisher_id, event_type, priority, payload): 'payload': {'instance_id': 12, ... }} """ + if priority not in log_levels: + raise BadPriorityException('%s not in valid priorities' % priority) driver = utils.import_class(FLAGS.notification_driver)() message = dict(publisher_id=publisher_id, event_type=event_type, diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index e4bd8539..1d62005a 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -34,4 +34,4 @@ class RabbitNotifier(object): """Sends a notification to the RabbitMQ""" context = nova.context.get_admin_context() topic = FLAGS.notification_topic - rpc.cast(context, topic, msg) + rpc.cast(context, topic, payload) diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 4d6289e6..396ce13b 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json + import nova from nova import flags @@ -42,9 +44,27 @@ class NotifierTestCase(test.TestCase): class Mock(object): pass - notifier.notify('derp', Mock()) + nova.notifier.notify('event_name', 'publisher_id', 'event_type', + nova.notifier.WARN, dict(a=3)) self.assertEqual(self.notify_called, True) + def test_verify_message_format(self): + """A test to ensure changing the message format is prohibitively + annoying""" + def message_assert(cls, blob): + message = json.loads(blob) + fields = [ ('publisher_id', 'publisher_id'), + ('event_type', 'event_type'), + ('priority', 'WARN'), + ('payload', dict(a=3))] + for k, v in fields: + self.assertEqual(message[k], v) + + self.stubs.Set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', + message_assert) + nova.notifier.notify('event_name', 'publisher_id', 'event_type', + nova.notifier.WARN, dict(a=3)) + def test_send_rabbit_notification(self): self.stubs.Set(nova.flags.FLAGS, 'notification_driver', 'nova.notifier.rabbit_notifier.RabbitNotifier') @@ -55,6 +75,22 @@ class NotifierTestCase(test.TestCase): class Mock(object): pass self.stubs.Set(nova.rpc, 'cast', mock_cast) - notifier.notify('derp', Mock()) + nova.notifier.notify('event_name', 'publisher_id', 'event_type', + nova.notifier.WARN, dict(a=3)) self.assertEqual(self.mock_cast, True) + + def test_invalid_priority(self): + self.stubs.Set(nova.flags.FLAGS, 'notification_driver', + 'nova.notifier.rabbit_notifier.RabbitNotifier') + self.mock_cast = False + def mock_cast(cls, *args): + pass + + class Mock(object): + pass + + self.stubs.Set(nova.rpc, 'cast', mock_cast) + self.assertRaises(nova.notifier.BadPriorityException, + nova.notifier.notify, 'event_name', 'publisher_id', + 'event_type', 'not a priority', dict(a=3)) From de784972a12c588f074d20bff12d82c9f34c9f5c Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Tue, 10 May 2011 23:29:16 +0000 Subject: [PATCH 096/129] Add priority based queues to notifications. Remove duplicate json encoding in notifier (rpc.cast does encoding... ) make no_op_notifier match rabbit one for signature on notify() --- nova/notifier/__init__.py | 5 ++--- nova/notifier/no_op_notifier.py | 2 +- nova/notifier/rabbit_notifier.py | 7 ++++--- nova/tests/test_notifier.py | 29 +++++++++++++++++++++++------ 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index 942c1a1a..6429ea96 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -14,14 +14,13 @@ # under the License. import datetime -import json from nova import flags from nova import utils FLAGS = flags.FLAGS -flags.DEFINE_string('default_notification_level', 'info', +flags.DEFINE_string('default_notification_level', 'INFO', 'Default notification level for outgoing notifications') WARN = 'WARN' @@ -69,4 +68,4 @@ def notify(event_name, publisher_id, event_type, priority, payload): priority=priority, payload=payload, time=str(datetime.datetime.utcnow())) - driver.notify(json.dumps(message)) + driver.notify(message) diff --git a/nova/notifier/no_op_notifier.py b/nova/notifier/no_op_notifier.py index 3fefe6f8..f425f06e 100644 --- a/nova/notifier/no_op_notifier.py +++ b/nova/notifier/no_op_notifier.py @@ -14,6 +14,6 @@ # under the License. class NoopNotifier(object): - def notify(self, event_name, model): + def notify(self, payload): """Notifies the recipient of the desired event given the model""" pass diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index 1d62005a..4b653869 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import json import nova.context @@ -28,10 +27,12 @@ flags.DEFINE_string('notification_topic', 'notifications', class RabbitNotifier(object): """Sends notifications to a specific RabbitMQ server and topic""" - pass def notify(self, payload): """Sends a notification to the RabbitMQ""" context = nova.context.get_admin_context() - topic = FLAGS.notification_topic + priority = payload.get('priority', + FLAGS.default_notification_level) + priority = priority.lower() + topic = '%s.%s' % (FLAGS.notification_topic, priority) rpc.cast(context, topic, payload) diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 396ce13b..8fc43d9d 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -13,13 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. -import json - import nova +from nova import context from nova import flags +from nova import rpc from nova import notifier from nova.notifier import no_op_notifier +from nova.notifier import rabbit_notifier from nova import test import stubout @@ -51,8 +52,7 @@ class NotifierTestCase(test.TestCase): def test_verify_message_format(self): """A test to ensure changing the message format is prohibitively annoying""" - def message_assert(cls, blob): - message = json.loads(blob) + def message_assert(cls, message): fields = [ ('publisher_id', 'publisher_id'), ('event_type', 'event_type'), ('priority', 'WARN'), @@ -71,7 +71,7 @@ class NotifierTestCase(test.TestCase): self.mock_cast = False def mock_cast(cls, *args): self.mock_cast = True - + class Mock(object): pass self.stubs.Set(nova.rpc, 'cast', mock_cast) @@ -86,7 +86,7 @@ class NotifierTestCase(test.TestCase): self.mock_cast = False def mock_cast(cls, *args): pass - + class Mock(object): pass @@ -94,3 +94,20 @@ class NotifierTestCase(test.TestCase): self.assertRaises(nova.notifier.BadPriorityException, nova.notifier.notify, 'event_name', 'publisher_id', 'event_type', 'not a priority', dict(a=3)) + + def test_rabbit_priority_queue(self): + self.stubs.Set(nova.flags.FLAGS, 'notification_driver', + 'nova.notifier.rabbit_notifier.RabbitNotifier') + self.stubs.Set(nova.flags.FLAGS, 'notification_topic', + 'testnotify') + + self.test_topic = None + + def mock_cast(context, topic, msg): + self.test_topic = topic + + self.stubs.Set(nova.rpc, 'cast', mock_cast) + nova.notifier.notify('event_name', 'publisher_id', + 'event_type', 'DEBUG', dict(a=3)) + self.assertEqual(self.test_topic, 'testnotify.debug') + From 6bbc8ca82a41e9a721cd26f8161de1a7845e5351 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Tue, 10 May 2011 23:57:38 +0000 Subject: [PATCH 097/129] added in log_notifier for easier debugging --- nova/notifier/log_notifier.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 nova/notifier/log_notifier.py diff --git a/nova/notifier/log_notifier.py b/nova/notifier/log_notifier.py new file mode 100644 index 00000000..05126b59 --- /dev/null +++ b/nova/notifier/log_notifier.py @@ -0,0 +1,33 @@ +# Copyright 2011 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 json + +from nova import flags +from nova import log as logging + +FLAGS = flags.FLAGS + +class LogNotifier(object): + """ log notifications using nova's default logging system """ + + def notify(self, payload): + """Notifies the recipient of the desired event given the model""" + priority = payload.get('priority', + FLAGS.default_notification_level) + priority = priority.lower() + logger = logging.getLogger('nova.notification.%s' % payload['event_type']) + getattr(logger, priority)(json.dumps(payload)) + From e006af2df49eb905f2d66921f2feeaf9baaa0015 Mon Sep 17 00:00:00 2001 From: Lvov Maxim Date: Wed, 11 May 2011 11:47:38 +0400 Subject: [PATCH 098/129] changing Authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 1cdeeff9..0762fd01 100644 --- a/Authors +++ b/Authors @@ -50,6 +50,7 @@ Kevin Bringard Kevin L. Mitchell Koji Iida Lorin Hochstein +Lvov Maxim Mark Washenberger Masanori Itoh Matt Dietz From 74aff1dbd29bd872d95791300e83bb73f19257be Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 11 May 2011 06:28:07 -0700 Subject: [PATCH 099/129] First cut with tests passing --- nova/scheduler/api.py | 6 ++ nova/scheduler/zone_aware_scheduler.py | 88 ++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 nova/scheduler/zone_aware_scheduler.py diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 816ae551..d8a0025e 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -81,6 +81,12 @@ def get_zone_capabilities(context): return _call_scheduler('get_zone_capabilities', context=context) +def select(context, specs=None): + """Returns a list of hosts.""" + return _call_scheduler('select', context=context, + params={"specs": specs}) + + def update_service_capabilities(context, service_name, host, capabilities): """Send an update to all the scheduler services informing them of the capabilities of this service.""" diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py new file mode 100644 index 00000000..b849e8de --- /dev/null +++ b/nova/scheduler/zone_aware_scheduler.py @@ -0,0 +1,88 @@ +# Copyright (c) 2011 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. + +""" +The Zone Aware Scheduler is a base class Scheduler for creating instances +across zones. There are two expansion points to this class for: +1. Assigning Weights to hosts for requested instances +2. Filtering Hosts based on required instance capabilities +""" + +import operator + +from nova import log as logging +from nova.scheduler import api + +LOG = logging.getLogger('nova.scheduler.zone_aware_scheduler') + + +class ZoneAwareScheduler(object): + """Base class for creating Zone Aware Schedulers.""" + + def _call_zone_method(self, context, method, specs): + """Call novaclient zone method. Broken out for testing.""" + return api.call_zone_method(context, method, specs=specs) + + def select(self, context, *args, **kwargs): + """Select returns a list of weights and zone/host information + corresponding to the best hosts to service the request. Any + child zone information has been encrypted so as not to reveal + anything about the children.""" + return self._schedule(context, "compute", *args, **kwargs) + + def schedule(self, context, topic, *args, **kwargs): + """The schedule() contract requires we return the one + best-suited host for this request. + """ + res = self._schedule(context, topic, *args, **kwargs) + return res[0] + + def _schedule(self, context, topic, *args, **kwargs): + """Returns a list of hosts that meet the required specs, + ordered by their fitness. + """ + # Filter local hosts based on requirements ... + host_list = self.filter_hosts() + + # then weigh the selected hosts. + # weighted = [ { 'weight':#, 'name':host, ...}, ] + weighted = self.weight_hosts(host_list) + + # Next, tack on the best weights from the child zones ... + child_results = self._call_zone_method(context, "select", + specs=specs) + for child_zone, result in child_results: + for weighting in result: + # Remember the child_zone so we can get back to + # it later if needed. This implicitly builds a zone + # path structure. + host_dict = { + "weight": weighting["weight"], + "child_zone": child_zone, + "child_blob": weighting["blob"]} + weighted.append(host_dict) + + weighted.sort(key=operator.itemgetter('weight')) + return weighted + + def filter_hosts(self): + """Derived classes must override this method and return + a list of hosts in [?] format.""" + raise NotImplemented() + + def weigh_hosts(self, hosts): + """Derived classes must override this method and return + a lists of hosts in [?] format.""" + raise NotImplemented() From c45d6af68266ea68ab2dd1e8c37333d60dedb3ee Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 11 May 2011 10:40:54 -0500 Subject: [PATCH 100/129] Code cleanup --- nova/notifier/__init__.py | 22 +++++++++++++--------- nova/tests/test_notifier.py | 12 +++++------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index 942c1a1a..58809f17 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -15,6 +15,7 @@ import datetime import json +import uuid from nova import flags from nova import utils @@ -41,6 +42,7 @@ def notify(event_name, publisher_id, event_type, priority, payload): Message format is as follows: + message_id - a UUID representing the id for this notification publisher_id - the source worker_type.host of the message timestamp - the GMT timestamp the notification was sent at event_type - the literal type of event (ex. Instance Creation) @@ -48,23 +50,25 @@ def notify(event_name, publisher_id, event_type, priority, payload): the set (DEBUG, WARN, INFO, ERROR, CRITICAL) payload - A python dictionary of attributes - The payload will be constructed as a dictionary of the above attributes, - and converted into a JSON dump, which will then be sent via the transport - mechanism defined by the driver. + The message body will be constructed as a dictionary of the above + attributes, and converted into a JSON dump, which will then be sent + via the transport mechanism defined by the driver. Message example: - { 'publisher_id': 'compute.host1', - 'timestamp': '2011-05-09 22:00:14.621831', - 'priority': 'WARN', - 'event_type': 'compute.create_instance', - 'payload': {'instance_id': 12, ... }} + {'message_id': str(uuid.uuid4()), + 'publisher_id': 'compute.host1', + 'timestamp': datetime.datetime.utcnow(), + 'priority': 'WARN', + 'event_type': 'compute.create_instance', + 'payload': {'instance_id': 12, ... }} """ if priority not in log_levels: raise BadPriorityException('%s not in valid priorities' % priority) driver = utils.import_class(FLAGS.notification_driver)() - message = dict(publisher_id=publisher_id, + message = dict(message_id=str(uuid.uuid4()), + publisher_id=publisher_id, event_type=event_type, priority=priority, payload=payload, diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 396ce13b..640a0cb3 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -53,12 +53,13 @@ class NotifierTestCase(test.TestCase): annoying""" def message_assert(cls, blob): message = json.loads(blob) - fields = [ ('publisher_id', 'publisher_id'), - ('event_type', 'event_type'), - ('priority', 'WARN'), - ('payload', dict(a=3))] + fields = [('publisher_id', 'publisher_id'), + ('event_type', 'event_type'), + ('priority', 'WARN'), + ('payload', dict(a=3))] for k, v in fields: self.assertEqual(message[k], v) + self.assertTrue(len(message['message_id']) > 0) self.stubs.Set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', message_assert) @@ -81,9 +82,6 @@ class NotifierTestCase(test.TestCase): self.assertEqual(self.mock_cast, True) def test_invalid_priority(self): - self.stubs.Set(nova.flags.FLAGS, 'notification_driver', - 'nova.notifier.rabbit_notifier.RabbitNotifier') - self.mock_cast = False def mock_cast(cls, *args): pass From 672af33630c89a160df6628549d6e941bdb74688 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 11 May 2011 11:02:01 -0700 Subject: [PATCH 101/129] make sure proper exceptions are raised for ec2 id conversion and add tests --- bin/nova-manage | 2 +- nova/tests/test_api.py | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 2f6af6e2..a36ec86d 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -972,7 +972,7 @@ class ImageCommands(object): try: internal_id = ec2utils.ec2_id_to_id(old_image_id) image = self.image_service.show(context, internal_id) - except exception.NotFound: + except (exception.InvalidEc2Id, exception.ImageNotFound): image = self.image_service.show_by_name(context, old_image_id) return image['id'] diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index fa0e5659..97f401b8 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -28,10 +28,12 @@ import StringIO import webob from nova import context +from nova import exception from nova import test from nova.api import ec2 -from nova.api.ec2 import cloud from nova.api.ec2 import apirequest +from nova.api.ec2 import cloud +from nova.api.ec2 import ec2utils from nova.auth import manager @@ -101,6 +103,21 @@ class XmlConversionTestCase(test.TestCase): self.assertEqual(conv('-0'), 0) +class Ec2utilsTestCase(test.TestCase): + def test_ec2_id_to_id(self): + self.assertEqual(ec2utils.ec2_id_to_id('i-0000001e'), 30) + self.assertEqual(ec2utils.ec2_id_to_id('ami-1d'), 29) + + def test_bad_ec2_id(self): + self.assertRaises(exception.InvalidEc2Id, + ec2utils.ec2_id_to_id, + 'badone') + + def test_id_to_ec2_id(self): + self.assertEqual(ec2utils.id_to_ec2_id(30), 'i-0000001e') + self.assertEqual(ec2utils.id_to_ec2_id(29, 'ami-%08x'), 'ami-0000001d') + + class ApiEc2TestCase(test.TestCase): """Unit test for the cloud controller on an EC2 API""" def setUp(self): From 1fdf9f11c71c9db7250ed5822dc995b1d7e2cd0b Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 11 May 2011 13:10:40 -0500 Subject: [PATCH 102/129] Moved everything into notifier/api --- nova/notifier/__init__.py | 61 ------------------------------ nova/notifier/api.py | 75 +++++++++++++++++++++++++++++++++++++ nova/tests/test_notifier.py | 22 ++++++----- 3 files changed, 87 insertions(+), 71 deletions(-) create mode 100644 nova/notifier/api.py diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index 0d4c970d..482d54e4 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -12,64 +12,3 @@ # 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 datetime -import uuid - -from nova import flags -from nova import utils - -FLAGS = flags.FLAGS - -flags.DEFINE_string('default_notification_level', 'INFO', - 'Default notification level for outgoing notifications') - -WARN = 'WARN' -INFO = 'INFO' -ERROR = 'ERROR' -CRITICAL = 'CRITICAL' -DEBUG = 'DEBUG' - -log_levels = (DEBUG, WARN, INFO, ERROR, CRITICAL) - -class BadPriorityException(Exception): - pass - -def notify(event_name, publisher_id, event_type, priority, payload): - """ - Sends a notification using the specified driver - - Message format is as follows: - - message_id - a UUID representing the id for this notification - publisher_id - the source worker_type.host of the message - timestamp - the GMT timestamp the notification was sent at - event_type - the literal type of event (ex. Instance Creation) - priority - patterned after the enumeration of Python logging levels in - the set (DEBUG, WARN, INFO, ERROR, CRITICAL) - payload - A python dictionary of attributes - - The message body will be constructed as a dictionary of the above - attributes, and converted into a JSON dump, which will then be sent - via the transport mechanism defined by the driver. - - Message example: - - {'message_id': str(uuid.uuid4()), - 'publisher_id': 'compute.host1', - 'timestamp': datetime.datetime.utcnow(), - 'priority': 'WARN', - 'event_type': 'compute.create_instance', - 'payload': {'instance_id': 12, ... }} - - """ - if priority not in log_levels: - raise BadPriorityException('%s not in valid priorities' % priority) - driver = utils.import_class(FLAGS.notification_driver)() - message = dict(message_id=str(uuid.uuid4()), - publisher_id=publisher_id, - event_type=event_type, - priority=priority, - payload=payload, - time=str(datetime.datetime.utcnow())) - driver.notify(message) diff --git a/nova/notifier/api.py b/nova/notifier/api.py new file mode 100644 index 00000000..04da8153 --- /dev/null +++ b/nova/notifier/api.py @@ -0,0 +1,75 @@ +# Copyright 2011 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 datetime + +import datetime +import uuid + +from nova import flags +from nova import utils + +FLAGS = flags.FLAGS + +flags.DEFINE_string('default_notification_level', 'INFO', + 'Default notification level for outgoing notifications') + +WARN = 'WARN' +INFO = 'INFO' +ERROR = 'ERROR' +CRITICAL = 'CRITICAL' +DEBUG = 'DEBUG' + +log_levels = (DEBUG, WARN, INFO, ERROR, CRITICAL) + +class BadPriorityException(Exception): + pass + +def notify(event_name, publisher_id, event_type, priority, payload): + """ + Sends a notification using the specified driver + + Message format is as follows: + + message_id - a UUID representing the id for this notification + publisher_id - the source worker_type.host of the message + timestamp - the GMT timestamp the notification was sent at + event_type - the literal type of event (ex. Instance Creation) + priority - patterned after the enumeration of Python logging levels in + the set (DEBUG, WARN, INFO, ERROR, CRITICAL) + payload - A python dictionary of attributes + + The message body will be constructed as a dictionary of the above + attributes, and converted into a JSON dump, which will then be sent + via the transport mechanism defined by the driver. + + Message example: + + {'message_id': str(uuid.uuid4()), + 'publisher_id': 'compute.host1', + 'timestamp': datetime.datetime.utcnow(), + 'priority': 'WARN', + 'event_type': 'compute.create_instance', + 'payload': {'instance_id': 12, ... }} + + """ + if priority not in log_levels: + raise BadPriorityException('%s not in valid priorities' % priority) + driver = utils.import_class(FLAGS.notification_driver)() + message = dict(message_id=str(uuid.uuid4()), + publisher_id=publisher_id, + event_type=event_type, + priority=priority, + payload=payload, + timestamp=str(datetime.datetime.utcnow())) + driver.notify(message) diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index d2964c42..64ec1dec 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -18,7 +18,8 @@ import nova from nova import context from nova import flags from nova import rpc -from nova import notifier +import nova.notifier.api +from nova.notifier.api import notify from nova.notifier import no_op_notifier from nova.notifier import rabbit_notifier from nova import test @@ -45,8 +46,8 @@ class NotifierTestCase(test.TestCase): class Mock(object): pass - nova.notifier.notify('event_name', 'publisher_id', 'event_type', - nova.notifier.WARN, dict(a=3)) + notify('event_name', 'publisher_id', 'event_type', + nova.notifier.api.WARN, dict(a=3)) self.assertEqual(self.notify_called, True) def test_verify_message_format(self): @@ -60,11 +61,12 @@ class NotifierTestCase(test.TestCase): for k, v in fields: self.assertEqual(message[k], v) self.assertTrue(len(message['message_id']) > 0) + self.assertTrue(len(message['timestamp']) > 0) self.stubs.Set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', message_assert) - nova.notifier.notify('event_name', 'publisher_id', 'event_type', - nova.notifier.WARN, dict(a=3)) + notify('event_name', 'publisher_id', 'event_type', + nova.notifier.api.WARN, dict(a=3)) def test_send_rabbit_notification(self): self.stubs.Set(nova.flags.FLAGS, 'notification_driver', @@ -76,8 +78,8 @@ class NotifierTestCase(test.TestCase): class Mock(object): pass self.stubs.Set(nova.rpc, 'cast', mock_cast) - nova.notifier.notify('event_name', 'publisher_id', 'event_type', - nova.notifier.WARN, dict(a=3)) + notify('event_name', 'publisher_id', 'event_type', + nova.notifier.api.WARN, dict(a=3)) self.assertEqual(self.mock_cast, True) @@ -89,8 +91,8 @@ class NotifierTestCase(test.TestCase): pass self.stubs.Set(nova.rpc, 'cast', mock_cast) - self.assertRaises(nova.notifier.BadPriorityException, - nova.notifier.notify, 'event_name', 'publisher_id', + self.assertRaises(nova.notifier.api.BadPriorityException, + notify, 'event_name', 'publisher_id', 'event_type', 'not a priority', dict(a=3)) def test_rabbit_priority_queue(self): @@ -105,7 +107,7 @@ class NotifierTestCase(test.TestCase): self.test_topic = topic self.stubs.Set(nova.rpc, 'cast', mock_cast) - nova.notifier.notify('event_name', 'publisher_id', + notify('event_name', 'publisher_id', 'event_type', 'DEBUG', dict(a=3)) self.assertEqual(self.test_topic, 'testnotify.debug') From 91b8ac3f980f74ba0de610ce55f7444d12213f90 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 11 May 2011 11:12:31 -0700 Subject: [PATCH 103/129] start of zone_aware_scheduler test --- nova/scheduler/api.py | 39 ++++++++++++++++++++++++++ nova/scheduler/zone_aware_scheduler.py | 31 ++++++++++++-------- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index d8a0025e..55f8e0a6 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -111,6 +111,45 @@ def _process(func, zone): return func(nova, zone) +def call_zone_method(context, method, errors_to_ignore=None, *args, **kwargs): + """Returns a list of (zone, call_result) objects.""" + if not isinstance(errors_to_ignore, (list, tuple)): + # This will also handle the default None + errors_to_ignore = [errors_to_ignore] + + pool = greenpool.GreenPool() + results = [] + for zone in db.zone_get_all(context): + try: + nova = novaclient.OpenStack(zone.username, zone.password, + zone.api_url) + nova.authenticate() + except novaclient.exceptions.BadRequest, e: + url = zone.api_url + LOG.warn(_("Failed request to zone; URL=%(url)s: %(e)s") + % locals()) + #TODO (dabo) - add logic for failure counts per zone, + # with escalation after a given number of failures. + continue + zone_method = getattr(nova.zones, method) + + def _error_trap(*args, **kwargs): + try: + return zone_method(*args, **kwargs) + except Exception as e: + if type(e) in errors_to_ignore: + return None + # TODO (dabo) - want to be able to re-raise here. + # Returning a string now; raising was causing issues. + # raise e + return "ERROR", "%s" % e + + res = pool.spawn(_error_trap, *args, **kwargs) + results.append((zone, res)) + pool.waitall() + return [(zone.id, res.wait()) for zone, res in results] + + def child_zone_helper(zone_list, func): """Fire off a command to each zone in the list. The return is [novaclient return objects] from each child zone. diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index b849e8de..b85cdfe6 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -24,11 +24,12 @@ import operator from nova import log as logging from nova.scheduler import api +from nova.scheduler import driver LOG = logging.getLogger('nova.scheduler.zone_aware_scheduler') -class ZoneAwareScheduler(object): +class ZoneAwareScheduler(driver.Scheduler): """Base class for creating Zone Aware Schedulers.""" def _call_zone_method(self, context, method, specs): @@ -42,23 +43,29 @@ class ZoneAwareScheduler(object): anything about the children.""" return self._schedule(context, "compute", *args, **kwargs) - def schedule(self, context, topic, *args, **kwargs): + def schedule(self, context, topic, *args, **kwargs): """The schedule() contract requires we return the one best-suited host for this request. """ res = self._schedule(context, topic, *args, **kwargs) + # TODO(sirp): should this be a host object rather than a weight-dict? return res[0] def _schedule(self, context, topic, *args, **kwargs): """Returns a list of hosts that meet the required specs, ordered by their fitness. """ + + #TODO(sandy): extract these from args. + num_instances = 1 + specs = {} + # Filter local hosts based on requirements ... - host_list = self.filter_hosts() + host_list = self.filter_hosts(num_instances, specs) # then weigh the selected hosts. # weighted = [ { 'weight':#, 'name':host, ...}, ] - weighted = self.weight_hosts(host_list) + weighted = self.weigh_hosts(num_instances, specs, host_list) # Next, tack on the best weights from the child zones ... child_results = self._call_zone_method(context, "select", @@ -77,12 +84,12 @@ class ZoneAwareScheduler(object): weighted.sort(key=operator.itemgetter('weight')) return weighted - def filter_hosts(self): - """Derived classes must override this method and return - a list of hosts in [?] format.""" - raise NotImplemented() + def filter_hosts(self, num, specs): + """Derived classes must override this method and return + a list of hosts in [?] format.""" + raise NotImplemented() - def weigh_hosts(self, hosts): - """Derived classes must override this method and return - a lists of hosts in [?] format.""" - raise NotImplemented() + def weigh_hosts(self, num, specs, hosts): + """Derived classes must override this method and return + a lists of hosts in [?] format.""" + raise NotImplemented() From 5ad924ce18ee715f1ab3ccad99d70164a8e38768 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 11 May 2011 13:22:55 -0500 Subject: [PATCH 104/129] Pep8 stuff --- nova/notifier/api.py | 2 ++ nova/notifier/log_notifier.py | 5 +++-- nova/notifier/no_op_notifier.py | 3 +++ nova/notifier/rabbit_notifier.py | 2 +- nova/tests/test_notifier.py | 16 ++++++++++------ 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 04da8153..7090af5f 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -32,9 +32,11 @@ DEBUG = 'DEBUG' log_levels = (DEBUG, WARN, INFO, ERROR, CRITICAL) + class BadPriorityException(Exception): pass + def notify(event_name, publisher_id, event_type, priority, payload): """ Sends a notification using the specified driver diff --git a/nova/notifier/log_notifier.py b/nova/notifier/log_notifier.py index 05126b59..4f99c589 100644 --- a/nova/notifier/log_notifier.py +++ b/nova/notifier/log_notifier.py @@ -20,6 +20,7 @@ from nova import log as logging FLAGS = flags.FLAGS + class LogNotifier(object): """ log notifications using nova's default logging system """ @@ -28,6 +29,6 @@ class LogNotifier(object): priority = payload.get('priority', FLAGS.default_notification_level) priority = priority.lower() - logger = logging.getLogger('nova.notification.%s' % payload['event_type']) + logger = logging.getLogger( + 'nova.notification.%s' % payload['event_type']) getattr(logger, priority)(json.dumps(payload)) - diff --git a/nova/notifier/no_op_notifier.py b/nova/notifier/no_op_notifier.py index f425f06e..400216f3 100644 --- a/nova/notifier/no_op_notifier.py +++ b/nova/notifier/no_op_notifier.py @@ -13,7 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. + class NoopNotifier(object): + """A notifier that doesn't actually do anything. Simply a placeholder""" + def notify(self, payload): """Notifies the recipient of the desired event given the model""" pass diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index 4b653869..6f0927e9 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -21,7 +21,7 @@ from nova import rpc FLAGS = flags.FLAGS -flags.DEFINE_string('notification_topic', 'notifications', +flags.DEFINE_string('notification_topic', 'notifications', 'RabbitMQ topic used for Nova notifications') diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 64ec1dec..b9a74a76 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -26,6 +26,7 @@ from nova import test import stubout + class NotifierTestCase(test.TestCase): """Test case for notifications""" def setUp(self): @@ -38,6 +39,7 @@ class NotifierTestCase(test.TestCase): def test_send_notification(self): self.notify_called = False + def mock_notify(cls, *args): self.notify_called = True @@ -52,7 +54,8 @@ class NotifierTestCase(test.TestCase): def test_verify_message_format(self): """A test to ensure changing the message format is prohibitively - annoying""" + annoying""" + def message_assert(cls, message): fields = [('publisher_id', 'publisher_id'), ('event_type', 'event_type'), @@ -72,12 +75,14 @@ class NotifierTestCase(test.TestCase): self.stubs.Set(nova.flags.FLAGS, 'notification_driver', 'nova.notifier.rabbit_notifier.RabbitNotifier') self.mock_cast = False + def mock_cast(cls, *args): self.mock_cast = True class Mock(object): pass - self.stubs.Set(nova.rpc, 'cast', mock_cast) + + self.stubs.Set(nova.rpc, 'cast', mock_cast) notify('event_name', 'publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3)) @@ -90,8 +95,8 @@ class NotifierTestCase(test.TestCase): class Mock(object): pass - self.stubs.Set(nova.rpc, 'cast', mock_cast) - self.assertRaises(nova.notifier.api.BadPriorityException, + self.stubs.Set(nova.rpc, 'cast', mock_cast) + self.assertRaises(nova.notifier.api.BadPriorityException, notify, 'event_name', 'publisher_id', 'event_type', 'not a priority', dict(a=3)) @@ -106,8 +111,7 @@ class NotifierTestCase(test.TestCase): def mock_cast(context, topic, msg): self.test_topic = topic - self.stubs.Set(nova.rpc, 'cast', mock_cast) + self.stubs.Set(nova.rpc, 'cast', mock_cast) notify('event_name', 'publisher_id', 'event_type', 'DEBUG', dict(a=3)) self.assertEqual(self.test_topic, 'testnotify.debug') - From 0087f66d8aa4ec412265f49c512d2909fb8e5033 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 11 May 2011 11:43:58 -0700 Subject: [PATCH 105/129] NoValidHost exception test --- nova/scheduler/zone_aware_scheduler.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index b85cdfe6..8285ec57 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -49,6 +49,8 @@ class ZoneAwareScheduler(driver.Scheduler): """ res = self._schedule(context, topic, *args, **kwargs) # TODO(sirp): should this be a host object rather than a weight-dict? + if not res: + raise driver.NoValidHost(_('No hosts were available')) return res[0] def _schedule(self, context, topic, *args, **kwargs): @@ -64,7 +66,7 @@ class ZoneAwareScheduler(driver.Scheduler): host_list = self.filter_hosts(num_instances, specs) # then weigh the selected hosts. - # weighted = [ { 'weight':#, 'name':host, ...}, ] + # weighted = [{weight=weight, name=hostname}, ...] weighted = self.weigh_hosts(num_instances, specs, host_list) # Next, tack on the best weights from the child zones ... @@ -86,10 +88,10 @@ class ZoneAwareScheduler(driver.Scheduler): def filter_hosts(self, num, specs): """Derived classes must override this method and return - a list of hosts in [?] format.""" + a list of hosts in [(hostname, capability_dict)] format.""" raise NotImplemented() def weigh_hosts(self, num, specs, hosts): """Derived classes must override this method and return - a lists of hosts in [?] format.""" + a lists of hosts in [(weight, hostname)] format.""" raise NotImplemented() From e9456b0e8b356d672df2fc89b91e9851ff897097 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 11 May 2011 14:41:31 -0500 Subject: [PATCH 106/129] Redundant line --- nova/tests/test_compute.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 1b0e66be..136d7a91 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -335,7 +335,6 @@ class ComputeTestCase(test.TestCase): def fake(*args, **kwargs): pass - self.stubs.Set(self.compute.driver, 'finish_resize', fake) self.stubs.Set(self.compute.driver, 'finish_resize', fake) context = self.context.elevated() instance_id = self._create_instance() From 3e494c104a52f6a848f8ba969de9f80eb32d31e0 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 11 May 2011 12:45:22 -0700 Subject: [PATCH 107/129] messing around with the flow of create() and specs --- nova/scheduler/zone_aware_scheduler.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 8285ec57..07f86450 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -36,6 +36,24 @@ class ZoneAwareScheduler(driver.Scheduler): """Call novaclient zone method. Broken out for testing.""" return api.call_zone_method(context, method, specs=specs) + def schedule_run_instance(self, context, topic='compute', specs=None, + *args, **kwargs): + """This method is called from nova.compute.api to provision + an instance. However we need to look at the parameters being + passed in to see if this is a request to: + 1. Create a Build Plan and then provision, or + 2. Use the Build Plan information in the request parameters + to simply create the instance (either in this zone or + a child zone).""" + + if 'blob' in specs: + return self.provision_instance(context, topic, specs) + + # Create build plan and provision ... + build_plan = self.select(context, specs) + for item in build_plan: + self.provision_instance(context, topic, item) + def select(self, context, *args, **kwargs): """Select returns a list of weights and zone/host information corresponding to the best hosts to service the request. Any From 633c193baad5b515ead3033b61d8d06902cde3d1 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 12 May 2011 17:44:07 +0400 Subject: [PATCH 108/129] Added network_info into refresh_security_group_rules --- nova/tests/test_virt.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 1311ba36..874c4693 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -849,7 +849,7 @@ class IptablesFirewallTestCase(test.TestCase): self.assertEquals(len(rulesv4), 2) self.assertEquals(len(rulesv6), 0) - def multinic_iptables_test(self): + def test_multinic_iptables(self): ipv4_rules_per_network = 2 ipv6_rules_per_network = 3 networks_count = 5 @@ -869,6 +869,16 @@ class IptablesFirewallTestCase(test.TestCase): self.assertEquals(ipv6_network_rules, ipv6_rules_per_network * networks_count) + def test_do_refresh_security_group_rules(self): + instance_ref = self._create_instance_ref() + self.mox.StubOutWithMock(self.fw, + 'add_filters_for_instance', + use_mock_anything=True) + self.fw.add_filters_for_instance(instance_ref, mox.IgnoreArg()) + self.fw.instances[instance_ref['id']] = instance_ref + self.mox.ReplayAll() + self.fw.do_refresh_security_group_rules("fake") + class NWFilterTestCase(test.TestCase): def setUp(self): From 0f0caa88adfe375d7416f04ccd1b3fa315292cae Mon Sep 17 00:00:00 2001 From: John Tran Date: Thu, 12 May 2011 12:51:03 -0700 Subject: [PATCH 109/129] incorporated ImageNotFound instead of NotFound --- nova/tests/test_cloud.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 82dd14cb..af1dbfd4 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -282,7 +282,7 @@ class CloudTestCase(test.TestCase): def test_deregister_image(self): deregister_image = self.cloud.deregister_image - def fake_delete(meh, context, id): + def fake_delete(self, context, id): return None self.stubs.Set(local.LocalImageService, 'delete', fake_delete) @@ -292,11 +292,11 @@ class CloudTestCase(test.TestCase): # invalid image self.stubs.UnsetAll() - def fake_detail_empty(meh, context): + def fake_detail_empty(self, context): return [] self.stubs.Set(local.LocalImageService, 'detail', fake_detail_empty) - self.assertRaises(exception.NotFound, deregister_image, + self.assertRaises(exception.ImageNotFound, deregister_image, self.context, 'ami-bad001') def test_console_output(self): From af416b1db46e6359ee1b3b10b18ae2b2dade571c Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 12 May 2011 20:01:32 +0000 Subject: [PATCH 110/129] Add a test for parallel builds. verified this test fails before this fix and succeeds after this fix --- nova/tests/test_xenapi.py | 23 +++++++++++++++++++++++ nova/tests/xenapi/stubs.py | 9 +++++++++ 2 files changed, 32 insertions(+) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 375480a2..a4e67981 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -16,6 +16,7 @@ """Test suite for XenAPI.""" +import eventlet import functools import os import re @@ -197,6 +198,28 @@ class XenAPIVMTestCase(test.TestCase): self.context = context.RequestContext('fake', 'fake', False) self.conn = xenapi_conn.get_connection(False) + def test_parallel_builds(self): + stubs.stubout_loopingcall_delay(self.stubs) + + def _do_build(id, proj, user, *args): + values = { + 'id': id, + 'project_id': proj, + 'user_id': user, + 'image_id': 1, + 'kernel_id': 2, + 'ramdisk_id': 3, + 'instance_type_id': '3', # m1.large + 'mac_address': 'aa:bb:cc:dd:ee:ff', + 'os_type': 'linux'} + instance = db.instance_create(self.context, values) + self.conn.spawn(instance) + + gt1 = eventlet.spawn(_do_build, 1, self.project.id, self.user.id) + gt2 = eventlet.spawn(_do_build, 2, self.project.id, self.user.id) + gt1.wait() + gt2.wait() + def test_list_instances_0(self): instances = self.conn.list_instances() self.assertEquals(instances, []) diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 6db06144..f3d3d0ce 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -16,6 +16,7 @@ """Stubouts, mocks and fixtures for the test suite""" +import eventlet from nova.virt import xenapi_conn from nova.virt.xenapi import fake from nova.virt.xenapi import volume_utils @@ -115,6 +116,14 @@ def stubout_loopingcall_start(stubs): self.f(*self.args, **self.kw) stubs.Set(utils.LoopingCall, 'start', fake_start) +def stubout_loopingcall_delay(stubs): + def fake_start(self, interval, now=True): + self._running = True + eventlet.sleep(1) + self.f(*self.args, **self.kw) + # This would fail before parallel xenapi calls were fixed + assert self._running == False + stubs.Set(utils.LoopingCall, 'start', fake_start) class FakeSessionForVMTests(fake.SessionBase): """ Stubs out a XenAPISession for VM tests """ From 0f2c1a6f4c79349445871d99bb6dcccdb989d2d1 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 12 May 2011 20:07:54 -0500 Subject: [PATCH 111/129] Adding basic tests for call_zone_method --- nova/tests/test_scheduler.py | 61 +++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 968ef9d6..54b3f80f 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -912,7 +912,8 @@ class SimpleDriverTestCase(test.TestCase): class FakeZone(object): - def __init__(self, api_url, username, password): + def __init__(self, id, api_url, username, password): + self.id = id self.api_url = api_url self.username = username self.password = password @@ -920,7 +921,7 @@ class FakeZone(object): def zone_get_all(context): return [ - FakeZone('http://example.com', 'bob', 'xxx'), + FakeZone(1, 'http://example.com', 'bob', 'xxx'), ] @@ -1037,7 +1038,7 @@ class FakeNovaClient(object): class DynamicNovaClientTest(test.TestCase): def test_issue_novaclient_command_found(self): - zone = FakeZone('http://example.com', 'bob', 'xxx') + zone = FakeZone(1, 'http://example.com', 'bob', 'xxx') self.assertEquals(api._issue_novaclient_command( FakeNovaClient(FakeServerCollection()), zone, "servers", "get", 100).a, 10) @@ -1051,7 +1052,7 @@ class DynamicNovaClientTest(test.TestCase): zone, "servers", "pause", 100), None) def test_issue_novaclient_command_not_found(self): - zone = FakeZone('http://example.com', 'bob', 'xxx') + zone = FakeZone(1, 'http://example.com', 'bob', 'xxx') self.assertEquals(api._issue_novaclient_command( FakeNovaClient(FakeEmptyServerCollection()), zone, "servers", "get", 100), None) @@ -1063,3 +1064,55 @@ class DynamicNovaClientTest(test.TestCase): self.assertEquals(api._issue_novaclient_command( FakeNovaClient(FakeEmptyServerCollection()), zone, "servers", "any", "name"), None) + + +class FakeZonesProxy(object): + def do_something(*args, **kwargs): + return 42 + + def raises_exception(*args, **kwargs): + raise Exception('testing') + + +class FakeNovaClientOpenStack(object): + def __init__(self, *args, **kwargs): + self.zones = FakeZonesProxy() + + def authenticate(self): + pass + + +class CallZoneMethodTest(test.TestCase): + def setUp(self): + super(CallZoneMethodTest, self).setUp() + self.stubs = stubout.StubOutForTesting() + self.stubs.Set(db, 'zone_get_all', zone_get_all) + self.stubs.Set(novaclient, 'OpenStack', FakeNovaClientOpenStack) + + def tearDown(self): + self.stubs.UnsetAll() + super(CallZoneMethodTest, self).tearDown() + + def test_call_zone_method(self): + context = {} + method = 'do_something' + results = api.call_zone_method(context, method) + expected = [(1, 42)] + self.assertEqual(expected, results) + + def test_call_zone_method_not_present(self): + context = {} + method = 'not_present' + self.assertRaises(AttributeError, api.call_zone_method, + context, method) + + def test_call_zone_method_generates_exception(self): + context = {} + method = 'raises_exception' + results = api.call_zone_method(context, method) + + # FIXME(sirp): for now the _error_trap code is catching errors and + # converting them to a ("ERROR", "string") tuples. The code (and this + # test) should eventually handle real exceptions. + expected = [(1, ('ERROR', 'testing'))] + self.assertEqual(expected, results) From 5fa8101e567f1e1d181056354c146a065c7804b3 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 12 May 2011 18:44:22 -0700 Subject: [PATCH 112/129] pep8 --- nova/tests/test_zone_aware_scheduler.py | 119 ++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 nova/tests/test_zone_aware_scheduler.py diff --git a/nova/tests/test_zone_aware_scheduler.py b/nova/tests/test_zone_aware_scheduler.py new file mode 100644 index 00000000..fdcde34c --- /dev/null +++ b/nova/tests/test_zone_aware_scheduler.py @@ -0,0 +1,119 @@ +# Copyright 2011 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. +""" +Tests For Zone Aware Scheduler. +""" + +from nova import test +from nova.scheduler import driver +from nova.scheduler import zone_aware_scheduler +from nova.scheduler import zone_manager + + +class FakeZoneAwareScheduler(zone_aware_scheduler.ZoneAwareScheduler): + def filter_hosts(self, num, specs): + # NOTE(sirp): this is returning [(hostname, services)] + return self.zone_manager.service_states.items() + + def weigh_hosts(self, num, specs, hosts): + fake_weight = 99 + weighted = [] + for hostname, caps in hosts: + weighted.append(dict(weight=fake_weight, name=hostname)) + return weighted + + +class FakeZoneManager(zone_manager.ZoneManager): + def __init__(self): + self.service_states = { + 'host1': { + 'compute': {'ram': 1000} + }, + 'host2': { + 'compute': {'ram': 2000} + }, + 'host3': { + 'compute': {'ram': 3000} + } + } + + +class FakeEmptyZoneManager(zone_manager.ZoneManager): + def __init__(self): + self.service_states = {} + + +def fake_empty_call_zone_method(context, method, specs): + return [] + + +def fake_call_zone_method(context, method, specs): + return [ + ('zone1', [ + dict(weight=1, blob='AAAAAAA'), + dict(weight=111, blob='BBBBBBB'), + dict(weight=112, blob='CCCCCCC'), + dict(weight=113, blob='DDDDDDD'), + ]), + ('zone2', [ + dict(weight=120, blob='EEEEEEE'), + dict(weight=2, blob='FFFFFFF'), + dict(weight=122, blob='GGGGGGG'), + dict(weight=123, blob='HHHHHHH'), + ]), + ('zone3', [ + dict(weight=130, blob='IIIIIII'), + dict(weight=131, blob='JJJJJJJ'), + dict(weight=132, blob='KKKKKKK'), + dict(weight=3, blob='LLLLLLL'), + ]), + ] + + +class ZoneAwareSchedulerTestCase(test.TestCase): + """Test case for Zone Aware Scheduler.""" + + def test_zone_aware_scheduler(self): + """ + Create a nested set of FakeZones, ensure that a select call returns the + appropriate build plan. + """ + sched = FakeZoneAwareScheduler() + self.stubs.Set(sched, '_call_zone_method', fake_call_zone_method) + + zm = FakeZoneManager() + sched.set_zone_manager(zm) + + fake_context = {} + build_plan = sched.select(fake_context, {}) + + self.assertEqual(15, len(build_plan)) + + hostnames = [plan_item['name'] + for plan_item in build_plan if 'name' in plan_item] + self.assertEqual(3, len(hostnames)) + + def test_empty_zone_aware_scheduler(self): + """ + Ensure empty hosts & child_zones result in NoValidHosts exception. + """ + sched = FakeZoneAwareScheduler() + self.stubs.Set(sched, '_call_zone_method', fake_empty_call_zone_method) + + zm = FakeEmptyZoneManager() + sched.set_zone_manager(zm) + + fake_context = {} + self.assertRaises(driver.NoValidHost, sched.schedule, fake_context, {}) From fffb7787306e31dbe0fa4666f2d7f931d79d77f0 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 13 May 2011 06:12:18 -0700 Subject: [PATCH 113/129] fixup based on Lorin's feedback --- nova/scheduler/zone_aware_scheduler.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 07f86450..b3d230bd 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -36,7 +36,7 @@ class ZoneAwareScheduler(driver.Scheduler): """Call novaclient zone method. Broken out for testing.""" return api.call_zone_method(context, method, specs=specs) - def schedule_run_instance(self, context, topic='compute', specs=None, + def schedule_run_instance(self, context, topic='compute', specs={}, *args, **kwargs): """This method is called from nova.compute.api to provision an instance. However we need to look at the parameters being @@ -54,6 +54,10 @@ class ZoneAwareScheduler(driver.Scheduler): for item in build_plan: self.provision_instance(context, topic, item) + def provision_instance(context, topic, item): + """Create the requested instance in this Zone or a child zone.""" + pass + def select(self, context, *args, **kwargs): """Select returns a list of weights and zone/host information corresponding to the best hosts to service the request. Any @@ -111,5 +115,5 @@ class ZoneAwareScheduler(driver.Scheduler): def weigh_hosts(self, num, specs, hosts): """Derived classes must override this method and return - a lists of hosts in [(weight, hostname)] format.""" + a lists of hosts in [{weight, hostname}] format.""" raise NotImplemented() From fe84780dd7276a09e720d723809bd5f3cc1f7c45 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 13 May 2011 16:47:18 +0000 Subject: [PATCH 115/129] pep8 fixes --- nova/tests/xenapi/stubs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index f3d3d0ce..4833ccb0 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -116,6 +116,7 @@ def stubout_loopingcall_start(stubs): self.f(*self.args, **self.kw) stubs.Set(utils.LoopingCall, 'start', fake_start) + def stubout_loopingcall_delay(stubs): def fake_start(self, interval, now=True): self._running = True @@ -125,6 +126,7 @@ def stubout_loopingcall_delay(stubs): assert self._running == False stubs.Set(utils.LoopingCall, 'start', fake_start) + class FakeSessionForVMTests(fake.SessionBase): """ Stubs out a XenAPISession for VM tests """ def __init__(self, uri): From e6d2222f513b52618ad2bd2134f842d0f309988e Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 16 May 2011 18:14:09 +0400 Subject: [PATCH 116/129] Added response about error in nova-manage project operations --- bin/nova-manage | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index a36ec86d..155ab592 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -362,27 +362,47 @@ class ProjectCommands(object): def add(self, project_id, user_id): """Adds user to project arguments: project_id user_id""" - self.manager.add_to_project(user_id, project_id) + try: + self.manager.add_to_project(user_id, project_id) + except exception.UserNotFound, e: + print e + raise def create(self, name, project_manager, description=None): """Creates a new project arguments: name project_manager [description]""" - self.manager.create_project(name, project_manager, description) + try: + self.manager.create_project(name, project_manager, description) + except exception.UserNotFound, e: + print e + raise def modify(self, name, project_manager, description=None): """Modifies a project arguments: name project_manager [description]""" - self.manager.modify_project(name, project_manager, description) - + try: + self.manager.modify_project(name, project_manager, description) + except exception.UserNotFound, e: + print e + raise + def delete(self, name): """Deletes an existing project arguments: name""" - self.manager.delete_project(name) + try: + self.manager.delete_project(name) + except exception.ProjectNotFound, e: + print e + raise 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(user_id, project_id) + try: + rc = self.manager.get_environment_rc(user_id, project_id) + except (exception.UserNotFound, exception.ProjectNotFound), e: + print e + raise with open(filename, 'w') as f: f.write(rc) @@ -400,7 +420,7 @@ class ProjectCommands(object): quo = {'project_id': project_id, key: value} try: db.quota_update(ctxt, project_id, quo) - except exception.NotFound: + except exception.ProjectQuotaNotFound: db.quota_create(ctxt, quo) project_quota = quota.get_quota(ctxt, project_id) for key, value in project_quota.iteritems(): @@ -409,7 +429,11 @@ class ProjectCommands(object): def remove(self, project_id, user_id): """Removes user from project arguments: project_id user_id""" - self.manager.remove_from_project(user_id, project_id) + try: + self.manager.remove_from_project(user_id, project_id) + except (exception.UserNotFound, exception.ProjectNotFound), e: + print e + raise def scrub(self, project_id): """Deletes data associated with project @@ -428,6 +452,9 @@ class ProjectCommands(object): zip_file = self.manager.get_credentials(user_id, project_id) with open(filename, 'w') as f: f.write(zip_file) + except (exception.UserNotFound, exception.ProjectNotFound), e: + print e + raise except db.api.NoMoreNetworks: print _('No more networks available. If this is a new ' 'installation, you need\nto call something like this:\n\n' From 04abb75adfce476c962e0b55417e1c488dda819a Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 16 May 2011 18:17:15 +0400 Subject: [PATCH 117/129] Pep8 cleaning --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 155ab592..f1214ff3 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -385,7 +385,7 @@ class ProjectCommands(object): except exception.UserNotFound, e: print e raise - + def delete(self, name): """Deletes an existing project arguments: name""" From f7701b51229f42b2129631bd3eaa1c2442fb6eea Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 16 May 2011 14:02:56 -0400 Subject: [PATCH 119/129] Removed obsolete method and test --- nova/tests/test_cloud.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index c45bdd12..7835ded2 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -338,41 +338,6 @@ class CloudTestCase(test.TestCase): self._create_key('test') self.cloud.delete_key_pair(self.context, 'test') - def test_run_instances(self): - if FLAGS.connection_type == 'fake': - LOG.debug(_("Can't test instances without a real virtual env.")) - return - image_id = FLAGS.default_image - instance_type = FLAGS.default_instance_type - max_count = 1 - kwargs = {'image_id': image_id, - 'instance_type': instance_type, - 'max_count': max_count} - rv = self.cloud.run_instances(self.context, **kwargs) - # TODO: check for proper response - instance_id = rv['reservationSet'][0].keys()[0] - instance = rv['reservationSet'][0][instance_id][0] - LOG.debug(_("Need to watch instance %s until it's running..."), - instance['instance_id']) - while True: - greenthread.sleep(1) - info = self.cloud._get_instance(instance['instance_id']) - LOG.debug(info['state']) - if info['state'] == power_state.RUNNING: - break - self.assert_(rv) - - if FLAGS.connection_type != 'fake': - time.sleep(45) # Should use boto for polling here - for reservations in rv['reservationSet']: - # for res_id in reservations.keys(): - # LOG.debug(reservations[res_id]) - # for instance in reservations[res_id]: - for instance in reservations[reservations.keys()[0]]: - instance_id = instance['instance_id'] - LOG.debug(_("Terminating instance %s"), instance_id) - rv = self.compute.terminate_instance(instance_id) - def test_terminate_instances(self): inst1 = db.instance_create(self.context, {'reservation_id': 'a', 'image_id': 1, From 861f42f7bf7a1ec92cc5b59de754553d2587505d Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 16 May 2011 22:40:16 +0400 Subject: [PATCH 120/129] style fixing --- bin/nova-manage | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index f1214ff3..a42dabf8 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -364,8 +364,8 @@ class ProjectCommands(object): arguments: project_id user_id""" try: self.manager.add_to_project(user_id, project_id) - except exception.UserNotFound, e: - print e + except exception.UserNotFound as ex: + print ex raise def create(self, name, project_manager, description=None): @@ -373,8 +373,8 @@ class ProjectCommands(object): arguments: name project_manager [description]""" try: self.manager.create_project(name, project_manager, description) - except exception.UserNotFound, e: - print e + except exception.UserNotFound as ex: + print ex raise def modify(self, name, project_manager, description=None): @@ -382,8 +382,8 @@ class ProjectCommands(object): arguments: name project_manager [description]""" try: self.manager.modify_project(name, project_manager, description) - except exception.UserNotFound, e: - print e + except exception.UserNotFound as ex: + print ex raise def delete(self, name): @@ -391,8 +391,8 @@ class ProjectCommands(object): arguments: name""" try: self.manager.delete_project(name) - except exception.ProjectNotFound, e: - print e + except exception.ProjectNotFound as ex: + print ex raise def environment(self, project_id, user_id, filename='novarc'): @@ -400,8 +400,8 @@ class ProjectCommands(object): arguments: project_id user_id [filename='novarc]""" try: rc = self.manager.get_environment_rc(user_id, project_id) - except (exception.UserNotFound, exception.ProjectNotFound), e: - print e + except (exception.UserNotFound, exception.ProjectNotFound) as ex: + print ex raise with open(filename, 'w') as f: f.write(rc) @@ -431,8 +431,8 @@ class ProjectCommands(object): arguments: project_id user_id""" try: self.manager.remove_from_project(user_id, project_id) - except (exception.UserNotFound, exception.ProjectNotFound), e: - print e + except (exception.UserNotFound, exception.ProjectNotFound) as ex: + print ex raise def scrub(self, project_id): @@ -452,8 +452,8 @@ class ProjectCommands(object): zip_file = self.manager.get_credentials(user_id, project_id) with open(filename, 'w') as f: f.write(zip_file) - except (exception.UserNotFound, exception.ProjectNotFound), e: - print e + except (exception.UserNotFound, exception.ProjectNotFound) as ex: + print ex raise except db.api.NoMoreNetworks: print _('No more networks available. If this is a new ' From c082b0f1d1b3975145a28ff3c3ff721e1ca6b05e Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 16 May 2011 15:16:34 -0500 Subject: [PATCH 121/129] Merge prop changes --- nova/flags.py | 2 +- nova/notifier/api.py | 20 ++++++++++++-------- nova/notifier/log_notifier.py | 19 +++++++++---------- nova/notifier/no_op_notifier.py | 9 +++------ nova/notifier/rabbit_notifier.py | 19 ++++++++----------- nova/tests/test_notifier.py | 22 +++++++++++----------- 6 files changed, 44 insertions(+), 47 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index a1f7f71c..32cb6efa 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -370,7 +370,7 @@ DEFINE_string('node_availability_zone', 'nova', 'availability zone of this node') DEFINE_string('notification_driver', - 'nova.notifier.no_op_notifier.NoopNotifier', + 'nova.notifier.no_op_notifier', 'Default driver for sending notifications') DEFINE_list('memcached_servers', None, 'Memcached servers or None for in process cache.') diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 4fcfa84f..5b9b8ea2 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -37,21 +37,25 @@ class BadPriorityException(Exception): pass -def notify(event_name, publisher_id, event_type, priority, message): +def notify(publisher_id, event_type, priority, message): """ Sends a notification using the specified driver - Message format is as follows: + Notify parameters: - message_id - a UUID representing the id for this notification publisher_id - the source worker_type.host of the message - timestamp - the GMT timestamp the notification was sent at event_type - the literal type of event (ex. Instance Creation) priority - patterned after the enumeration of Python logging levels in the set (DEBUG, WARN, INFO, ERROR, CRITICAL) message - A python dictionary of attributes - The message payload will be constructed as a dictionary of the above + Outgoing message format includes the above parameters, and appends the + following: + + message_id - a UUID representing the id for this notification + timestamp - the GMT timestamp the notification was sent at + + The composite message will be constructed as a dictionary of the above attributes, which will then be sent via the transport mechanism defined by the driver. @@ -62,17 +66,17 @@ def notify(event_name, publisher_id, event_type, priority, message): 'timestamp': datetime.datetime.utcnow(), 'priority': 'WARN', 'event_type': 'compute.create_instance', - 'message': {'instance_id': 12, ... }} + 'payload': {'instance_id': 12, ... }} """ if priority not in log_levels: raise BadPriorityException( _('%s not in valid priorities' % priority)) - driver = utils.import_class(FLAGS.notification_driver)() + driver = utils.import_object(FLAGS.notification_driver) msg = dict(message_id=str(uuid.uuid4()), publisher_id=publisher_id, event_type=event_type, priority=priority, - message=message, + payload=message, timestamp=str(datetime.datetime.utcnow())) driver.notify(msg) diff --git a/nova/notifier/log_notifier.py b/nova/notifier/log_notifier.py index f072a612..a3df3172 100644 --- a/nova/notifier/log_notifier.py +++ b/nova/notifier/log_notifier.py @@ -21,14 +21,13 @@ from nova import log as logging FLAGS = flags.FLAGS -class LogNotifier(object): - """Log notifications using nova's default logging system""" +def notify(message): + """Notifies the recipient of the desired event given the model. + Log notifications using nova's default logging system""" - def notify(self, message): - """Notifies the recipient of the desired event given the model""" - priority = message.get('priority', - FLAGS.default_notification_level) - priority = priority.lower() - logger = logging.getLogger( - 'nova.notification.%s' % message['event_type']) - getattr(logger, priority)(json.dumps(message)) + priority = message.get('priority', + FLAGS.default_notification_level) + priority = priority.lower() + logger = logging.getLogger( + 'nova.notification.%s' % message['event_type']) + getattr(logger, priority)(json.dumps(message)) diff --git a/nova/notifier/no_op_notifier.py b/nova/notifier/no_op_notifier.py index f5e745f1..02971050 100644 --- a/nova/notifier/no_op_notifier.py +++ b/nova/notifier/no_op_notifier.py @@ -14,9 +14,6 @@ # under the License. -class NoopNotifier(object): - """A notifier that doesn't actually do anything. Simply a placeholder""" - - def notify(self, message): - """Notifies the recipient of the desired event given the model""" - pass +def notify(message): + """Notifies the recipient of the desired event given the model""" + pass diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index 7e2ee5f0..acab7965 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -25,14 +25,11 @@ flags.DEFINE_string('notification_topic', 'notifications', 'RabbitMQ topic used for Nova notifications') -class RabbitNotifier(object): - """Sends notifications to a specific RabbitMQ server and topic""" - - def notify(self, message): - """Sends a notification to the RabbitMQ""" - context = nova.context.get_admin_context() - priority = message.get('priority', - FLAGS.default_notification_level) - priority = priority.lower() - topic = '%s.%s' % (FLAGS.notification_topic, priority) - rpc.cast(context, topic, message) +def notify(message): + """Sends a notification to the RabbitMQ""" + context = nova.context.get_admin_context() + priority = message.get('priority', + FLAGS.default_notification_level) + priority = priority.lower() + topic = '%s.%s' % (FLAGS.notification_topic, priority) + rpc.cast(context, topic, message) diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 82c4d3f5..b6b0fcc6 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -43,12 +43,12 @@ class NotifierTestCase(test.TestCase): def mock_notify(cls, *args): self.notify_called = True - self.stubs.Set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', + self.stubs.Set(nova.notifier.no_op_notifier, 'notify', mock_notify) class Mock(object): pass - notify('event_name', 'publisher_id', 'event_type', + notify('publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3)) self.assertEqual(self.notify_called, True) @@ -56,24 +56,24 @@ class NotifierTestCase(test.TestCase): """A test to ensure changing the message format is prohibitively annoying""" - def message_assert(cls, message): + def message_assert(message): fields = [('publisher_id', 'publisher_id'), ('event_type', 'event_type'), ('priority', 'WARN'), - ('message', dict(a=3))] + ('payload', dict(a=3))] for k, v in fields: self.assertEqual(message[k], v) self.assertTrue(len(message['message_id']) > 0) self.assertTrue(len(message['timestamp']) > 0) - self.stubs.Set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', + self.stubs.Set(nova.notifier.no_op_notifier, 'notify', message_assert) - notify('event_name', 'publisher_id', 'event_type', + notify('publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3)) def test_send_rabbit_notification(self): self.stubs.Set(nova.flags.FLAGS, 'notification_driver', - 'nova.notifier.rabbit_notifier.RabbitNotifier') + 'nova.notifier.rabbit_notifier') self.mock_cast = False def mock_cast(cls, *args): @@ -83,7 +83,7 @@ class NotifierTestCase(test.TestCase): pass self.stubs.Set(nova.rpc, 'cast', mock_cast) - notify('event_name', 'publisher_id', 'event_type', + notify('publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3)) self.assertEqual(self.mock_cast, True) @@ -97,12 +97,12 @@ class NotifierTestCase(test.TestCase): self.stubs.Set(nova.rpc, 'cast', mock_cast) self.assertRaises(nova.notifier.api.BadPriorityException, - notify, 'event_name', 'publisher_id', + notify, 'publisher_id', 'event_type', 'not a priority', dict(a=3)) def test_rabbit_priority_queue(self): self.stubs.Set(nova.flags.FLAGS, 'notification_driver', - 'nova.notifier.rabbit_notifier.RabbitNotifier') + 'nova.notifier.rabbit_notifier') self.stubs.Set(nova.flags.FLAGS, 'notification_topic', 'testnotify') @@ -112,6 +112,6 @@ class NotifierTestCase(test.TestCase): self.test_topic = topic self.stubs.Set(nova.rpc, 'cast', mock_cast) - notify('event_name', 'publisher_id', + notify('publisher_id', 'event_type', 'DEBUG', dict(a=3)) self.assertEqual(self.test_topic, 'testnotify.debug') From ce2a8b71fc15ba6c868cfa388f68c18b4dfa59c1 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 16 May 2011 15:45:40 -0500 Subject: [PATCH 122/129] Conceded :-D --- nova/notifier/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 5b9b8ea2..a2231055 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -37,7 +37,7 @@ class BadPriorityException(Exception): pass -def notify(publisher_id, event_type, priority, message): +def notify(publisher_id, event_type, priority, payload): """ Sends a notification using the specified driver @@ -47,7 +47,7 @@ def notify(publisher_id, event_type, priority, message): event_type - the literal type of event (ex. Instance Creation) priority - patterned after the enumeration of Python logging levels in the set (DEBUG, WARN, INFO, ERROR, CRITICAL) - message - A python dictionary of attributes + payload - A python dictionary of attributes Outgoing message format includes the above parameters, and appends the following: @@ -77,6 +77,6 @@ def notify(publisher_id, event_type, priority, message): publisher_id=publisher_id, event_type=event_type, priority=priority, - payload=message, + payload=payload, timestamp=str(datetime.datetime.utcnow())) driver.notify(msg) From e265917b94bd76eda671c8f1c416283e82ca90d8 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 18 May 2011 12:55:17 -0500 Subject: [PATCH 124/129] Spacing changes --- nova/notifier/api.py | 1 + nova/notifier/log_notifier.py | 1 + nova/notifier/rabbit_notifier.py | 1 + 3 files changed, 3 insertions(+) diff --git a/nova/notifier/api.py b/nova/notifier/api.py index a2231055..a3e7a039 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -19,6 +19,7 @@ import uuid from nova import flags from nova import utils + FLAGS = flags.FLAGS flags.DEFINE_string('default_notification_level', 'INFO', diff --git a/nova/notifier/log_notifier.py b/nova/notifier/log_notifier.py index a3df3172..25dfc693 100644 --- a/nova/notifier/log_notifier.py +++ b/nova/notifier/log_notifier.py @@ -18,6 +18,7 @@ import json from nova import flags from nova import log as logging + FLAGS = flags.FLAGS diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index acab7965..d46670b5 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -19,6 +19,7 @@ import nova.context from nova import flags from nova import rpc + FLAGS = flags.FLAGS flags.DEFINE_string('notification_topic', 'notifications', From 2b20127fc6d49e46d3ede974a5753f9b811c62aa Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 18 May 2011 21:25:35 +0000 Subject: [PATCH 125/129] Sort list of controllers/methods before printing --- bin/stack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/stack b/bin/stack index d84a82e2..a1c6d134 100755 --- a/bin/stack +++ b/bin/stack @@ -65,7 +65,7 @@ def format_help(d): indent = MAX_INDENT - 6 out = [] - for k, v in d.iteritems(): + for k, v in sorted(d.iteritems()): if (len(k) + 6) > MAX_INDENT: out.extend([' %s' % k]) initial_indent = ' ' * (indent + 6) From 27b71e79e12f3dbb27e40fccf0128f0f214ac324 Mon Sep 17 00:00:00 2001 From: Andrey Brindeyev Date: Fri, 20 May 2011 17:57:04 +0400 Subject: [PATCH 126/129] Addressing bug #785763. Usual default for maximum number of DHCP leases in dnsmasq is 150. This prevents instances to obtain IP addresses from DHCP in case we have more than 150 in our network. Adding myself to Authors. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 546c9091..6741c81f 100644 --- a/Authors +++ b/Authors @@ -1,4 +1,5 @@ Alex Meade +Andrey Brindeyev Andy Smith Andy Southgate Anne Gentle From 7a3ecc4015d1d2198d0fa0662aecc06ba7cc5e9c Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 20 May 2011 21:21:04 +0200 Subject: [PATCH 127/129] Include data files for public key tests in the tarball. --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index e7a6e7da..4e145de7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -35,6 +35,7 @@ include nova/tests/bundle/1mb.manifest.xml include nova/tests/bundle/1mb.no_kernel_or_ramdisk.manifest.xml include nova/tests/bundle/1mb.part.0 include nova/tests/bundle/1mb.part.1 +include nova/tests/public_key/* include nova/tests/db/nova.austin.sqlite include plugins/xenapi/README include plugins/xenapi/etc/xapi.d/plugins/objectstore