From 5a9d2eb44ced0affe143e6274c9c9326f1c2d7da Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 18 Mar 2011 11:49:11 -0700 Subject: created api endpoint to allow uploading of public key --- nova/api/ec2/cloud.py | 12 ++++++++++++ nova/tests/test_cloud.py | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index cadda97db..6fe01b0e9 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -302,6 +302,18 @@ class CloudController(object): 'keyMaterial': data['private_key']} # TODO(vish): when context is no longer an object, pass it here + def import_public_key(self, context, key_name, public_key, + fingerprint=None): + LOG.audit(_("Import key %s"), key_name, context=context) + key = {} + key['user_id'] = context.user_id + key['name'] = key_name + key['public_key'] = public_key + if fingerprint: + key['fingerprint'] = fingerprint + db.key_pair_create(context, key) + return True + def delete_key_pair(self, context, key_name, **kwargs): LOG.audit(_("Delete key pair %s"), key_name, context=context) try: diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index cf8ee7eff..03b1ad2fc 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') -- cgit From 15a40f842cb8a4362fbd82e36e3df4af7ab46a84 Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 18 Mar 2011 12:17:40 -0700 Subject: cleaned up tests stubs that were accidentally checked in --- nova/tests/test_cloud.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 03b1ad2fc..3a266c996 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') -- cgit From a105fd449a0b91cde3ab86cc552705dfe50e3f6d Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 21 Mar 2011 14:35:19 -0700 Subject: if fingerprint data not provided, added logic to calculate it using the pub key. --- nova/api/ec2/cloud.py | 15 +++++++++++++-- nova/tests/public_key/dummy.fingerprint | 1 + nova/tests/public_key/dummy.pub | 1 + nova/tests/test_cloud.py | 32 +++++++++++++++++++++++++------- 4 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 nova/tests/public_key/dummy.fingerprint create mode 100644 nova/tests/public_key/dummy.pub (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 6fe01b0e9..8ec74fbe0 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -27,6 +27,8 @@ import datetime import IPy import os import urllib +import tempfile +import shutil from nova import compute from nova import context @@ -309,8 +311,17 @@ class CloudController(object): key['user_id'] = context.user_id key['name'] = key_name key['public_key'] = public_key - if fingerprint: - key['fingerprint'] = fingerprint + if fingerprint is None: + tmpdir = tempfile.mkdtemp() + pubfile = os.path.join(tmpdir, 'temp.pub') + fh = open(pubfile, 'w') + fh.write(public_key) + fh.close() + (out, err) = utils.execute('ssh-keygen', '-q', '-l', '-f', + '%s' % (pubfile)) + fingerprint = out.split(' ')[1] + shutil.rmtree(tmpdir) + key['fingerprint'] = fingerprint db.key_pair_create(context, key) return True diff --git a/nova/tests/public_key/dummy.fingerprint b/nova/tests/public_key/dummy.fingerprint new file mode 100644 index 000000000..715bca27a --- /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 000000000..d4cf2bc0d --- /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 3a266c996..c49a39ed0 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') -- cgit From 062301faf57d1e07b5068ae90c91c8c7da460e1f Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Mon, 28 Mar 2011 09:28:18 -0700 Subject: Start up nova-api service on an unused port if 0 is specified. Fixes bug 744150 --- nova/service.py | 6 ++++++ nova/tests/integrated/integrated_helpers.py | 3 ++- nova/tests/integrated/test_login.py | 1 + nova/wsgi.py | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/service.py b/nova/service.py index 47c0b96c0..e399273a0 100644 --- a/nova/service.py +++ b/nova/service.py @@ -248,6 +248,12 @@ class WsgiService(object): def wait(self): self.wsgi_app.wait() + def get_port(self, api): + for i in xrange(len(self.apis)): + if self.apis[i] == api: + return self.wsgi_app.ports[i] + return None + class ApiService(WsgiService): """Class for our nova-api service""" diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index cc7326e73..752563e89 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -141,6 +141,7 @@ class IntegratedUnitTestContext(object): self.api_service = api_service - self.auth_url = 'http://localhost:8774/v1.0' + host, port = api_service.get_port('osapi') + self.auth_url = 'http://%s:%s/v1.0' % (host, port) return api_service diff --git a/nova/tests/integrated/test_login.py b/nova/tests/integrated/test_login.py index 6b241f240..764f3326d 100644 --- a/nova/tests/integrated/test_login.py +++ b/nova/tests/integrated/test_login.py @@ -33,6 +33,7 @@ FLAGS.verbose = True class LoginTest(test.TestCase): def setUp(self): super(LoginTest, self).setUp() + self.flags(ec2_listen_port=0, osapi_listen_port=0) self.context = integrated_helpers.IntegratedUnitTestContext() self.user = self.context.test_user self.api = self.user.openstack_api diff --git a/nova/wsgi.py b/nova/wsgi.py index ba0819466..54401f998 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -61,6 +61,7 @@ class Server(object): def __init__(self, threads=1000): self.pool = eventlet.GreenPool(threads) + self.ports = [] def start(self, application, port, host='0.0.0.0', backlog=128): """Run a WSGI server with the given application.""" @@ -68,6 +69,7 @@ class Server(object): logging.audit(_("Starting %(arg0)s on %(host)s:%(port)s") % locals()) socket = eventlet.listen((host, port), backlog=backlog) self.pool.spawn_n(self._run, application, socket) + self.ports.append(socket.getsockname()) def wait(self): """Wait until all servers have completed running.""" -- cgit From 131b7da40946b12bae59ebcc8f1c3d66d0cb5cff Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 30 Mar 2011 09:04:29 -0700 Subject: Store socket_info as a dictionary rather than an array --- nova/service.py | 14 +++++++------- nova/tests/integrated/integrated_helpers.py | 2 +- nova/wsgi.py | 7 ++++--- 3 files changed, 12 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/service.py b/nova/service.py index e399273a0..1405da605 100644 --- a/nova/service.py +++ b/nova/service.py @@ -248,11 +248,9 @@ class WsgiService(object): def wait(self): self.wsgi_app.wait() - def get_port(self, api): - for i in xrange(len(self.apis)): - if self.apis[i] == api: - return self.wsgi_app.ports[i] - return None + def get_socket_info(self, api_name): + """Returns the (host, port) that an API was started on.""" + return self.wsgi_app.socket_info[api_name] class ApiService(WsgiService): @@ -331,8 +329,10 @@ def _run_wsgi(paste_config_file, apis): logging.debug(_("App Config: %(api)s\n%(config)r") % locals()) logging.info(_("Running %s API"), api) app = wsgi.load_paste_app(paste_config_file, api) - apps.append((app, getattr(FLAGS, "%s_listen_port" % api), - getattr(FLAGS, "%s_listen" % api))) + apps.append((app, + getattr(FLAGS, "%s_listen_port" % api), + getattr(FLAGS, "%s_listen" % api), + api)) if len(apps) == 0: logging.error(_("No known API applications configured in %s."), paste_config_file) diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 5169dcb2e..14b2e06c6 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -176,7 +176,7 @@ class _IntegratedTestBase(test.TestCase): self.api_service = api_service - host, port = api_service.get_port('osapi') + host, port = api_service.get_socket_info('osapi') self.auth_url = 'http://%s:%s/v1.0' % (host, port) def tearDown(self): diff --git a/nova/wsgi.py b/nova/wsgi.py index 54401f998..2487ada22 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -61,15 +61,16 @@ class Server(object): def __init__(self, threads=1000): self.pool = eventlet.GreenPool(threads) - self.ports = [] + self.socket_info = {} - def start(self, application, port, host='0.0.0.0', backlog=128): + def start(self, application, port, host='0.0.0.0', key=None, backlog=128): """Run a WSGI server with the given application.""" arg0 = sys.argv[0] logging.audit(_("Starting %(arg0)s on %(host)s:%(port)s") % locals()) socket = eventlet.listen((host, port), backlog=backlog) self.pool.spawn_n(self._run, application, socket) - self.ports.append(socket.getsockname()) + if key: + self.socket_info[key] = socket.getsockname() def wait(self): """Wait until all servers have completed running.""" -- cgit From 4ab6962fb7461573119297aa3508f7df8c6efa42 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 30 Mar 2011 09:08:36 -0700 Subject: Fixed mis-merge: OS API version still has to be v1.1 --- nova/tests/integrated/integrated_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 14b2e06c6..bc98921f0 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -177,7 +177,7 @@ class _IntegratedTestBase(test.TestCase): self.api_service = api_service host, port = api_service.get_socket_info('osapi') - self.auth_url = 'http://%s:%s/v1.0' % (host, port) + self.auth_url = 'http://%s:%s/v1.1' % (host, port) def tearDown(self): self.context.cleanup() -- cgit From d3de6cd1b2997e495a000b998b321346e2a75306 Mon Sep 17 00:00:00 2001 From: Renuka Apte Date: Fri, 8 Apr 2011 14:46:26 -0700 Subject: Fixes euca-attach-volume for iscsi using Xenserver Minor changes required to xenapi functions to get correct format for volume-id, iscsi-host, etc. --- nova/virt/xenapi/volume_utils.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 72284ac02..27964cac0 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -209,9 +209,9 @@ def _get_volume_id(path_or_id): # see compute/manager->setup_compute_volume volume_id = path_or_id[path_or_id.find('/vol-') + 1:] if volume_id == path_or_id: - volume_id = path_or_id[path_or_id.find('-vol-') + 1:] - volume_id = volume_id.replace('--', '-') - return volume_id + volume_id = path_or_id[path_or_id.find('-volume--') + 1:] + volume_id = volume_id.replace('volume--', '') + return int(volume_id) def _get_target_host(iscsi_string): @@ -244,25 +244,21 @@ def _get_target(volume_id): Gets iscsi name and portal from volume name and host. For this method to work the following are needed: 1) volume_ref['host'] must resolve to something rather than loopback - 2) ietd must bind only to the address as resolved above - If any of the two conditions are not met, fall back on Flags. """ - volume_ref = db.volume_get_by_ec2_id(context.get_admin_context(), + volume_ref = db.volume_get(context.get_admin_context(), volume_id) result = (None, None) try: - (r, _e) = utils.execute("sudo iscsiadm -m discovery -t " - "sendtargets -p %s" % - volume_ref['host']) + (r, _e) = utils.execute('sudo', 'iscsiadm', '-m', 'discovery', + '-t', 'sendtargets', '-p', volume_ref['host']) except exception.ProcessExecutionError, exc: LOG.exception(exc) else: - targets = r.splitlines() - if len(_e) == 0 and len(targets) == 1: - for target in targets: - if volume_id in target: + volume_name = "volume-%08x" % volume_id + for target in r.splitlines(): + if FLAGS.iscsi_ip_prefix in target and volume_name in target: (location, _sep, iscsi_name) = target.partition(" ") break - iscsi_portal = location.split(",")[0] - result = (iscsi_name, iscsi_portal) + iscsi_portal = location.split(",")[0] + result = (iscsi_name, iscsi_portal) return result -- cgit From 12ec5f5c0d6a88779780b15b6ef38a016d6aae4a Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 12 Apr 2011 08:04:55 -0700 Subject: Add new flag 'max_kernel_ramdisk_size' to specify a maximum size of kernel or ramdisk so we don't copy large files to dom0 and fill up /boot/guest --- nova/virt/xenapi/vm_utils.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d2045a557..dd1fd9383 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -51,6 +51,8 @@ FLAGS = flags.FLAGS flags.DEFINE_string('default_os_type', 'linux', 'Default OS type') flags.DEFINE_integer('block_device_creation_timeout', 10, 'time to wait for a block device to be created') +flags.DEFINE_integer('max_kernel_ramdisk_size', 16 * 1024 * 1024, + 'maximum size in bytes of kernel or ramdisk images') XENAPI_POWER_STATE = { 'Halted': power_state.SHUTDOWN, @@ -448,6 +450,12 @@ class VMHelper(HelperBase): if image_type == ImageType.DISK: # Make room for MBR. vdi_size += MBR_SIZE_BYTES + elif image_type == ImageType.KERNEL_RAMDISK and \ + vdi_size > FLAGS.max_kernel_ramdisk_size: + max_size = FLAGS.max_kernel_ramdisk_size + raise exception.Error( + _("Kernel/Ramdisk image is too large, %(vdi_size)d bytes " + "(max %(max_size)d bytes)") % locals()) name_label = get_name_label_for_image(image) vdi_ref = cls.create_vdi(session, sr_ref, name_label, vdi_size, False) -- cgit From bc953f37560b7353b9b8c86e8d0bdaa5672d3acd Mon Sep 17 00:00:00 2001 From: Renuka Apte Date: Tue, 12 Apr 2011 15:20:30 -0700 Subject: Minor fixes --- nova/virt/xenapi/volume_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 27964cac0..819c48be5 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -250,7 +250,7 @@ def _get_target(volume_id): result = (None, None) try: (r, _e) = utils.execute('sudo', 'iscsiadm', '-m', 'discovery', - '-t', 'sendtargets', '-p', volume_ref['host']) + '-t', 'sendtargets', '-p', volume_ref['host']) except exception.ProcessExecutionError, exc: LOG.exception(exc) else: -- cgit From c04b0caca4a725be390271be30bf8a034aa5ca9d Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 13 Apr 2011 10:10:40 -0700 Subject: Minor formatting cleanup --- nova/virt/xenapi/vm_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index dd1fd9383..32a617ef4 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -454,8 +454,8 @@ class VMHelper(HelperBase): vdi_size > FLAGS.max_kernel_ramdisk_size: max_size = FLAGS.max_kernel_ramdisk_size raise exception.Error( - _("Kernel/Ramdisk image is too large, %(vdi_size)d bytes " - "(max %(max_size)d bytes)") % locals()) + _("Kernel/Ramdisk image is too large: %(vdi_size)d bytes, " + "max %(max_size)d bytes") % locals()) name_label = get_name_label_for_image(image) vdi_ref = cls.create_vdi(session, sr_ref, name_label, vdi_size, False) -- cgit From e171e7511c6b1a2baacf0ab9857643cc1fe82eb3 Mon Sep 17 00:00:00 2001 From: Renuka Apte Date: Tue, 3 May 2011 17:08:04 -0700 Subject: Fix indentation. --- nova/virt/xenapi/volume_utils.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 819c48be5..55c11a4ad 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -246,19 +246,21 @@ def _get_target(volume_id): 1) volume_ref['host'] must resolve to something rather than loopback """ volume_ref = db.volume_get(context.get_admin_context(), - volume_id) + volume_id) result = (None, None) try: - (r, _e) = utils.execute('sudo', 'iscsiadm', '-m', 'discovery', - '-t', 'sendtargets', '-p', volume_ref['host']) + (r, _e) = utils.execute('sudo', 'iscsiadm', + '-m', 'discovery', + '-t', 'sendtargets', + '-p', volume_ref['host']) except exception.ProcessExecutionError, exc: LOG.exception(exc) else: volume_name = "volume-%08x" % volume_id for target in r.splitlines(): if FLAGS.iscsi_ip_prefix in target and volume_name in target: - (location, _sep, iscsi_name) = target.partition(" ") - break + (location, _sep, iscsi_name) = target.partition(" ") + break iscsi_portal = location.split(",")[0] result = (iscsi_name, iscsi_portal) return result -- cgit From 1c1a06c3731dd82b331f317ba52edbfe2110a40e Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 9 May 2011 11:47:33 -0400 Subject: clean up unused functions from virt/images.py --- nova/virt/images.py | 61 ----------------------------------------------------- 1 file changed, 61 deletions(-) (limited to 'nova') diff --git a/nova/virt/images.py b/nova/virt/images.py index 2e3f2ee4d..1eb6f4a5f 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -51,67 +51,6 @@ def fetch(image_id, path, _user, _project): metadata = image_service.get(elevated, image_id, image_file) return metadata - -# NOTE(vish): The methods below should be unnecessary, but I'm leaving -# them in case the glance client does not work on windows. -def _fetch_image_no_curl(url, path, headers): - request = urllib2.Request(url) - for (k, v) in headers.iteritems(): - request.add_header(k, v) - - def urlretrieve(urlfile, fpath): - chunk = 1 * 1024 * 1024 - f = open(fpath, "wb") - while 1: - data = urlfile.read(chunk) - if not data: - break - f.write(data) - - urlopened = urllib2.urlopen(request) - urlretrieve(urlopened, path) - LOG.debug(_("Finished retreving %(url)s -- placed in %(path)s") % locals()) - - -def _fetch_s3_image(image, path, user, project): - url = image_url(image) - - # This should probably move somewhere else, like e.g. a download_as - # method on User objects and at the same time get rewritten to use - # a web client. - headers = {} - headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) - - (_, _, url_path, _, _, _) = urlparse.urlparse(url) - access = manager.AuthManager().get_access_key(user, project) - signature = signer.Signer(user.secret.encode()).s3_authorization(headers, - 'GET', - url_path) - headers['Authorization'] = 'AWS %s:%s' % (access, signature) - - if sys.platform.startswith('win'): - return _fetch_image_no_curl(url, path, headers) - else: - cmd = ['/usr/bin/curl', '--fail', '--silent', url] - for (k, v) in headers.iteritems(): - cmd += ['-H', '\'%s: %s\'' % (k, v)] - - cmd += ['-o', path] - return utils.execute(*cmd) - - -def _fetch_local_image(image, path, user, project): - source = _image_path(os.path.join(image, 'image')) - if sys.platform.startswith('win'): - return shutil.copy(source, path) - else: - return utils.execute('cp', source, path) - - -def _image_path(path): - return os.path.join(FLAGS.images_path, path) - - # TODO(vish): xenapi should use the glance client code directly instead # of retrieving the image using this method. def image_url(image): -- cgit From 09b795b8d6f0b925dbd4bcd203f471607c42f368 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 9 May 2011 19:46:15 -0400 Subject: got rid of unnecessary imports --- nova/virt/images.py | 9 --------- 1 file changed, 9 deletions(-) (limited to 'nova') diff --git a/nova/virt/images.py b/nova/virt/images.py index 1eb6f4a5f..8689c0ed3 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -21,19 +21,10 @@ Handling of VM disk images. """ -import os.path -import shutil -import sys -import time -import urllib2 -import urlparse - from nova import context from nova import flags from nova import log as logging from nova import utils -from nova.auth import manager -from nova.auth import signer FLAGS = flags.FLAGS -- cgit From eb0619c91b4756d355b7a5cd5c1f16d342f14a6b Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 11 May 2011 06:28:07 -0700 Subject: First cut with tests passing --- nova/api/openstack/__init__.py | 3 +- nova/api/openstack/zones.py | 48 +++++++++++++++++++ nova/crypto.py | 45 +++++++++++++++++ nova/scheduler/api.py | 6 +++ nova/scheduler/zone_aware_scheduler.py | 88 ++++++++++++++++++++++++++++++++++ nova/tests/api/openstack/test_zones.py | 40 ++++++++++++++++ nova/tests/test_crypto.py | 48 +++++++++++++++++++ 7 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 nova/scheduler/zone_aware_scheduler.py create mode 100644 nova/tests/test_crypto.py (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 348b70d5b..b743d306b 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -98,7 +98,8 @@ class APIRouter(wsgi.Router): server_members['inject_network_info'] = 'POST' mapper.resource("zone", "zones", controller=zones.Controller(), - collection={'detail': 'GET', 'info': 'GET'}), + collection={'detail': 'GET', 'info': 'GET', + 'select': 'GET',}), mapper.resource("user", "users", controller=users.Controller(), collection={'detail': 'GET'}) diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 227ffecdc..70653dc0e 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -13,6 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. +import json +import urlparse + +from nova import crypto from nova import db from nova import flags from nova import log as logging @@ -21,6 +25,12 @@ from nova.scheduler import api FLAGS = flags.FLAGS +flags.DEFINE_string('build_plan_encryption_key', + None, + '128bit (hex) encryption key for scheduler build plans.') + + +LOG = logging.getLogger('nova.api.openstack.zones') def _filter_keys(item, keys): @@ -41,6 +51,14 @@ def _scrub_zone(zone): 'deleted', 'deleted_at', 'updated_at')) +def check_encryption_key(func): + def wrapped(*args, **kwargs): + if not FLAGS.build_plan_encryption_key: + raise exception.Error(_("--build_plan_encryption_key not set")) + return func(*args, **kwargs) + return wrapped + + class Controller(common.OpenstackController): _serialization_metadata = { @@ -97,3 +115,33 @@ class Controller(common.OpenstackController): zone_id = int(id) zone = api.zone_update(context, zone_id, env["zone"]) return dict(zone=_scrub_zone(zone)) + + @check_encryption_key + def select(self, req): + """Returns a weighted list of costs to create instances + of desired capabilities.""" + ctx = req.environ['nova.context'] + qs = req.environ['QUERY_STRING'] + param_dict = urlparse.parse_qs(qs) + param_dict.pop("fresh", None) + # parse_qs returns a dict where the values are lists, + # since query strings can have multiple values for the + # same key. We need to convert that to single values. + for key in param_dict: + param_dict[key] = param_dict[key][0] + build_plan = api.select(ctx, specs=param_dict) + cooked = self._scrub_build_plan(build_plan) + return {"weights": cooked} + + def _scrub_build_plan(self, build_plan): + """Remove all the confidential data and return a sanitized + version of the build plan. Include an encrypted full version + of the weighting entry so we can get back to it later.""" + encryptor = crypto.encryptor(FLAGS.build_plan_encryption_key) + cooked = [] + for entry in build_plan: + json_entry = json.dumps(entry) + cipher_text = encryptor(json_entry) + cooked.append(dict(weight=entry['weight'], + blob=cipher_text)) + return cooked diff --git a/nova/crypto.py b/nova/crypto.py index 14b9cbef6..bdc32482a 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -332,6 +332,51 @@ def mkcacert(subject='nova', years=1): return cert, pk, pkey +def _build_cipher(key, iv, encode=True): + """Make a 128bit AES CBC encode/decode Cipher object. + Padding is handled internally.""" + operation = 1 if encode else 0 + return M2Crypto.EVP.Cipher(alg='aes_128_cbc', key=key, iv=iv, op=operation) + + +def encryptor(key, iv=None): + """Simple symmetric key encryption.""" + key = base64.b64decode(key) + if iv is None: + iv = '\0' * 16 + else: + iv = base64.b64decode(iv) + + def encrypt(data): + cipher = _build_cipher(key, iv, encode=True) + v = cipher.update(data) + v = v + cipher.final() + del cipher + v = base64.b64encode(v) + return v + + return encrypt + + +def decryptor(key, iv=None): + """Simple symmetric key decryption.""" + key = base64.b64decode(key) + if iv is None: + iv = '\0' * 16 + else: + iv = base64.b64decode(iv) + + def decrypt(data): + data = base64.b64decode(data) + cipher = _build_cipher(key, iv, encode=False) + v = cipher.update(data) + v = v + cipher.final() + del cipher + return v + + return decrypt + + # Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/ # # Permission is hereby granted, free of charge, to any person obtaining a diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 816ae5513..d8a0025ed 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 000000000..b849e8de1 --- /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() diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index 5d5799b59..879039091 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -20,6 +20,7 @@ import json import nova.db from nova import context +from nova import crypto from nova import flags from nova import test from nova.api.openstack import zones @@ -79,6 +80,17 @@ def zone_capabilities(method, context): return dict() +GLOBAL_BUILD_PLAN = [ + dict(name='host1', weight=10, ip='10.0.0.1', zone='zone1'), + dict(name='host2', weight=9, ip='10.0.0.2', zone='zone2'), + dict(name='host3', weight=8, ip='10.0.0.3', zone='zone3'), + dict(name='host4', weight=7, ip='10.0.0.4', zone='zone4'), + ] + + +def zone_select(context, specs): + return GLOBAL_BUILD_PLAN + class ZonesTest(test.TestCase): def setUp(self): super(ZonesTest, self).setUp() @@ -190,3 +202,31 @@ class ZonesTest(test.TestCase): self.assertEqual(res_dict['zone']['name'], 'darksecret') self.assertEqual(res_dict['zone']['cap1'], 'a;b') self.assertEqual(res_dict['zone']['cap2'], 'c;d') + + def test_zone_select(self): + FLAGS.build_plan_encryption_key = 'c286696d887c9aa0611bbb3e2025a45a' + self.stubs.Set(api, 'select', zone_select) + + req = webob.Request.blank('/v1.0/zones/select') + + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res.status_int, 200) + + self.assertTrue('weights' in res_dict) + + for item in res_dict['weights']: + blob = item['blob'] + decrypt = crypto.decryptor(FLAGS.build_plan_encryption_key) + secret_item = json.loads(decrypt(blob)) + found = False + for original_item in GLOBAL_BUILD_PLAN: + if original_item['name'] != secret_item['name']: + continue + found = True + for key in ('weight', 'ip', 'zone'): + self.assertEqual(secret_item[key], original_item[key]) + + self.assertTrue(found) + self.assertEqual(len(item), 2) + self.assertTrue('weight' in item) diff --git a/nova/tests/test_crypto.py b/nova/tests/test_crypto.py new file mode 100644 index 000000000..945d78794 --- /dev/null +++ b/nova/tests/test_crypto.py @@ -0,0 +1,48 @@ +# 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 Crypto module. +""" + +from nova import crypto +from nova import test + + +class SymmetricKeyTestCase(test.TestCase): + """Test case for Encrypt/Decrypt""" + def test_encrypt_decrypt(self): + key = 'c286696d887c9aa0611bbb3e2025a45a' + plain_text = "The quick brown fox jumped over the lazy dog." + + # No IV supplied (all 0's) + encrypt = crypto.encryptor(key) + cipher_text = encrypt(plain_text) + self.assertNotEquals(plain_text, cipher_text) + + decrypt = crypto.decryptor(key) + plain = decrypt(cipher_text) + + self.assertEquals(plain_text, plain) + + # IV supplied ... + iv = '562e17996d093d28ddb3ba695a2e6f58' + encrypt = crypto.encryptor(key, iv) + cipher_text = encrypt(plain_text) + self.assertNotEquals(plain_text, cipher_text) + + decrypt = crypto.decryptor(key, iv) + plain = decrypt(cipher_text) + + self.assertEquals(plain_text, plain) -- cgit From 3470ed651b837106bf8afe736adfda63ad8d746e Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 11 May 2011 11:12:31 -0700 Subject: start of zone_aware_scheduler test --- nova/compute/api.py | 7 ++++++ nova/scheduler/api.py | 39 ++++++++++++++++++++++++++++++++++ nova/scheduler/zone_aware_scheduler.py | 31 ++++++++++++++++----------- 3 files changed, 65 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 63884be97..8a7c713a2 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -248,11 +248,18 @@ class API(base.Base): uid = context.user_id LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's" " instance %(instance_id)s") % locals()) + + # NOTE(sandy): For now we're just going to pass in the + # instance_type record to the scheduler. In a later phase + # we'll be ripping this whole for-loop out and deferring the + # creation of the Instance record. At that point all this will + # change. rpc.cast(context, FLAGS.scheduler_topic, {"method": "run_instance", "args": {"topic": FLAGS.compute_topic, "instance_id": instance_id, + "instance_type": instance_type, "availability_zone": availability_zone, "injected_files": injected_files}}) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index d8a0025ed..55f8e0a6d 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 b849e8de1..b85cdfe6d 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() -- cgit From ec91629806e4711df92686c64dd341480e237f97 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 11 May 2011 11:43:58 -0700 Subject: NoValidHost exception test --- nova/scheduler/zone_aware_scheduler.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index b85cdfe6d..8285ec576 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() -- cgit From facb7a77685164574eecb7faac966c9bc1b4dec9 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 11 May 2011 12:45:22 -0700 Subject: messing around with the flow of create() and specs --- nova/scheduler/zone_aware_scheduler.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'nova') diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 8285ec576..07f86450b 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 -- cgit From fd8b9eb204b77da583f1aee4022920367730823f Mon Sep 17 00:00:00 2001 From: Renuka Apte Date: Wed, 11 May 2011 17:06:56 -0700 Subject: Fix remote volume code --- nova/virt/xenapi/volume_utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 55c11a4ad..7821a4f7e 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -204,13 +204,16 @@ def _get_volume_id(path_or_id): if isinstance(path_or_id, int): return path_or_id # n must contain at least the volume_id - # /vol- is for remote volumes - # -vol- is for local volumes + # :volume- is for remote volumes + # -volume- is for local volumes # see compute/manager->setup_compute_volume - volume_id = path_or_id[path_or_id.find('/vol-') + 1:] + volume_id = path_or_id[path_or_id.find(':volume-') + 1:] if volume_id == path_or_id: volume_id = path_or_id[path_or_id.find('-volume--') + 1:] volume_id = volume_id.replace('volume--', '') + else: + volume_id = volume_id.replace('volume-', '') + volume_id = volume_id[0:volume_id.find('-')] return int(volume_id) -- cgit From 22c33d80ce040f09c9bcd7584cf1165cf769e192 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 12 May 2011 10:55:04 -0400 Subject: Initial work on request extensions. --- nova/api/openstack/extensions.py | 78 +++++++++++++++++++++++ nova/tests/api/openstack/extensions/foxinsocks.py | 3 + nova/tests/api/openstack/test_extensions.py | 47 +++++++++++++- 3 files changed, 127 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 7ea7afef6..e6dd228ec 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -165,6 +165,34 @@ class ResponseExtensionController(common.OpenstackController): return res +class RequestExtensionController(common.OpenstackController): + + def __init__(self, application): + self.application = application + self.handlers = [] + + def add_handler(self, handler): + self.handlers.append(handler) + + def process(self, req, *args, **kwargs): + res = req.get_response(self.application) + content_type = req.best_match_content_type() + # currently response handlers are un-ordered + for handler in self.handlers: + res = handler(req, res) + try: + body = res.body + headers = res.headers + except AttributeError: + default_xmlns = None + body = self._serialize(res, content_type, default_xmlns) + headers = {"Content-Type": content_type} + res = webob.Response() + res.body = body + res.headers = headers + return res + + class ExtensionController(common.OpenstackController): def __init__(self, extension_manager): @@ -245,6 +273,25 @@ class ExtensionMiddleware(wsgi.Middleware): return response_ext_controllers + def _request_ext_controllers(self, application, ext_mgr, mapper): + """Returns a dict of RequestExtensionController-s by collection.""" + request_ext_controllers = {} + for req_ext in ext_mgr.get_request_extensions(): + if not req_ext.key in request_ext_controllers.keys(): + controller = RequestExtensionController(application) + mapper.connect(req_ext.url_route + '.:(format)', + action='process', + controller=controller, + conditions=req_ext.conditions) + + mapper.connect(req_ext.url_route, + action='process', + controller=controller, + conditions=req_ext.conditions) + request_ext_controllers[req_ext.key] = controller + + return request_ext_controllers + def __init__(self, application, ext_mgr=None): if ext_mgr is None: @@ -279,6 +326,14 @@ class ExtensionMiddleware(wsgi.Middleware): controller = resp_controllers[response_ext.key] controller.add_handler(response_ext.handler) + # extended requests + req_controllers = self._request_ext_controllers(application, ext_mgr, + mapper) + for request_ext in ext_mgr.get_request_extensions(): + LOG.debug(_('Extended request: %s'), request_ext.key) + controller = req_controllers[request_ext.key] + controller.add_handler(request_ext.handler) + self._router = routes.middleware.RoutesMiddleware(self._dispatch, mapper) @@ -359,6 +414,18 @@ class ExtensionManager(object): pass return response_exts + def get_request_extensions(self): + """Returns a list of RequestExtension objects.""" + request_exts = [] + for alias, ext in self.extensions.iteritems(): + try: + request_exts.extend(ext.get_request_extensions()) + except AttributeError: + # NOTE(dprince): Extension aren't required to have request + # extensions + pass + return request_exts + def _check_extension(self, extension): """Checks for required methods in extension objects.""" try: @@ -431,6 +498,17 @@ class ResponseExtension(object): self.key = "%s-%s" % (method, url_route) +class RequestExtension(object): + """Provide a way to handle custom request data that is sent to core + nova OpenStack API controllers. + """ + def __init__(self, method, url_route, handler): + self.url_route = url_route + self.handler = handler + self.conditions = dict(method=[method]) + self.key = "%s-%s" % (method, url_route) + + class ActionExtension(object): """Add custom actions to core nova OpenStack API controllers.""" diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index 0860b51ac..7699ffb56 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -89,6 +89,9 @@ class Foxinsocks(object): response_exts.append(resp_ext2) return response_exts + def get_request_extensions(self): + return [] + def _add_tweedle(self, input_dict, req, id): return "Tweedle Beetle Added." diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 481d34ed1..7fadb5b69 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -45,10 +45,12 @@ class StubController(nova.wsgi.Controller): class StubExtensionManager(object): - def __init__(self, resource_ext=None, action_ext=None, response_ext=None): + def __init__(self, resource_ext=None, action_ext=None, response_ext=None, + request_ext=None): self.resource_ext = resource_ext self.action_ext = action_ext self.response_ext = response_ext + self.request_ext = request_ext def get_name(self): return "Tweedle Beetle Extension" @@ -77,6 +79,12 @@ class StubExtensionManager(object): response_exts.append(self.response_ext) return response_exts + def get_request_extensions(self): + request_extensions = [] + if self.request_ext: + request_extensions.append(self.request_ext) + return request_extensions + class ExtensionControllerTest(unittest.TestCase): @@ -234,3 +242,40 @@ class ResponseExtensionTest(unittest.TestCase): response_data = json.loads(response.body) self.assertEqual(test_resp, response_data['flavor']['googoose']) self.assertEqual("Pig Bands!", response_data['big_bands']) + + +class RequestExtensionTest(unittest.TestCase): + + def setUp(self): + super(RequestExtensionTest, self).setUp() + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.reset_fake_data() + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) + self.context = context.get_admin_context() + + def tearDown(self): + self.stubs.UnsetAll() + super(RequestExtensionTest, self).tearDown() + + def test_post_request_extension_with_stub_mgr(self): + + def _req_handler(req, res): + # only handle JSON responses + data = json.loads(res.body) + data['flavor']['googoose'] = req.GET.get('test_param') + return data + + resp_ext = extensions.RequestExtension('GET', + '/v1.1/flavors/:(id)', + _req_handler) + + manager = StubExtensionManager(None, None, None, resp_ext) + app = fakes.wsgi_app() + ext_midware = extensions.ExtensionMiddleware(app, manager) + request = webob.Request.blank("/v1.1/flavors/1?test_param=foo") + request.environ['api.version'] = '1.1' + response = request.get_response(ext_midware) + self.assertEqual(200, response.status_int) + response_data = json.loads(response.body) + self.assertEqual('foo', response_data['flavor']['googoose']) -- cgit From ce2b13d9fb30c0afbcff97f434d7423cad39b8b9 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 12 May 2011 12:52:32 -0400 Subject: Remove ResponseExtensions. The new RequestExtension covers both use cases. --- nova/api/openstack/extensions.py | 88 ++--------------------- nova/tests/api/openstack/extensions/foxinsocks.py | 15 ++-- nova/tests/api/openstack/test_extensions.py | 75 ++++--------------- 3 files changed, 24 insertions(+), 154 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index e6dd228ec..3a7763463 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -105,15 +105,14 @@ class ExtensionDescriptor(object): actions = [] return actions - def get_response_extensions(self): - """List of extensions.ResponseExtension extension objects. + def get_request_extensions(self): + """List of extensions.RequestException extension objects. - Response extensions are used to insert information into existing - response data. + Request extensions are used to handle custom request data. """ - response_exts = [] - return response_exts + request_exts = [] + return request_exts class ActionExtensionController(common.OpenstackController): @@ -137,34 +136,6 @@ class ActionExtensionController(common.OpenstackController): return res -class ResponseExtensionController(common.OpenstackController): - - def __init__(self, application): - self.application = application - self.handlers = [] - - def add_handler(self, handler): - self.handlers.append(handler) - - def process(self, req, *args, **kwargs): - res = req.get_response(self.application) - content_type = req.best_match_content_type() - # currently response handlers are un-ordered - for handler in self.handlers: - res = handler(res) - try: - body = res.body - headers = res.headers - except AttributeError: - default_xmlns = None - body = self._serialize(res, content_type, default_xmlns) - headers = {"Content-Type": content_type} - res = webob.Response() - res.body = body - res.headers = headers - return res - - class RequestExtensionController(common.OpenstackController): def __init__(self, application): @@ -254,25 +225,6 @@ class ExtensionMiddleware(wsgi.Middleware): return action_controllers - def _response_ext_controllers(self, application, ext_mgr, mapper): - """Returns a dict of ResponseExtensionController-s by collection.""" - response_ext_controllers = {} - for resp_ext in ext_mgr.get_response_extensions(): - if not resp_ext.key in response_ext_controllers.keys(): - controller = ResponseExtensionController(application) - mapper.connect(resp_ext.url_route + '.:(format)', - action='process', - controller=controller, - conditions=resp_ext.conditions) - - mapper.connect(resp_ext.url_route, - action='process', - controller=controller, - conditions=resp_ext.conditions) - response_ext_controllers[resp_ext.key] = controller - - return response_ext_controllers - def _request_ext_controllers(self, application, ext_mgr, mapper): """Returns a dict of RequestExtensionController-s by collection.""" request_ext_controllers = {} @@ -318,14 +270,6 @@ class ExtensionMiddleware(wsgi.Middleware): controller = action_controllers[action.collection] controller.add_action(action.action_name, action.handler) - # extended responses - resp_controllers = self._response_ext_controllers(application, ext_mgr, - mapper) - for response_ext in ext_mgr.get_response_extensions(): - LOG.debug(_('Extended response: %s'), response_ext.key) - controller = resp_controllers[response_ext.key] - controller.add_handler(response_ext.handler) - # extended requests req_controllers = self._request_ext_controllers(application, ext_mgr, mapper) @@ -402,18 +346,6 @@ class ExtensionManager(object): pass return actions - def get_response_extensions(self): - """Returns a list of ResponseExtension objects.""" - response_exts = [] - for alias, ext in self.extensions.iteritems(): - try: - response_exts.extend(ext.get_response_extensions()) - except AttributeError: - # NOTE(dprince): Extension aren't required to have response - # extensions - pass - return response_exts - def get_request_extensions(self): """Returns a list of RequestExtension objects.""" request_exts = [] @@ -488,16 +420,6 @@ class ExtensionManager(object): self.extensions[alias] = ext -class ResponseExtension(object): - """Add data to responses from core nova OpenStack API controllers.""" - - def __init__(self, method, url_route, handler): - self.url_route = url_route - self.handler = handler - self.conditions = dict(method=[method]) - self.key = "%s-%s" % (method, url_route) - - class RequestExtension(object): """Provide a way to handle custom request data that is sent to core nova OpenStack API controllers. diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index 7699ffb56..b3f30c4e5 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -63,35 +63,32 @@ class Foxinsocks(object): self._delete_tweedle)) return actions - def get_response_extensions(self): + def get_request_extensions(self): response_exts = [] - def _goose_handler(res): + def _goose_handler(req, res): #NOTE: This only handles JSON responses. # You can use content type header to test for XML. data = json.loads(res.body) - data['flavor']['googoose'] = "Gooey goo for chewy chewing!" + data['flavor']['googoose'] = req.GET.get('chewing') return data - resp_ext = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', + resp_ext = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', _goose_handler) response_exts.append(resp_ext) - def _bands_handler(res): + def _bands_handler(req, res): #NOTE: This only handles JSON responses. # You can use content type header to test for XML. data = json.loads(res.body) data['big_bands'] = 'Pig Bands!' return data - resp_ext2 = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', + resp_ext2 = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', _bands_handler) response_exts.append(resp_ext2) return response_exts - def get_request_extensions(self): - return [] - def _add_tweedle(self, input_dict, req, id): return "Tweedle Beetle Added." diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 7fadb5b69..c63474dea 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -45,11 +45,9 @@ class StubController(nova.wsgi.Controller): class StubExtensionManager(object): - def __init__(self, resource_ext=None, action_ext=None, response_ext=None, - request_ext=None): + def __init__(self, resource_ext=None, action_ext=None, request_ext=None): self.resource_ext = resource_ext self.action_ext = action_ext - self.response_ext = response_ext self.request_ext = request_ext def get_name(self): @@ -73,12 +71,6 @@ class StubExtensionManager(object): action_exts.append(self.action_ext) return action_exts - def get_response_extensions(self): - response_exts = [] - if self.response_ext: - response_exts.append(self.response_ext) - return response_exts - def get_request_extensions(self): request_extensions = [] if self.request_ext: @@ -191,10 +183,10 @@ class ActionExtensionTest(unittest.TestCase): self.assertEqual(404, response.status_int) -class ResponseExtensionTest(unittest.TestCase): +class RequestExtensionTest(unittest.TestCase): def setUp(self): - super(ResponseExtensionTest, self).setUp() + super(RequestExtensionTest, self).setUp() self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.reset_fake_data() fakes.FakeAuthDatabase.data = {} @@ -203,79 +195,38 @@ class ResponseExtensionTest(unittest.TestCase): def tearDown(self): self.stubs.UnsetAll() - super(ResponseExtensionTest, self).tearDown() + super(RequestExtensionTest, self).tearDown() def test_get_resources_with_stub_mgr(self): - test_resp = "Gooey goo for chewy chewing!" - - def _resp_handler(res): + def _req_handler(req, res): # only handle JSON responses data = json.loads(res.body) - data['flavor']['googoose'] = test_resp + data['flavor']['googoose'] = req.GET.get('chewing') return data - resp_ext = extensions.ResponseExtension('GET', + req_ext = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', - _resp_handler) + _req_handler) - manager = StubExtensionManager(None, None, resp_ext) + manager = StubExtensionManager(None, None, req_ext) app = fakes.wsgi_app() ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/v1.1/flavors/1") + request = webob.Request.blank("/v1.1/flavors/1?chewing=bluegoo") request.environ['api.version'] = '1.1' response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) response_data = json.loads(response.body) - self.assertEqual(test_resp, response_data['flavor']['googoose']) + self.assertEqual('bluegoo', response_data['flavor']['googoose']) def test_get_resources_with_mgr(self): - test_resp = "Gooey goo for chewy chewing!" - app = fakes.wsgi_app() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/v1.1/flavors/1") + request = webob.Request.blank("/v1.1/flavors/1?chewing=newblue") request.environ['api.version'] = '1.1' response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) response_data = json.loads(response.body) - self.assertEqual(test_resp, response_data['flavor']['googoose']) + self.assertEqual('newblue', response_data['flavor']['googoose']) self.assertEqual("Pig Bands!", response_data['big_bands']) - - -class RequestExtensionTest(unittest.TestCase): - - def setUp(self): - super(RequestExtensionTest, self).setUp() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.reset_fake_data() - fakes.FakeAuthDatabase.data = {} - fakes.stub_out_auth(self.stubs) - self.context = context.get_admin_context() - - def tearDown(self): - self.stubs.UnsetAll() - super(RequestExtensionTest, self).tearDown() - - def test_post_request_extension_with_stub_mgr(self): - - def _req_handler(req, res): - # only handle JSON responses - data = json.loads(res.body) - data['flavor']['googoose'] = req.GET.get('test_param') - return data - - resp_ext = extensions.RequestExtension('GET', - '/v1.1/flavors/:(id)', - _req_handler) - - manager = StubExtensionManager(None, None, None, resp_ext) - app = fakes.wsgi_app() - ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/v1.1/flavors/1?test_param=foo") - request.environ['api.version'] = '1.1' - response = request.get_response(ext_midware) - self.assertEqual(200, response.status_int) - response_data = json.loads(response.body) - self.assertEqual('foo', response_data['flavor']['googoose']) -- cgit From e03921c2799acf36083eb13c3134b861bc4732a6 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 12 May 2011 14:37:15 -0400 Subject: Make it so that ExtensionRequest objects now return proper webob objects. This avoids the odd serialization code in the RequestExtensionController class which converts JSON dicts to webobs for us. --- nova/api/openstack/extensions.py | 11 ----------- nova/tests/api/openstack/extensions/foxinsocks.py | 6 ++++-- nova/tests/api/openstack/test_extensions.py | 3 ++- 3 files changed, 6 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 3a7763463..ac79b9310 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -147,20 +147,9 @@ class RequestExtensionController(common.OpenstackController): def process(self, req, *args, **kwargs): res = req.get_response(self.application) - content_type = req.best_match_content_type() # currently response handlers are un-ordered for handler in self.handlers: res = handler(req, res) - try: - body = res.body - headers = res.headers - except AttributeError: - default_xmlns = None - body = self._serialize(res, content_type, default_xmlns) - headers = {"Content-Type": content_type} - res = webob.Response() - res.body = body - res.headers = headers return res diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index b3f30c4e5..f8e31589a 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -71,7 +71,8 @@ class Foxinsocks(object): # You can use content type header to test for XML. data = json.loads(res.body) data['flavor']['googoose'] = req.GET.get('chewing') - return data + res.body = json.dumps(data) + return res resp_ext = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', _goose_handler) @@ -82,7 +83,8 @@ class Foxinsocks(object): # You can use content type header to test for XML. data = json.loads(res.body) data['big_bands'] = 'Pig Bands!' - return data + res.body = json.dumps(data) + return res resp_ext2 = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', _bands_handler) diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index c63474dea..544298602 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -203,7 +203,8 @@ class RequestExtensionTest(unittest.TestCase): # only handle JSON responses data = json.loads(res.body) data['flavor']['googoose'] = req.GET.get('chewing') - return data + res.body = json.dumps(data) + return res req_ext = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', -- cgit From 27b5de353aee88d37c369bb5b019a746116732c0 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 12 May 2011 14:45:39 -0400 Subject: Variable renaming. --- nova/tests/api/openstack/extensions/foxinsocks.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index f8e31589a..dbdd0928a 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -64,7 +64,7 @@ class Foxinsocks(object): return actions def get_request_extensions(self): - response_exts = [] + request_exts = [] def _goose_handler(req, res): #NOTE: This only handles JSON responses. @@ -74,9 +74,9 @@ class Foxinsocks(object): res.body = json.dumps(data) return res - resp_ext = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', + req_ext1 = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', _goose_handler) - response_exts.append(resp_ext) + request_exts.append(req_ext1) def _bands_handler(req, res): #NOTE: This only handles JSON responses. @@ -86,10 +86,10 @@ class Foxinsocks(object): res.body = json.dumps(data) return res - resp_ext2 = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', + req_ext2 = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', _bands_handler) - response_exts.append(resp_ext2) - return response_exts + request_exts.append(req_ext2) + return request_exts def _add_tweedle(self, input_dict, req, id): -- cgit From e72667cb125f1d970f302bb18f051380fac0711d Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 12 May 2011 14:52:54 -0400 Subject: Update comment. --- nova/api/openstack/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index ac79b9310..0e729e137 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -147,7 +147,7 @@ class RequestExtensionController(common.OpenstackController): def process(self, req, *args, **kwargs): res = req.get_response(self.application) - # currently response handlers are un-ordered + # currently request handlers are un-ordered for handler in self.handlers: res = handler(req, res) return res -- cgit From e7662bfcead8df8cc1fc655af6da15dc47777565 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 12 May 2011 13:42:04 -0700 Subject: fix for lp760921. Previously, if tune2fs failed, as it does on windows hosts, kpartx -d also failed to be called which leaves mapped partitions that retain holds on the nbd device. These holds cause the observed errors. --- nova/virt/disk.py | 50 ++++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 24 deletions(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index ddea1a1f7..f8aea1f34 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -81,34 +81,36 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): else: mapped_device = device - # We can only loopback mount raw images. If the device isn't there, - # it's normally because it's a .vmdk or a .vdi etc - if not os.path.exists(mapped_device): - raise exception.Error('Mapped device was not found (we can' - ' only inject raw disk images): %s' % - mapped_device) - - # Configure ext2fs so that it doesn't auto-check every N boots - out, err = utils.execute('sudo', 'tune2fs', - '-c', 0, '-i', 0, mapped_device) - - tmpdir = tempfile.mkdtemp() try: - # mount loopback to dir - out, err = utils.execute( - 'sudo', 'mount', mapped_device, tmpdir) - if err: - raise exception.Error(_('Failed to mount filesystem: %s') - % err) - + # We can only loopback mount raw images. If the device isn't there, + # it's normally because it's a .vmdk or a .vdi etc + if not os.path.exists(mapped_device): + raise exception.Error('Mapped device was not found (we can' + ' only inject raw disk images): %s' % + mapped_device) + + # Configure ext2fs so that it doesn't auto-check every N boots + out, err = utils.execute('sudo', 'tune2fs', + '-c', 0, '-i', 0, mapped_device) + + tmpdir = tempfile.mkdtemp() try: - inject_data_into_fs(tmpdir, key, net, utils.execute) + # mount loopback to dir + out, err = utils.execute( + 'sudo', 'mount', mapped_device, tmpdir) + if err: + raise exception.Error(_('Failed to mount filesystem: %s') + % err) + + try: + inject_data_into_fs(tmpdir, key, net, utils.execute) + finally: + # unmount device + utils.execute('sudo', 'umount', mapped_device) finally: - # unmount device - utils.execute('sudo', 'umount', mapped_device) + # remove temporary directory + utils.execute('rmdir', tmpdir) finally: - # remove temporary directory - utils.execute('rmdir', tmpdir) if not partition is None: # remove partitions utils.execute('sudo', 'kpartx', '-d', device) -- cgit From 5502c2764bd55a2b9c5012fd01d821ee5882aca2 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 12 May 2011 20:07:54 -0500 Subject: Adding basic tests for call_zone_method --- nova/api/openstack/__init__.py | 2 +- nova/tests/api/openstack/test_zones.py | 3 +- nova/tests/test_scheduler.py | 61 +++++++++++++++++++++++++++++++--- 3 files changed, 60 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index b743d306b..5b7f080ad 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -99,7 +99,7 @@ class APIRouter(wsgi.Router): mapper.resource("zone", "zones", controller=zones.Controller(), collection={'detail': 'GET', 'info': 'GET', - 'select': 'GET',}), + 'select': 'GET'}) mapper.resource("user", "users", controller=users.Controller(), collection={'detail': 'GET'}) diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index 879039091..b42b3e7d8 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -91,6 +91,7 @@ GLOBAL_BUILD_PLAN = [ def zone_select(context, specs): return GLOBAL_BUILD_PLAN + class ZonesTest(test.TestCase): def setUp(self): super(ZonesTest, self).setUp() @@ -202,7 +203,7 @@ class ZonesTest(test.TestCase): self.assertEqual(res_dict['zone']['name'], 'darksecret') self.assertEqual(res_dict['zone']['cap1'], 'a;b') self.assertEqual(res_dict['zone']['cap2'], 'c;d') - + def test_zone_select(self): FLAGS.build_plan_encryption_key = 'c286696d887c9aa0611bbb3e2025a45a' self.stubs.Set(api, 'select', zone_select) diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 968ef9d6c..54b3f80fb 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) -- cgit From 35c37d7d74296bf6362ceb675e4f2c2e7b8f994a Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 12 May 2011 18:44:22 -0700 Subject: pep8 --- nova/api/openstack/__init__.py | 2 +- nova/tests/api/openstack/test_zones.py | 3 +- nova/tests/test_zone_aware_scheduler.py | 119 ++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 nova/tests/test_zone_aware_scheduler.py (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index b743d306b..5d45efde6 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -99,7 +99,7 @@ class APIRouter(wsgi.Router): mapper.resource("zone", "zones", controller=zones.Controller(), collection={'detail': 'GET', 'info': 'GET', - 'select': 'GET',}), + 'select': 'GET'}), mapper.resource("user", "users", controller=users.Controller(), collection={'detail': 'GET'}) diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index 879039091..b42b3e7d8 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -91,6 +91,7 @@ GLOBAL_BUILD_PLAN = [ def zone_select(context, specs): return GLOBAL_BUILD_PLAN + class ZonesTest(test.TestCase): def setUp(self): super(ZonesTest, self).setUp() @@ -202,7 +203,7 @@ class ZonesTest(test.TestCase): self.assertEqual(res_dict['zone']['name'], 'darksecret') self.assertEqual(res_dict['zone']['cap1'], 'a;b') self.assertEqual(res_dict['zone']['cap2'], 'c;d') - + def test_zone_select(self): FLAGS.build_plan_encryption_key = 'c286696d887c9aa0611bbb3e2025a45a' self.stubs.Set(api, 'select', zone_select) diff --git a/nova/tests/test_zone_aware_scheduler.py b/nova/tests/test_zone_aware_scheduler.py new file mode 100644 index 000000000..fdcde34c9 --- /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, {}) -- cgit From 31b9cb7b78df8d2d6c0d68f08031b5c3abc8a62e Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 13 May 2011 06:12:18 -0700 Subject: fixup based on Lorin's feedback --- nova/scheduler/zone_aware_scheduler.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 07f86450b..b3d230bd2 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() -- cgit From 6404e2bb02f0736c43ef37292c1143e58903c5db Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 16 May 2011 23:14:51 -0400 Subject: Update comment on RequestExtension class. --- nova/api/openstack/extensions.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 0e729e137..8e77b25fb 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -410,8 +410,11 @@ class ExtensionManager(object): class RequestExtension(object): - """Provide a way to handle custom request data that is sent to core - nova OpenStack API controllers. + """Extend requests and responses of core nova OpenStack API controllers. + + Provide a way to add data to responses and handle custom request data + that is sent to core nova OpenStack API controllers. + """ def __init__(self, method, url_route, handler): self.url_route = url_route -- cgit From f51bd03c9ce5f4248cb6f10e3ed662ae6ba33ebd Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 17 May 2011 15:49:31 +0000 Subject: Instead of using a temp file with openssl, just write directly to stdin --- nova/virt/xenapi/vmops.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 13d7d215b..0074444f8 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -25,7 +25,6 @@ import M2Crypto import os import pickle import subprocess -import tempfile import uuid from nova import context @@ -1163,18 +1162,17 @@ class SimpleDH(object): return mpi def _run_ssl(self, text, which): - base_cmd = ('cat %(tmpfile)s | openssl enc -aes-128-cbc ' - '-a -pass pass:%(shared)s -nosalt %(dec_flag)s') + base_cmd = ('openssl enc -aes-128-cbc -a -pass pass:%(shared)s ' + '-nosalt %(dec_flag)s') if which.lower()[0] == 'd': dec_flag = ' -d' else: dec_flag = '' - fd, tmpfile = tempfile.mkstemp() - os.close(fd) - file(tmpfile, 'w').write(text) shared = self._shared cmd = base_cmd % locals() proc = _runproc(cmd) + proc.stdin.write(text) + proc.stdin.close() proc.wait() err = proc.stderr.read() if err: -- cgit From 01f7b0aa8de984baa27be50171526696aac48c0c Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 18 May 2011 14:46:39 -0500 Subject: Adding FlagNotSet exception --- nova/api/openstack/zones.py | 3 ++- nova/exception.py | 4 ++++ nova/tests/api/openstack/test_zones.py | 12 ++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 70653dc0e..145b24347 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -18,6 +18,7 @@ import urlparse from nova import crypto from nova import db +from nova import exception from nova import flags from nova import log as logging from nova.api.openstack import common @@ -54,7 +55,7 @@ def _scrub_zone(zone): def check_encryption_key(func): def wrapped(*args, **kwargs): if not FLAGS.build_plan_encryption_key: - raise exception.Error(_("--build_plan_encryption_key not set")) + raise exception.FlagNotSet(flag='build_plan_encryption_key') return func(*args, **kwargs) return wrapped diff --git a/nova/exception.py b/nova/exception.py index cf6069454..56c20d111 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -255,6 +255,10 @@ class NotFound(NovaException): super(NotFound, self).__init__(**kwargs) +class FlagNotSet(NotFound): + message = _("Required flag %(flag)s not set.") + + class InstanceNotFound(NotFound): message = _("Instance %(instance_id)s could not be found.") diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index b42b3e7d8..62a763c6f 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -21,6 +21,7 @@ import json import nova.db from nova import context from nova import crypto +from nova import exception from nova import flags from nova import test from nova.api.openstack import zones @@ -120,6 +121,17 @@ class ZonesTest(test.TestCase): FLAGS.zone_capabilities = self.old_zone_capabilities super(ZonesTest, self).tearDown() + def test_check_encryption_key(self): + @zones.check_encryption_key + def test_func(): + return 42 + + self.assertRaises(exception.FlagNotSet, test_func) + + FLAGS.build_plan_encryption_key = "something" + ret = test_func() + self.assertEqual(ret, 42) + def test_get_zone_list_scheduler(self): self.stubs.Set(api, '_call_scheduler', zone_get_all_scheduler) req = webob.Request.blank('/v1.0/zones') -- cgit From d44a4728c23cebd1eaa7615c3b439e44972750cc Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 18 May 2011 15:14:24 -0500 Subject: On second thought, removing decorator --- nova/api/openstack/zones.py | 12 +++--------- nova/tests/api/openstack/test_zones.py | 11 ----------- 2 files changed, 3 insertions(+), 20 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 145b24347..af73d8f6d 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -52,14 +52,6 @@ def _scrub_zone(zone): 'deleted', 'deleted_at', 'updated_at')) -def check_encryption_key(func): - def wrapped(*args, **kwargs): - if not FLAGS.build_plan_encryption_key: - raise exception.FlagNotSet(flag='build_plan_encryption_key') - return func(*args, **kwargs) - return wrapped - - class Controller(common.OpenstackController): _serialization_metadata = { @@ -117,7 +109,6 @@ class Controller(common.OpenstackController): zone = api.zone_update(context, zone_id, env["zone"]) return dict(zone=_scrub_zone(zone)) - @check_encryption_key def select(self, req): """Returns a weighted list of costs to create instances of desired capabilities.""" @@ -138,6 +129,9 @@ class Controller(common.OpenstackController): """Remove all the confidential data and return a sanitized version of the build plan. Include an encrypted full version of the weighting entry so we can get back to it later.""" + if not FLAGS.build_plan_encryption_key: + raise exception.FlagNotSet(flag='build_plan_encryption_key') + encryptor = crypto.encryptor(FLAGS.build_plan_encryption_key) cooked = [] for entry in build_plan: diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index 62a763c6f..fa2e05033 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -121,17 +121,6 @@ class ZonesTest(test.TestCase): FLAGS.zone_capabilities = self.old_zone_capabilities super(ZonesTest, self).tearDown() - def test_check_encryption_key(self): - @zones.check_encryption_key - def test_func(): - return 42 - - self.assertRaises(exception.FlagNotSet, test_func) - - FLAGS.build_plan_encryption_key = "something" - ret = test_func() - self.assertEqual(ret, 42) - def test_get_zone_list_scheduler(self): self.stubs.Set(api, '_call_scheduler', zone_get_all_scheduler) req = webob.Request.blank('/v1.0/zones') -- cgit From b2db9895c271825d1a58ade9c6de85ac90f760a7 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 19 May 2011 22:56:23 -0400 Subject: fixed pep8 issue --- nova/virt/images.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/virt/images.py b/nova/virt/images.py index 8689c0ed3..02c898fda 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -42,6 +42,7 @@ def fetch(image_id, path, _user, _project): metadata = image_service.get(elevated, image_id, image_file) return metadata + # TODO(vish): xenapi should use the glance client code directly instead # of retrieving the image using this method. def image_url(image): -- cgit From 1faf77248409bac4226822d48cfdd6213be5e24c Mon Sep 17 00:00:00 2001 From: Andrey Brindeyev Date: Fri, 20 May 2011 17:57:04 +0400 Subject: 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. --- nova/network/linux_net.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 85c4c278c..1fe77d7f1 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -27,6 +27,7 @@ from nova import exception from nova import flags from nova import log as logging from nova import utils +from IPy import IP LOG = logging.getLogger("nova.linux_net") @@ -698,6 +699,7 @@ def _dnsmasq_cmd(net): '--listen-address=%s' % net['gateway'], '--except-interface=lo', '--dhcp-range=%s,static,120s' % net['dhcp_start'], + '--dhcp-lease-max=%s' % IP(net['cidr']).len(), '--dhcp-hostsfile=%s' % _dhcp_file(net['bridge'], 'conf'), '--dhcp-script=%s' % FLAGS.dhcpbridge, '--leasefile-ro'] -- cgit From f8e808504249aaa7a5278f6c91f7d300ba6dc8f8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 20 May 2011 11:21:52 -0700 Subject: synchronize vlan creation --- nova/network/linux_net.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 85c4c278c..8e43c5a67 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -449,6 +449,7 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): ensure_bridge(bridge, interface, net_attrs) +@utils.synchronized('ensure_vlan', external=True) def ensure_vlan(vlan_num): """Create a vlan unless it already exists.""" interface = 'vlan%s' % vlan_num -- cgit From 968523ff49fc9d5aed7182b4084b2d7ec9f567ba Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Sat, 21 May 2011 13:00:06 +0100 Subject: When adding a keypair that already exists, give a friendly error and no traceback in nova-api. --- nova/api/ec2/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index cd59340bd..4686c32ec 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -338,6 +338,10 @@ class Executor(wsgi.Application): else: return self._error(req, context, type(ex).__name__, unicode(ex)) + except exception.KeyPairExists as ex: + LOG.info(_('KeyPairExists raised: %s'), unicode(ex), + context=context) + return self._error(req, context, type(ex).__name__, unicode(ex)) except Exception as ex: extra = {'environment': req.environ} LOG.exception(_('Unexpected error raised: %s'), unicode(ex), -- cgit From 63dbfeb2cb5b834a0cb4dd23c30522f540ac539b Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Mon, 23 May 2011 22:15:10 +0100 Subject: Added test case for attempting to create a duplicate keypair --- nova/tests/test_api.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 97f401b87..7c0331eff 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -224,6 +224,29 @@ class ApiEc2TestCase(test.TestCase): self.manager.delete_project(project) self.manager.delete_user(user) + def test_create_duplicate_key_pair(self): + """Test that, after successfully generating a keypair, + requesting a second keypair with the same name fails sanely""" + self.expect_http() + self.mox.ReplayAll() + keyname = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ + for x in range(random.randint(4, 8))) + user = self.manager.create_user('fake', 'fake', 'fake') + project = self.manager.create_project('fake', 'fake', 'fake') + # NOTE(vish): create depends on pool, so call helper directly + self.ec2.create_key_pair('test') + + try: + self.ec2.create_key_pair('test') + except EC2ResponseError, e: + if e.code == 'KeyPairExists': + pass + else: + self.fail("Unexpected EC2ResponseError: %s " + "(expected KeyPairExists)" % e.code) + else: + self.fail('Exception not raised.') + def test_get_all_security_groups(self): """Test that we can retrieve security groups""" self.expect_http() -- cgit From 107eedf06ba6d27e65169302bd51a391e6e104f7 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Mon, 23 May 2011 22:15:41 +0100 Subject: Changed ec2 api dupe key exception log handler info->debug --- nova/api/ec2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 4686c32ec..c13993dd3 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -339,7 +339,7 @@ class Executor(wsgi.Application): return self._error(req, context, type(ex).__name__, unicode(ex)) except exception.KeyPairExists as ex: - LOG.info(_('KeyPairExists raised: %s'), unicode(ex), + LOG.debug(_('KeyPairExists raised: %s'), unicode(ex), context=context) return self._error(req, context, type(ex).__name__, unicode(ex)) except Exception as ex: -- cgit From 999d1a4edb1f6992e3bb85e7a45ebd735e8cdb42 Mon Sep 17 00:00:00 2001 From: termie Date: Tue, 24 May 2011 13:19:09 -0700 Subject: Properly reparse flags when adding dynamic flags --- nova/flags.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index 32cb6efa8..7304700f1 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -110,7 +110,7 @@ class FlagValues(gflags.FlagValues): return name in self.__dict__['__dirty'] def ClearDirty(self): - self.__dict__['__is_dirty'] = [] + self.__dict__['__dirty'] = [] def WasAlreadyParsed(self): return self.__dict__['__was_already_parsed'] @@ -119,11 +119,11 @@ class FlagValues(gflags.FlagValues): if '__stored_argv' not in self.__dict__: return new_flags = FlagValues(self) - for k in self.__dict__['__dirty']: + for k in self.FlagDict().iterkeys(): new_flags[k] = gflags.FlagValues.__getitem__(self, k) new_flags(self.__dict__['__stored_argv']) - for k in self.__dict__['__dirty']: + for k in new_flags.FlagDict().iterkeys(): setattr(self, k, getattr(new_flags, k)) self.ClearDirty() -- cgit From d8e1f0b6b3ab7a8549773910815b1d2a5d1b8f2f Mon Sep 17 00:00:00 2001 From: termie Date: Tue, 24 May 2011 13:19:09 -0700 Subject: add a test from vish and fix the issues --- nova/flags.py | 1 + nova/tests/test_flags.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index 7304700f1..9eaac5596 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -122,6 +122,7 @@ class FlagValues(gflags.FlagValues): for k in self.FlagDict().iterkeys(): new_flags[k] = gflags.FlagValues.__getitem__(self, k) + new_flags.Reset() new_flags(self.__dict__['__stored_argv']) for k in new_flags.FlagDict().iterkeys(): setattr(self, k, getattr(new_flags, k)) diff --git a/nova/tests/test_flags.py b/nova/tests/test_flags.py index 707300fcf..05319d91f 100644 --- a/nova/tests/test_flags.py +++ b/nova/tests/test_flags.py @@ -91,6 +91,20 @@ class FlagsTestCase(test.TestCase): self.assert_('runtime_answer' in self.global_FLAGS) self.assertEqual(self.global_FLAGS.runtime_answer, 60) + def test_long_vs_short_flags(self): + flags.DEFINE_string('duplicate_answer_long', 'val', 'desc', + flag_values=self.global_FLAGS) + argv = ['flags_test', '--duplicate_answer=60', 'extra_arg'] + args = self.global_FLAGS(argv) + + self.assert_('duplicate_answer' not in self.global_FLAGS) + self.assert_(self.global_FLAGS.duplicate_answer_long, 60) + + flags.DEFINE_integer('duplicate_answer', 60, 'desc', + flag_values=self.global_FLAGS) + self.assertEqual(self.global_FLAGS.duplicate_answer, 60) + self.assertEqual(self.global_FLAGS.duplicate_answer_long, 'val') + def test_flag_leak_left(self): self.assertEqual(FLAGS.flags_unittest, 'foo') FLAGS.flags_unittest = 'bar' -- cgit From 6be49381fc1c232e99de3e9774fb6c3e5b685fcf Mon Sep 17 00:00:00 2001 From: termie Date: Tue, 24 May 2011 13:19:09 -0700 Subject: make fake_flags set defaults instead of runtime values --- nova/tests/fake_flags.py | 28 ++++++++++++++-------------- nova/tests/real_flags.py | 26 -------------------------- 2 files changed, 14 insertions(+), 40 deletions(-) delete mode 100644 nova/tests/real_flags.py (limited to 'nova') diff --git a/nova/tests/fake_flags.py b/nova/tests/fake_flags.py index 5d7ca98b5..ecefc464a 100644 --- a/nova/tests/fake_flags.py +++ b/nova/tests/fake_flags.py @@ -21,24 +21,24 @@ from nova import flags FLAGS = flags.FLAGS flags.DECLARE('volume_driver', 'nova.volume.manager') -FLAGS.volume_driver = 'nova.volume.driver.FakeISCSIDriver' -FLAGS.connection_type = 'fake' -FLAGS.fake_rabbit = True +FLAGS['volume_driver'].SetDefault('nova.volume.driver.FakeISCSIDriver') +FLAGS['connection_type'].SetDefault('fake') +FLAGS['fake_rabbit'].SetDefault(True) flags.DECLARE('auth_driver', 'nova.auth.manager') -FLAGS.auth_driver = 'nova.auth.dbdriver.DbDriver' +FLAGS['auth_driver'].SetDefault('nova.auth.dbdriver.DbDriver') flags.DECLARE('network_size', 'nova.network.manager') flags.DECLARE('num_networks', 'nova.network.manager') flags.DECLARE('fake_network', 'nova.network.manager') -FLAGS.network_size = 8 -FLAGS.num_networks = 2 -FLAGS.fake_network = True -FLAGS.image_service = 'nova.image.local.LocalImageService' +FLAGS['network_size'].SetDefault(8) +FLAGS['num_networks'].SetDefault(2) +FLAGS['fake_network'].SetDefault(True) +FLAGS['image_service'].SetDefault('nova.image.local.LocalImageService') flags.DECLARE('num_shelves', 'nova.volume.driver') flags.DECLARE('blades_per_shelf', 'nova.volume.driver') flags.DECLARE('iscsi_num_targets', 'nova.volume.driver') -FLAGS.num_shelves = 2 -FLAGS.blades_per_shelf = 4 -FLAGS.iscsi_num_targets = 8 -FLAGS.verbose = True -FLAGS.sqlite_db = "tests.sqlite" -FLAGS.use_ipv6 = True +FLAGS['num_shelves'].SetDefault(2) +FLAGS['blades_per_shelf'].SetDefault(4) +FLAGS['iscsi_num_targets'].SetDefault(8) +FLAGS['verbose'].SetDefault(True) +FLAGS['sqlite_db'].SetDefault("tests.sqlite") +FLAGS['use_ipv6'].SetDefault(True) diff --git a/nova/tests/real_flags.py b/nova/tests/real_flags.py deleted file mode 100644 index 71da04992..000000000 --- a/nova/tests/real_flags.py +++ /dev/null @@ -1,26 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from nova import flags - -FLAGS = flags.FLAGS - -FLAGS.connection_type = 'libvirt' -FLAGS.fake_rabbit = False -FLAGS.fake_network = False -FLAGS.verbose = False -- cgit