From 5a9d2eb44ced0affe143e6274c9c9326f1c2d7da Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 18 Mar 2011 11:49:11 -0700 Subject: [PATCH 01/23] 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(+) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index cadda97dbbd7..6fe01b0e9b54 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 cf8ee7eff723..03b1ad2fc132 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -279,6 +279,22 @@ class CloudTestCase(test.TestCase): self.assertTrue(filter(lambda k: k['keyName'] == 'test1', keys)) self.assertTrue(filter(lambda k: k['keyName'] == 'test2', keys)) + def test_import_public_key(self): + result = self.cloud.import_public_key(self.context, + 'testimportkey', 'mytestpubkey', 'mytestfprint') + self.assertTrue(result) + keydata = db.key_pair_get(self.context, + self.context.user.id, + 'testimportkey') + print "PUBLIC_KEY:" + file = open('/tmp/blah', 'w') + file.write(keydata['public_key']) + file.close() + print keydata['public_key'] + self.assertEqual('mytestpubkey', keydata['public_key']) + self.assertEqual('mytestfprint', keydata['fingerprint']) + self.assertTrue(1) + def test_delete_key_pair(self): self._create_key('test') self.cloud.delete_key_pair(self.context, 'test') From 15a40f842cb8a4362fbd82e36e3df4af7ab46a84 Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 18 Mar 2011 12:17:40 -0700 Subject: [PATCH 02/23] cleaned up tests stubs that were accidentally checked in --- nova/tests/test_cloud.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 03b1ad2fc132..3a266c996f77 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -281,19 +281,15 @@ class CloudTestCase(test.TestCase): def test_import_public_key(self): result = self.cloud.import_public_key(self.context, - 'testimportkey', 'mytestpubkey', 'mytestfprint') + 'testimportkey', + 'mytestpubkey', + 'mytestfprint') self.assertTrue(result) keydata = db.key_pair_get(self.context, self.context.user.id, 'testimportkey') - print "PUBLIC_KEY:" - file = open('/tmp/blah', 'w') - file.write(keydata['public_key']) - file.close() - print keydata['public_key'] self.assertEqual('mytestpubkey', keydata['public_key']) self.assertEqual('mytestfprint', keydata['fingerprint']) - self.assertTrue(1) def test_delete_key_pair(self): self._create_key('test') From a105fd449a0b91cde3ab86cc552705dfe50e3f6d Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 21 Mar 2011 14:35:19 -0700 Subject: [PATCH 03/23] 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 diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 6fe01b0e9b54..8ec74fbe07a7 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 000000000000..715bca27a27f --- /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 000000000000..d4cf2bc0d857 --- /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 3a266c996f77..c49a39ed0a0d 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -280,16 +280,34 @@ class CloudTestCase(test.TestCase): self.assertTrue(filter(lambda k: k['keyName'] == 'test2', keys)) def test_import_public_key(self): - result = self.cloud.import_public_key(self.context, - 'testimportkey', - 'mytestpubkey', - 'mytestfprint') - self.assertTrue(result) + # test when user provides all values + result1 = self.cloud.import_public_key(self.context, + 'testimportkey1', + 'mytestpubkey', + 'mytestfprint') + self.assertTrue(result1) keydata = db.key_pair_get(self.context, - self.context.user.id, - 'testimportkey') + self.context.user.id, + 'testimportkey1') self.assertEqual('mytestpubkey', keydata['public_key']) self.assertEqual('mytestfprint', keydata['fingerprint']) + # test when user omits fingerprint + pubkey_path = os.path.join(os.path.dirname(__file__), 'public_key') + f = open(pubkey_path + '/dummy.pub', 'r') + dummypub = f.readline().rstrip() + f.close + f = open(pubkey_path + '/dummy.fingerprint', 'r') + dummyfprint = f.readline().rstrip() + f.close + result2 = self.cloud.import_public_key(self.context, + 'testimportkey2', + dummypub) + self.assertTrue(result2) + keydata = db.key_pair_get(self.context, + self.context.user.id, + 'testimportkey2') + self.assertEqual(dummypub, keydata['public_key']) + self.assertEqual(dummyfprint, keydata['fingerprint']) def test_delete_key_pair(self): self._create_key('test') From 7cd6e9f1cf62ff5628ae4680aa66ada676c8c288 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 23 Mar 2011 11:16:22 -0700 Subject: [PATCH 04/23] added myself to authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 7993955e2f0d..c1e164896dfc 100644 --- a/Authors +++ b/Authors @@ -28,6 +28,7 @@ Jesse Andrews Joe Heck Joel Moore John Dewey +John Tran Jonathan Bryce Jordan Rinke Josh Durgin From 12ec5f5c0d6a88779780b15b6ef38a016d6aae4a Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 12 Apr 2011 08:04:55 -0700 Subject: [PATCH 05/23] 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(+) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d2045a557421..dd1fd9383672 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) From c04b0caca4a725be390271be30bf8a034aa5ca9d Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 13 Apr 2011 10:10:40 -0700 Subject: [PATCH 06/23] Minor formatting cleanup --- nova/virt/xenapi/vm_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index dd1fd9383672..32a617ef485a 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) From 1c1a06c3731dd82b331f317ba52edbfe2110a40e Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 9 May 2011 11:47:33 -0400 Subject: [PATCH 07/23] clean up unused functions from virt/images.py --- nova/virt/images.py | 61 --------------------------------------------- 1 file changed, 61 deletions(-) diff --git a/nova/virt/images.py b/nova/virt/images.py index 2e3f2ee4d7ab..1eb6f4a5f2c8 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): From 09b795b8d6f0b925dbd4bcd203f471607c42f368 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 9 May 2011 19:46:15 -0400 Subject: [PATCH 08/23] got rid of unnecessary imports --- nova/virt/images.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/nova/virt/images.py b/nova/virt/images.py index 1eb6f4a5f2c8..8689c0ed3830 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 From 22c33d80ce040f09c9bcd7584cf1165cf769e192 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 12 May 2011 10:55:04 -0400 Subject: [PATCH 09/23] Initial work on request extensions. --- nova/api/openstack/extensions.py | 78 +++++++++++++++++++ .../api/openstack/extensions/foxinsocks.py | 3 + nova/tests/api/openstack/test_extensions.py | 47 ++++++++++- 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 7ea7afef6646..e6dd228ec714 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 0860b51ac719..7699ffb564ca 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 481d34ed140f..7fadb5b690fe 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']) From ce2b13d9fb30c0afbcff97f434d7423cad39b8b9 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 12 May 2011 12:52:32 -0400 Subject: [PATCH 10/23] Remove ResponseExtensions. The new RequestExtension covers both use cases. --- nova/api/openstack/extensions.py | 88 ++----------------- .../api/openstack/extensions/foxinsocks.py | 15 ++-- nova/tests/api/openstack/test_extensions.py | 87 ++++-------------- 3 files changed, 30 insertions(+), 160 deletions(-) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index e6dd228ec714..3a776346312d 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 7699ffb564ca..b3f30c4e5b19 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 7fadb5b690fe..c63474dea54c 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,59 +183,6 @@ class ActionExtensionTest(unittest.TestCase): self.assertEqual(404, response.status_int) -class ResponseExtensionTest(unittest.TestCase): - - def setUp(self): - super(ResponseExtensionTest, 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(ResponseExtensionTest, self).tearDown() - - def test_get_resources_with_stub_mgr(self): - - test_resp = "Gooey goo for chewy chewing!" - - def _resp_handler(res): - # only handle JSON responses - data = json.loads(res.body) - data['flavor']['googoose'] = test_resp - return data - - resp_ext = extensions.ResponseExtension('GET', - '/v1.1/flavors/:(id)', - _resp_handler) - - manager = StubExtensionManager(None, None, resp_ext) - app = fakes.wsgi_app() - ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/v1.1/flavors/1") - 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']) - - 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.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("Pig Bands!", response_data['big_bands']) - - class RequestExtensionTest(unittest.TestCase): def setUp(self): @@ -258,24 +197,36 @@ class RequestExtensionTest(unittest.TestCase): self.stubs.UnsetAll() super(RequestExtensionTest, self).tearDown() - def test_post_request_extension_with_stub_mgr(self): + def test_get_resources_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') + data['flavor']['googoose'] = req.GET.get('chewing') return data - resp_ext = extensions.RequestExtension('GET', + req_ext = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', _req_handler) - manager = StubExtensionManager(None, 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?test_param=foo") + 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('foo', response_data['flavor']['googoose']) + self.assertEqual('bluegoo', response_data['flavor']['googoose']) + + def test_get_resources_with_mgr(self): + + app = fakes.wsgi_app() + ext_midware = extensions.ExtensionMiddleware(app) + 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('newblue', response_data['flavor']['googoose']) + self.assertEqual("Pig Bands!", response_data['big_bands']) From e03921c2799acf36083eb13c3134b861bc4732a6 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 12 May 2011 14:37:15 -0400 Subject: [PATCH 11/23] 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(-) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 3a776346312d..ac79b9310d39 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 b3f30c4e5b19..f8e31589ab6d 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 c63474dea54c..544298602ee1 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)', From 27b5de353aee88d37c369bb5b019a746116732c0 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 12 May 2011 14:45:39 -0400 Subject: [PATCH 12/23] Variable renaming. --- nova/tests/api/openstack/extensions/foxinsocks.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index f8e31589ab6d..dbdd0928a15d 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): From e72667cb125f1d970f302bb18f051380fac0711d Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 12 May 2011 14:52:54 -0400 Subject: [PATCH 13/23] Update comment. --- nova/api/openstack/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index ac79b9310d39..0e729e137b4c 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 From e7662bfcead8df8cc1fc655af6da15dc47777565 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 12 May 2011 13:42:04 -0700 Subject: [PATCH 14/23] 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 | 48 ++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/nova/virt/disk.py b/nova/virt/disk.py index ddea1a1f7adc..f8aea1f34d52 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) From 93fad121c209af015fc2f359c55ad28bba389941 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Mon, 16 May 2011 08:23:10 -0500 Subject: [PATCH 15/23] Added lines to include tools/* (except ajaxterm) in pep8 tests --- run_tests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/run_tests.sh b/run_tests.sh index e3a0bd2435aa..a9def3e358d7 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -61,6 +61,8 @@ function run_pep8 { echo "Running pep8 ..." srcfiles=`find bin -type f ! -name "nova.conf*"` srcfiles+=" nova setup.py plugins/xenserver/xenapi/etc/xapi.d/plugins/glance" + srcfiles+=" tools/clean-vlans tools/esx/guest_tool.py tools/euca-get-ajax-console" + srcfiles+=" tools/install_venv.py tools/nova-debug" pep8 --repeat --show-pep8 --show-source --exclude=vcsversion.py ${srcfiles} } From e2a1258458e121f192d639d2804b3d3efa40495f Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Mon, 16 May 2011 08:26:39 -0500 Subject: [PATCH 16/23] Removed tools/clean_vlans and tools/nova-debug from pep8 tests as they are shell scripts --- run_tests.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/run_tests.sh b/run_tests.sh index a9def3e358d7..2e4f4cd22179 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -61,8 +61,7 @@ function run_pep8 { echo "Running pep8 ..." srcfiles=`find bin -type f ! -name "nova.conf*"` srcfiles+=" nova setup.py plugins/xenserver/xenapi/etc/xapi.d/plugins/glance" - srcfiles+=" tools/clean-vlans tools/esx/guest_tool.py tools/euca-get-ajax-console" - srcfiles+=" tools/install_venv.py tools/nova-debug" + srcfiles+=" tools/esx/guest_tool.py tools/euca-get-ajax-console tools/install_venv.py" pep8 --repeat --show-pep8 --show-source --exclude=vcsversion.py ${srcfiles} } From ea3b85bc276c268968c06679bf157fe4b0b4b68f Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Mon, 16 May 2011 11:16:55 -0500 Subject: [PATCH 17/23] Fixed all pep8 errors in tools/install_venv.py. All tests pass. --- tools/install_venv.py | 170 ++++++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 82 deletions(-) diff --git a/tools/install_venv.py b/tools/install_venv.py index 8149a3afa882..812b1dd0fb86 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -31,119 +31,125 @@ import sys ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) VENV = os.path.join(ROOT, '.nova-venv') PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires') -TWISTED_NOVA='http://nova.openstack.org/Twisted-10.0.0Nova.tar.gz' -PY_VERSION = "python" + str(sys.version_info[0]) + '.' + str(sys.version_info[1]) +TWISTED_NOVA = 'http://nova.openstack.org/Twisted-10.0.0Nova.tar.gz' +PY_VERSION = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) + def die(message, *args): - print >>sys.stderr, message % args - sys.exit(1) + print >>sys.stderr, message % args + sys.exit(1) + def check_python_version(): - if sys.version_info < (2, 6): - die("Need Python Version >= 2.6") + if sys.version_info < (2, 6): + die("Need Python Version >= 2.6") + def run_command(cmd, redirect_output=True, check_exit_code=True): - """ - Runs a command in an out-of-process shell, returning the - output of that command. Working directory is ROOT. - """ - if redirect_output: - stdout = subprocess.PIPE - else: - stdout = None + """ + Runs a command in an out-of-process shell, returning the + output of that command. Working directory is ROOT. + """ + if redirect_output: + stdout = subprocess.PIPE + else: + stdout = None - proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout) - output = proc.communicate()[0] - if check_exit_code and proc.returncode != 0: - die('Command "%s" failed.\n%s', ' '.join(cmd), output) - return output + proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout) + output = proc.communicate()[0] + if check_exit_code and proc.returncode != 0: + die('Command "%s" failed.\n%s', ' '.join(cmd), output) + return output -HAS_EASY_INSTALL = bool(run_command(['which', 'easy_install'], check_exit_code=False).strip()) -HAS_VIRTUALENV = bool(run_command(['which', 'virtualenv'], check_exit_code=False).strip()) +HAS_EASY_INSTALL = bool(run_command(['which', 'easy_install'], + check_exit_code=False).strip()) +HAS_VIRTUALENV = bool(run_command(['which', 'virtualenv'], + check_exit_code=False).strip()) def check_dependencies(): - """Make sure virtualenv is in the path.""" + """Make sure virtualenv is in the path.""" - if not HAS_VIRTUALENV: - print 'not found.' - # Try installing it via easy_install... - if HAS_EASY_INSTALL: - print 'Installing virtualenv via easy_install...', - if not (run_command(['which', 'easy_install']) and - run_command(['easy_install', 'virtualenv'])): - die('ERROR: virtualenv not found.\n\nNova development requires virtualenv,' - ' please install it using your favorite package management tool') - print 'done.' - print 'done.' + if not HAS_VIRTUALENV: + print 'not found.' + # Try installing it via easy_install... + if HAS_EASY_INSTALL: + print 'Installing virtualenv via easy_install...', + if not (run_command(['which', 'easy_install']) and + run_command(['easy_install', 'virtualenv'])): + die('ERROR: virtualenv not found.\n\nNova development' + ' requires virtualenv, please install it using your' + ' favorite package management tool') + print 'done.' + print 'done.' def create_virtualenv(venv=VENV): - """Creates the virtual environment and installs PIP only into the - virtual environment - """ - print 'Creating venv...', - run_command(['virtualenv', '-q', '--no-site-packages', VENV]) - print 'done.' - print 'Installing pip in virtualenv...', - if not run_command(['tools/with_venv.sh', 'easy_install', 'pip']).strip(): - die("Failed to install pip.") - print 'done.' + """Creates the virtual environment and installs PIP only into the + virtual environment + """ + print 'Creating venv...', + run_command(['virtualenv', '-q', '--no-site-packages', VENV]) + print 'done.' + print 'Installing pip in virtualenv...', + if not run_command(['tools/with_venv.sh', 'easy_install', 'pip']).strip(): + die("Failed to install pip.") + print 'done.' def install_dependencies(venv=VENV): - print 'Installing dependencies with pip (this can take a while)...' - # Install greenlet by hand - just listing it in the requires file does not - # get it in stalled in the right order - run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, 'greenlet'], - redirect_output=False) - run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, '-r', PIP_REQUIRES], - redirect_output=False) - run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, TWISTED_NOVA], - redirect_output=False) + print 'Installing dependencies with pip (this can take a while)...' + # Install greenlet by hand - just listing it in the requires file does not + # get it in stalled in the right order + run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, + 'greenlet'], redirect_output=False) + run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, '-r', + PIP_REQUIRES], redirect_output=False) + run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, + TWISTED_NOVA], redirect_output=False) - - # Tell the virtual env how to "import nova" - pthfile = os.path.join(venv, "lib", PY_VERSION, "site-packages", "nova.pth") - f = open(pthfile, 'w') - f.write("%s\n" % ROOT) - # Patch eventlet (see FAQ # 1485) - patchsrc = os.path.join(ROOT, 'tools', 'eventlet-patch') - patchfile = os.path.join(venv, "lib", PY_VERSION, "site-packages", "eventlet", - "green", "subprocess.py") - patch_cmd = "patch %s %s" % (patchfile, patchsrc) - os.system(patch_cmd) + # Tell the virtual env how to "import nova" + pthfile = os.path.join(venv, "lib", PY_VERSION, "site-packages", + "nova.pth") + f = open(pthfile, 'w') + f.write("%s\n" % ROOT) + # Patch eventlet (see FAQ # 1485) + patchsrc = os.path.join(ROOT, 'tools', 'eventlet-patch') + patchfile = os.path.join(venv, "lib", PY_VERSION, "site-packages", + "eventlet", "green", "subprocess.py") + patch_cmd = "patch %s %s" % (patchfile, patchsrc) + os.system(patch_cmd) def print_help(): - help = """ - Nova development environment setup is complete. + help = """ + Nova development environment setup is complete. - Nova development uses virtualenv to track and manage Python dependencies - while in development and testing. + Nova development uses virtualenv to track and manage Python dependencies + while in development and testing. - To activate the Nova virtualenv for the extent of your current shell session - you can run: + To activate the Nova virtualenv for the extent of your current shell + session you can run: - $ source .nova-venv/bin/activate + $ source .nova-venv/bin/activate - Or, if you prefer, you can run commands in the virtualenv on a case by case - basis by running: + Or, if you prefer, you can run commands in the virtualenv on a case by case + basis by running: - $ tools/with_venv.sh + $ tools/with_venv.sh - Also, make test will automatically use the virtualenv. - """ - print help + Also, make test will automatically use the virtualenv. + """ + print help def main(argv): - check_python_version() - check_dependencies() - create_virtualenv() - install_dependencies() - print_help() + check_python_version() + check_dependencies() + create_virtualenv() + install_dependencies() + print_help() if __name__ == '__main__': - main(sys.argv) + main(sys.argv) From 67148b1093a7ff880f90a164a6cafa24b89ba951 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Mon, 16 May 2011 20:08:51 -0500 Subject: [PATCH 18/23] Added opt-out system for excluding files/dirs from pep8 (using GLOBIGNORE). --- run_tests.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/run_tests.sh b/run_tests.sh index 2e4f4cd22179..9aa5554842d1 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -59,9 +59,14 @@ function run_tests { function run_pep8 { echo "Running pep8 ..." + # Opt-out files from pep8 + ignore_scripts="*.sh:*nova-debug:*clean-vlans" + ignore_files="*eventlet-patch:*pip-requires" + ignore_dirs="*ajaxterm*" + GLOBIGNORE="$ignore_scripts:$ignore_files:$ignore_dirs" srcfiles=`find bin -type f ! -name "nova.conf*"` + srcfiles+=" `find tools/*`" srcfiles+=" nova setup.py plugins/xenserver/xenapi/etc/xapi.d/plugins/glance" - srcfiles+=" tools/esx/guest_tool.py tools/euca-get-ajax-console tools/install_venv.py" pep8 --repeat --show-pep8 --show-source --exclude=vcsversion.py ${srcfiles} } From 6404e2bb02f0736c43ef37292c1143e58903c5db Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 16 May 2011 23:14:51 -0400 Subject: [PATCH 19/23] Update comment on RequestExtension class. --- nova/api/openstack/extensions.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 0e729e137b4c..8e77b25fb700 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 From f51bd03c9ce5f4248cb6f10e3ed662ae6ba33ebd Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 17 May 2011 15:49:31 +0000 Subject: [PATCH 21/23] 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(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 13d7d215bf73..0074444f8eab 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: From b2db9895c271825d1a58ade9c6de85ac90f760a7 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 19 May 2011 22:56:23 -0400 Subject: [PATCH 23/23] fixed pep8 issue --- nova/virt/images.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/virt/images.py b/nova/virt/images.py index 8689c0ed3830..02c898fda3b9 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):