From b344877bdf24985dea5342060c989a9d06fe0964 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 25 Feb 2011 13:01:32 -0800 Subject: add a caching layer to the has_role call to increase performance --- nova/api/ec2/__init__.py | 6 ++--- nova/auth/manager.py | 58 +++++++++++++++++++++++++++++++++++------------- nova/flags.py | 2 ++ 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 5adc2c075..7a9c4f957 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -46,8 +46,6 @@ flags.DEFINE_integer('lockout_minutes', 15, 'Number of minutes to lockout if triggered.') flags.DEFINE_integer('lockout_window', 15, 'Number of minutes for lockout window.') -flags.DEFINE_list('lockout_memcached_servers', None, - 'Memcached servers or None for in process cache.') class RequestLogging(wsgi.Middleware): @@ -104,11 +102,11 @@ class Lockout(wsgi.Middleware): def __init__(self, application): """middleware can use fake for testing.""" - if FLAGS.lockout_memcached_servers: + if FLAGS.memcached_servers: import memcache else: from nova import fakememcache as memcache - self.mc = memcache.Client(FLAGS.lockout_memcached_servers, + self.mc = memcache.Client(FLAGS.memcached_servers, debug=0) super(Lockout, self).__init__(application) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 450ab803a..906734799 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -214,6 +214,13 @@ class AuthManager(object): if driver or not getattr(self, 'driver', None): self.driver = utils.import_class(driver or FLAGS.auth_driver) + if FLAGS.memcached_servers: + import memcache + else: + from nova import fakememcache as memcache + self.mc = memcache.Client(FLAGS.memcached_servers, + debug=0) + def authenticate(self, access, signature, params, verb='GET', server_string='127.0.0.1:8773', path='/', check_type='ec2', headers=None): @@ -351,6 +358,25 @@ class AuthManager(object): if self.has_role(user, role): return True + def _build_mc_key(self, user, role, project=None): + return "rolecache-%s-%s-%s" % (User.safe_id(user), role, + (Project.safe_id(project) if project else 'None')) + + def _clear_mc_key(self, user, role, project=None): + # (anthony) it would be better to delete the key + self.mc.set(self._build_mc_key(user, role, project), None) + + def _has_role(self, user, role, project=None): + with self.driver() as drv: + mc_key = self._build_mc_key(user, role, project) + rslt = self.mc.get(mc_key) + if rslt == None: + rslt = drv.has_role(user, role, project) + self.mc.set(mc_key, rslt) + return rslt + else: + return rslt + def has_role(self, user, role, project=None): """Checks existence of role for user @@ -374,24 +400,24 @@ class AuthManager(object): @rtype: bool @return: True if the user has the role. """ - with self.driver() as drv: - if role == 'projectmanager': - if not project: - raise exception.Error(_("Must specify project")) - return self.is_project_manager(user, project) + if role == 'projectmanager': + if not project: + raise exception.Error(_("Must specify project")) + return self.is_project_manager(user, project) + + global_role = self._has_role(User.safe_id(user), + role, + None) - global_role = drv.has_role(User.safe_id(user), - role, - None) - if not global_role: - return global_role + if not global_role: + return global_role - if not project or role in FLAGS.global_roles: - return global_role + if not project or role in FLAGS.global_roles: + return global_role - return drv.has_role(User.safe_id(user), - role, - Project.safe_id(project)) + return self._has_role(User.safe_id(user), + role, + Project.safe_id(project)) def add_role(self, user, role, project=None): """Adds role for user @@ -423,6 +449,7 @@ class AuthManager(object): LOG.audit(_("Adding sitewide role %(role)s to user %(uid)s") % locals()) with self.driver() as drv: + self._clear_mc_key(uid, role, pid) drv.add_role(uid, role, pid) def remove_role(self, user, role, project=None): @@ -451,6 +478,7 @@ class AuthManager(object): LOG.audit(_("Removing sitewide role %(role)s" " from user %(uid)s") % locals()) with self.driver() as drv: + self._clear_mc_key(uid, role, pid) drv.remove_role(uid, role, pid) @staticmethod diff --git a/nova/flags.py b/nova/flags.py index 8cf199b2f..f885de293 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -354,3 +354,5 @@ DEFINE_string('host', socket.gethostname(), DEFINE_string('node_availability_zone', 'nova', 'availability zone of this node') +DEFINE_list('memcached_servers', None, + 'Memcached servers or None for in process cache.') -- cgit From 47c312bb659d0ad49a678c8213093827e6067f31 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 25 Feb 2011 16:41:48 -0800 Subject: only create auth connection if cache misses --- nova/auth/manager.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 906734799..511bc3a64 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -367,15 +367,15 @@ class AuthManager(object): self.mc.set(self._build_mc_key(user, role, project), None) def _has_role(self, user, role, project=None): - with self.driver() as drv: - mc_key = self._build_mc_key(user, role, project) - rslt = self.mc.get(mc_key) - if rslt == None: + mc_key = self._build_mc_key(user, role, project) + rslt = self.mc.get(mc_key) + if rslt == None: + with self.driver() as drv: rslt = drv.has_role(user, role, project) self.mc.set(mc_key, rslt) return rslt - else: - return rslt + else: + return rslt def has_role(self, user, role, project=None): """Checks existence of role for user -- cgit From e0ba72946011b67a218e3c619b3105529bb43e53 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 25 Feb 2011 17:18:41 -0800 Subject: force memcache key to be str --- nova/auth/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 511bc3a64..84c8a6cb2 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -359,8 +359,8 @@ class AuthManager(object): return True def _build_mc_key(self, user, role, project=None): - return "rolecache-%s-%s-%s" % (User.safe_id(user), role, - (Project.safe_id(project) if project else 'None')) + return str("rolecache-%s-%s-%s" % (User.safe_id(user), role, + (Project.safe_id(project) if project else 'None'))) def _clear_mc_key(self, user, role, project=None): # (anthony) it would be better to delete the key -- cgit From 63d799a5ac6172b73708a183f3d952a2c8b53c2b Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 3 Mar 2011 11:56:21 -0600 Subject: Basic notifications drivers and tests --- nova/flags.py | 3 +++ nova/notifier/__init__.py | 19 +++++++++++++++++++ nova/notifier/no_op_notifier.py | 19 +++++++++++++++++++ nova/notifier/rabbit_notifier.py | 24 +++++++++++++++++++++++ nova/tests/test_notifier.py | 41 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+) create mode 100644 nova/notifier/__init__.py create mode 100644 nova/notifier/no_op_notifier.py create mode 100644 nova/notifier/rabbit_notifier.py create mode 100644 nova/tests/test_notifier.py diff --git a/nova/flags.py b/nova/flags.py index 8cf199b2f..7b4723b50 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -354,3 +354,6 @@ DEFINE_string('host', socket.gethostname(), DEFINE_string('node_availability_zone', 'nova', 'availability zone of this node') + +DEFINE_string('notification_driver', 'nova.notifier.no_op_driver.NoopDriver', + 'Default driver for sending notifications') diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py new file mode 100644 index 000000000..3bf60cba9 --- /dev/null +++ b/nova/notifier/__init__.py @@ -0,0 +1,19 @@ +# 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. + +def notify(model): + """Sends a notification using the specified driver""" + driver = FLAGS.notification_driver + driver.notify(model) diff --git a/nova/notifier/no_op_notifier.py b/nova/notifier/no_op_notifier.py new file mode 100644 index 000000000..c0d418564 --- /dev/null +++ b/nova/notifier/no_op_notifier.py @@ -0,0 +1,19 @@ +# 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. + +class NoopNotifier(object): + def notify(self, model): + """Notifies the recipient of the desired event given the model""" + pass diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py new file mode 100644 index 000000000..7a5802fb9 --- /dev/null +++ b/nova/notifier/rabbit_notifier.py @@ -0,0 +1,24 @@ +# 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. + +class RabbitNotifier(object): + """Sends notifications to a specific RabbitMQ server and topic""" + + def __init__(self): + pass + + def notify(self, model): + """Sends a notification to the RabbitMQ""" + pass diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py new file mode 100644 index 000000000..831ae8bf3 --- /dev/null +++ b/nova/tests/test_notifier.py @@ -0,0 +1,41 @@ +# 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. + +from nova import notifier +from nova import test + +import stubout + +class NotifierTestCase(test.TestCase): + """Test case for notifications""" + def setUp(self): + super(NotifierTestCase, self).setUp() + self.stubs = stubout.StubOutForTesting() + + def tearDown(self): + self.stubs.UnsetAll() + super(NotifierTestCase, self).tearDown() + + def test_send_notification(self): + self.notify_called = False + def mock_notify(self, model): + self.notify_called = True + + self.stubs.set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', + mock_notify) + + model = dict(x=1, y=2) + notifier.notify(model) + self.assertEqual(True, self.notify_called) -- cgit From cff74a76e6369989e8006aa9d7c20fde14b31952 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Fri, 4 Mar 2011 19:24:55 +0000 Subject: More unit tests and rabbit hooks --- nova/flags.py | 3 ++- nova/notifier/__init__.py | 11 ++++++++--- nova/notifier/no_op_notifier.py | 2 +- nova/notifier/rabbit_notifier.py | 23 ++++++++++++++++++----- nova/tests/test_notifier.py | 29 ++++++++++++++++++++++++----- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index 7b4723b50..c2259433f 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -355,5 +355,6 @@ DEFINE_string('host', socket.gethostname(), DEFINE_string('node_availability_zone', 'nova', 'availability zone of this node') -DEFINE_string('notification_driver', 'nova.notifier.no_op_driver.NoopDriver', +DEFINE_string('notification_driver', + 'nova.notifier.no_op_notifier.NoopNotifier', 'Default driver for sending notifications') diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index 3bf60cba9..8053b8a0a 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -13,7 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. -def notify(model): +from nova import flags +from nova import utils + +FLAGS = flags.FLAGS + +def notify(event_name, model): """Sends a notification using the specified driver""" - driver = FLAGS.notification_driver - driver.notify(model) + driver = utils.import_class(FLAGS.notification_driver)() + driver.notify(event_name, model) diff --git a/nova/notifier/no_op_notifier.py b/nova/notifier/no_op_notifier.py index c0d418564..3fefe6f8f 100644 --- a/nova/notifier/no_op_notifier.py +++ b/nova/notifier/no_op_notifier.py @@ -14,6 +14,6 @@ # under the License. class NoopNotifier(object): - def notify(self, model): + def notify(self, event_name, model): """Notifies the recipient of the desired event given the model""" pass diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index 7a5802fb9..33cf06566 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -13,12 +13,25 @@ # License for the specific language governing permissions and limitations # under the License. +import json + +import nova.context + +from nova import flags +from nova import rpc + +FLAGS = flags.FLAGS + +flags.DEFINE_string('notification_topic', 'notifications', + 'RabbitMQ topic used for Nova notifications') + class RabbitNotifier(object): """Sends notifications to a specific RabbitMQ server and topic""" + pass - def __init__(self): - pass - - def notify(self, model): + def notify(self, event_name, model): """Sends a notification to the RabbitMQ""" - pass + context = nova.context.get_admin_context() + topic = FLAGS.notification_topic + msg = { 'event_name': event_name, 'model': model.__dict__ } + rpc.cast(context, topic, json.dumps(msg)) diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 831ae8bf3..4d6289e6a 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -13,7 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. +import nova + +from nova import flags from nova import notifier +from nova.notifier import no_op_notifier from nova import test import stubout @@ -30,12 +34,27 @@ class NotifierTestCase(test.TestCase): def test_send_notification(self): self.notify_called = False - def mock_notify(self, model): + def mock_notify(cls, *args): self.notify_called = True - self.stubs.set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', + self.stubs.Set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', mock_notify) - model = dict(x=1, y=2) - notifier.notify(model) - self.assertEqual(True, self.notify_called) + class Mock(object): + pass + notifier.notify('derp', Mock()) + self.assertEqual(self.notify_called, True) + + def test_send_rabbit_notification(self): + self.stubs.Set(nova.flags.FLAGS, 'notification_driver', + 'nova.notifier.rabbit_notifier.RabbitNotifier') + self.mock_cast = False + def mock_cast(cls, *args): + self.mock_cast = True + + class Mock(object): + pass + self.stubs.Set(nova.rpc, 'cast', mock_cast) + notifier.notify('derp', Mock()) + + self.assertEqual(self.mock_cast, True) -- cgit From 91458f29f8b04407471d9d94a5ad165817f72a43 Mon Sep 17 00:00:00 2001 From: Muneyuki Noguchi Date: Fri, 25 Mar 2011 11:47:17 +0900 Subject: Restore volume state on migration failure. --- nova/compute/manager.py | 19 +++++++++++++++++-- nova/virt/libvirt_conn.py | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 468771f46..356d20285 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1003,7 +1003,7 @@ class ComputeManager(manager.SchedulerDependentManager): "Domain not found: no domain with matching name.\" " "This error can be safely ignored.")) - def recover_live_migration(self, ctxt, instance_ref, host=None): + def recover_live_migration(self, ctxt, instance_ref, host=None, dest=None): """Recovers Instance/volume state from migrating -> running. :param ctxt: security context @@ -1011,6 +1011,7 @@ class ComputeManager(manager.SchedulerDependentManager): :param host: DB column value is updated by this hostname. if none, the host instance currently running is selected. + :param dest: destination host """ @@ -1024,7 +1025,21 @@ class ComputeManager(manager.SchedulerDependentManager): 'host': host}) for volume in instance_ref['volumes']: - self.db.volume_update(ctxt, volume['id'], {'status': 'in-use'}) + volume_id = volume['id'] + self.db.volume_update(ctxt, volume_id, {'status': 'in-use'}) + if dest: + topic = self.db.queue_get_for(ctxt, FLAGS.compute_topic, dest) + rpc.call(ctxt, topic, + {"method": "restore_volume_state", + "args": {'volume_id': volume_id}}) + + def restore_volume_state(self, context, volume_id): + """Restore volume state on migration failure. + + :param context: security context + :param volume_id: nova.db.sqlalchemy.models.Volume.id + """ + self.volume_manager.remove_compute_volume(context, volume_id) def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval.""" diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 2cecb010d..5c6baa36e 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1370,7 +1370,7 @@ class LibvirtConnection(driver.ComputeDriver): FLAGS.live_migration_bandwidth) except Exception: - recover_method(ctxt, instance_ref) + recover_method(ctxt, instance_ref, None, dest) raise # Waiting for completion of live_migration. -- cgit From 52f2479aac7b2fc84c23dba9f337cbfcde6e06e2 Mon Sep 17 00:00:00 2001 From: Kevin Bringard Date: Fri, 25 Mar 2011 10:17:51 -0600 Subject: Added a flag to allow a user to specify a dnsmasq_config_file is they would like to fine tune the dnsmasq settings --- nova/network/linux_net.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 06b05366a..560f568a9 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -65,6 +65,8 @@ flags.DEFINE_string('dns_server', None, flags.DEFINE_string('dmz_cidr', '10.128.0.0/24', 'dmz range that should be accepted') +flags.DEFINE_string('dnsmasq_config_file',"", + 'Override the default dnsmasq settings with those in this file') binary_name = os.path.basename(inspect.stack()[-1][1]) @@ -672,7 +674,7 @@ def _dnsmasq_cmd(net): cmd = ['sudo', '-E', 'dnsmasq', '--strict-order', '--bind-interfaces', - '--conf-file=', + ' --conf-file=%s' %FLAGS.dnsmasq_config_file, '--domain=%s' % FLAGS.dhcp_domain, '--pid-file=%s' % _dhcp_file(net['bridge'], 'pid'), '--listen-address=%s' % net['gateway'], -- cgit From 2e106e3c88bc518cbb43faa8f398b7481ee3d255 Mon Sep 17 00:00:00 2001 From: Kevin Bringard Date: Fri, 25 Mar 2011 10:56:36 -0600 Subject: Fixed a typo on line 677 where there was no space between % and FLAGS --- nova/network/linux_net.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 560f568a9..b59a8b7a2 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -674,7 +674,7 @@ def _dnsmasq_cmd(net): cmd = ['sudo', '-E', 'dnsmasq', '--strict-order', '--bind-interfaces', - ' --conf-file=%s' %FLAGS.dnsmasq_config_file, + ' --conf-file=%s' % FLAGS.dnsmasq_config_file, '--domain=%s' % FLAGS.dhcp_domain, '--pid-file=%s' % _dhcp_file(net['bridge'], 'pid'), '--listen-address=%s' % net['gateway'], -- cgit From c400024de45073ccc23a6738c78518365a511562 Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 25 Mar 2011 13:17:51 -0700 Subject: added a simple test for describe_images with mock for detail funciton --- Authors | 1 + nova/tests/test_cloud.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Authors b/Authors index 1679d2dee..39a851951 100644 --- a/Authors +++ b/Authors @@ -29,6 +29,7 @@ Jesse Andrews Joe Heck Joel Moore John Dewey +John Tran Jonathan Bryce Jordan Rinke Josh Durgin diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index cf8ee7eff..2f0571ca3 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -81,7 +81,12 @@ class CloudTestCase(test.TestCase): def fake_show(meh, context, id): return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} + def fake_detail(meh, context): + return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type':'machine'}}] + self.stubs.Set(local.LocalImageService, 'show', fake_show) + self.stubs.Set(local.LocalImageService, 'detail', fake_detail) self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) def tearDown(self): @@ -224,6 +229,11 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp1['id']) db.service_destroy(self.context, comp2['id']) + def test_describe_images(self): + result = self.cloud.describe_images(self.context) + result = result['imagesSet'][0] + self.assertEqual(result['imageId'], 'ami-00000001') + def test_console_output(self): instance_type = FLAGS.default_instance_type max_count = 1 -- cgit From 3b284176505d255d08f07858d9dc881ddf95ece8 Mon Sep 17 00:00:00 2001 From: Kevin Bringard Date: Mon, 28 Mar 2011 07:31:37 -0600 Subject: Removed extraneous white space --- nova/network/linux_net.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index b59a8b7a2..9e2c59e70 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -59,15 +59,12 @@ flags.DEFINE_string('input_chain', 'INPUT', 'chain to add nova_input to') flags.DEFINE_integer('dhcp_lease_time', 120, 'Lifetime of a DHCP lease') - flags.DEFINE_string('dns_server', None, 'if set, uses specific dns server for dnsmasq') flags.DEFINE_string('dmz_cidr', '10.128.0.0/24', 'dmz range that should be accepted') - flags.DEFINE_string('dnsmasq_config_file',"", 'Override the default dnsmasq settings with those in this file') - binary_name = os.path.basename(inspect.stack()[-1][1]) @@ -674,7 +671,7 @@ def _dnsmasq_cmd(net): cmd = ['sudo', '-E', 'dnsmasq', '--strict-order', '--bind-interfaces', - ' --conf-file=%s' % FLAGS.dnsmasq_config_file, + '--conf-file=%s' % FLAGS.dnsmasq_config_file, '--domain=%s' % FLAGS.dhcp_domain, '--pid-file=%s' % _dhcp_file(net['bridge'], 'pid'), '--listen-address=%s' % net['gateway'], -- cgit From add207150a68b312614604281ca079164304110d Mon Sep 17 00:00:00 2001 From: Kevin Bringard Date: Mon, 28 Mar 2011 07:33:57 -0600 Subject: Updated Authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 09759ddcb..298ba8e8d 100644 --- a/Authors +++ b/Authors @@ -40,6 +40,7 @@ Joshua McKenty Justin Santa Barbara Kei Masumoto Ken Pepple +Kevin Bringard Kevin L. Mitchell Koji Iida Lorin Hochstein -- cgit From 9ce24afab007a9b5144c8c8a8f2fcc4157ba34d7 Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 28 Mar 2011 11:29:23 -0700 Subject: when image_id provided cannot be found, returns more informative error message. --- nova/api/ec2/cloud.py | 6 +++++- nova/tests/test_cloud.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index e257e44e7..2f47f0927 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -909,7 +909,11 @@ class CloudController(object): def deregister_image(self, context, image_id, **kwargs): LOG.audit(_("De-registering image %s"), image_id, context=context) - image = self._get_image(context, image_id) + try: + image = self._get_image(context, image_id) + except exception.NotFound: + raise exception.NotFound(_('Image %s not found') % + image_id) internal_id = image['id'] self.image_service.delete(context, internal_id) return {'imageId': image_id} diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 2f0571ca3..8043d4670 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -41,6 +41,7 @@ from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils from nova.image import local from nova.objectstore import image +from nova.exception import NotEmpty, NotFound FLAGS = flags.FLAGS @@ -85,8 +86,12 @@ class CloudTestCase(test.TestCase): return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, 'type':'machine'}}] + def fake_delete(meh, context, id): + return None + self.stubs.Set(local.LocalImageService, 'show', fake_show) self.stubs.Set(local.LocalImageService, 'detail', fake_detail) + self.stubs.Set(local.LocalImageService, 'delete', fake_delete) self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) def tearDown(self): @@ -234,6 +239,16 @@ class CloudTestCase(test.TestCase): result = result['imagesSet'][0] self.assertEqual(result['imageId'], 'ami-00000001') + def test_deregister_image(self): + deregister_image = self.cloud.deregister_image + """When provided a valid image, should be successful""" + result1 = deregister_image(self.context, 'ami-00000001') + self.assertEqual(result1['imageId'], 'ami-00000001') + """Invalid image should throw an NotFound exception""" + self.stubs.UnsetAll() + self.assertRaises(NotFound, deregister_image, + self.context, 'ami-bad001') + def test_console_output(self): instance_type = FLAGS.default_instance_type max_count = 1 -- cgit From 00afedaec5c6544bf9ff982d5f9d8e7b6b2a4b19 Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 28 Mar 2011 18:16:55 -0700 Subject: made changes per code review: 1) removed import of image from objectstore 2) changed to comments instaed of triple quotes. --- nova/tests/test_cloud.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 20c85d79c..b8a95e451 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -41,8 +41,7 @@ from nova.compute import power_state from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils from nova.image import local -from nova.objectstore import image -from nova.exception import NotEmpty, NotFound +from nova.exception import NotFound FLAGS = flags.FLAGS @@ -75,16 +74,7 @@ class CloudTestCase(test.TestCase): def fake_show(meh, context, id): return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} - def fake_detail(meh, context): - return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, - 'type':'machine'}}] - - def fake_delete(meh, context, id): - return None - self.stubs.Set(local.LocalImageService, 'show', fake_show) - self.stubs.Set(local.LocalImageService, 'detail', fake_detail) - self.stubs.Set(local.LocalImageService, 'delete', fake_delete) self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) def tearDown(self): @@ -228,17 +218,27 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp2['id']) def test_describe_images(self): + def fake_detail(meh, context): + return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type':'machine'}}] + self.stubs.Set(local.LocalImageService, 'detail', fake_detail) result = self.cloud.describe_images(self.context) result = result['imagesSet'][0] self.assertEqual(result['imageId'], 'ami-00000001') def test_deregister_image(self): deregister_image = self.cloud.deregister_image - """When provided a valid image, should be successful""" + def fake_delete(meh, context, id): + return None + self.stubs.Set(local.LocalImageService, 'delete', fake_delete) + # valid image result1 = deregister_image(self.context, 'ami-00000001') self.assertEqual(result1['imageId'], 'ami-00000001') - """Invalid image should throw an NotFound exception""" + # invalid image self.stubs.UnsetAll() + def fake_detail_empty(meh, context): + return [] + self.stubs.Set(local.LocalImageService, 'detail', fake_detail_empty) self.assertRaises(NotFound, deregister_image, self.context, 'ami-bad001') -- cgit From 1b67237d05e7103dc6b2beadd5782466682a136b Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 28 Mar 2011 18:19:56 -0700 Subject: cleaned up var name --- nova/tests/test_cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index b8a95e451..07e52a6be 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -232,8 +232,8 @@ class CloudTestCase(test.TestCase): return None self.stubs.Set(local.LocalImageService, 'delete', fake_delete) # valid image - result1 = deregister_image(self.context, 'ami-00000001') - self.assertEqual(result1['imageId'], 'ami-00000001') + result = deregister_image(self.context, 'ami-00000001') + self.assertEqual(result['imageId'], 'ami-00000001') # invalid image self.stubs.UnsetAll() def fake_detail_empty(meh, context): -- cgit From e5b0f3921331b5c0acbe321b00e2a9fa8d27be4e Mon Sep 17 00:00:00 2001 From: Muneyuki Noguchi Date: Tue, 29 Mar 2011 10:40:48 +0900 Subject: Add remove_volume to compute API. --- nova/compute/api.py | 7 +++++++ nova/compute/manager.py | 26 ++++++++++++++------------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 266cbe677..0cdb8f4b7 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -668,6 +668,13 @@ class API(base.Base): "volume_id": volume_id}}) return instance + def remove_volume(self, context, volume_id, host): + """Remove volume on specified compute host.""" + rpc.call(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "remove_volume", + "args": {'volume_id': volume_id}}) + def associate_floating_ip(self, context, instance_id, address): instance = self.get(context, instance_id) self.network_api.associate_floating_ip(context, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index aa612b137..bbd1fed1a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -46,6 +46,7 @@ import functools from eventlet import greenthread +from nova import compute from nova import exception from nova import flags from nova import log as logging @@ -772,6 +773,14 @@ class ComputeManager(manager.SchedulerDependentManager): self.db.volume_detached(context, volume_id) return True + def remove_volume(self, context, volume_id): + """Remove volume on compute host. + + :param context: security context + :param volume_id: nova.db.sqlalchemy.models.Volume.id + """ + self.volume_manager.remove_compute_volume(context, volume_id) + @exception.wrap_exception def compare_cpu(self, context, cpu_info): """Checks the host cpu is compatible to a cpu given by xml. @@ -1018,22 +1027,15 @@ class ComputeManager(manager.SchedulerDependentManager): 'state': power_state.RUNNING, 'host': host}) + if dest: + # NOTE(noguchimn): We set image_service here + # not to import an image service object. + compute_api = compute.API(image_service=1) for volume in instance_ref['volumes']: volume_id = volume['id'] self.db.volume_update(ctxt, volume_id, {'status': 'in-use'}) if dest: - topic = self.db.queue_get_for(ctxt, FLAGS.compute_topic, dest) - rpc.call(ctxt, topic, - {"method": "restore_volume_state", - "args": {'volume_id': volume_id}}) - - def restore_volume_state(self, context, volume_id): - """Restore volume state on migration failure. - - :param context: security context - :param volume_id: nova.db.sqlalchemy.models.Volume.id - """ - self.volume_manager.remove_compute_volume(context, volume_id) + compute_api.remove_volume(ctxt, volume_id, dest) def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval.""" -- cgit From ee00cb8057eac328c98dd9c040ffa324f11a87be Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 29 Mar 2011 13:43:00 -0700 Subject: added blank lines in between functions & removed the test_describe_images (was meant for a diff bug lp682888) --- nova/tests/test_cloud.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 07e52a6be..582e40e08 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -217,27 +217,22 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp1['id']) db.service_destroy(self.context, comp2['id']) - def test_describe_images(self): - def fake_detail(meh, context): - return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, - 'type':'machine'}}] - self.stubs.Set(local.LocalImageService, 'detail', fake_detail) - result = self.cloud.describe_images(self.context) - result = result['imagesSet'][0] - self.assertEqual(result['imageId'], 'ami-00000001') - def test_deregister_image(self): deregister_image = self.cloud.deregister_image + def fake_delete(meh, context, id): return None + self.stubs.Set(local.LocalImageService, 'delete', fake_delete) # valid image result = deregister_image(self.context, 'ami-00000001') self.assertEqual(result['imageId'], 'ami-00000001') # invalid image self.stubs.UnsetAll() + def fake_detail_empty(meh, context): return [] + self.stubs.Set(local.LocalImageService, 'detail', fake_detail_empty) self.assertRaises(NotFound, deregister_image, self.context, 'ami-bad001') -- cgit From d224b0509273ca8a92c5c2b9abca69038835935c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 30 Mar 2011 15:10:40 -0400 Subject: adding v1.0 support for rebuild; adding compute api rebuild support --- nova/api/openstack/servers.py | 19 ++++++++++++++++--- nova/compute/api.py | 9 +++++++++ nova/tests/api/openstack/test_servers.py | 27 +++++++++++++++++++++++---- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f7696d918..4b2703549 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -317,9 +317,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPBadRequest()) return exc.HTTPAccepted() - def _action_rebuild(self, input_dict, req, id): - return faults.Fault(exc.HTTPNotImplemented()) - def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ try: @@ -606,6 +603,19 @@ class ControllerV10(Controller): except exception.TimeoutException: return exc.HTTPRequestTimeout() + def _action_rebuild(self, input_dict, req, id): + context = req.environ['nova.context'] + if (not 'rebuild' in input_dict + or not 'imageId' in input_dict['rebuild']): + msg = _("No imageId was specified") + return faults.Fault(exc.HTTPBadRequest(msg)) + + image_id = input_dict['rebuild']['imageId'] + + self.compute_api.rebuild(context, id, image_id) + + return exc.HTTPAccepted() + class ControllerV11(Controller): def _image_id_from_req_data(self, data): @@ -632,6 +642,9 @@ class ControllerV11(Controller): def _limit_items(self, items, req): return common.limited_by_marker(items, req) + def _action_rebuild(self, input_dict, req, id): + return faults.Fault(exc.HTTPNotImplemented()) + class ServerCreateRequestXMLDeserializer(object): """ diff --git a/nova/compute/api.py b/nova/compute/api.py index 1dbd73f8f..93a5e7855 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -480,6 +480,15 @@ class API(base.Base): """Reboot the given instance.""" self._cast_compute_message('reboot_instance', context, instance_id) + def rebuild(self, context, instance_id, image_id, metadata=None): + """Rebuild the given instance with the provided metadata.""" + return + # default to an empty list + metadata = metadata or [] + #TODO: validate metadata + self._cast_compute_message('rebuild_instance', context, + instance_id, metadata) + def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process""" context = context.elevated() diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 130b8c5d5..87abc0067 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -774,15 +774,34 @@ class ServersTest(test.TestCase): req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) - def test_server_rebuild(self): - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) + def test_server_rebuild_accepted(self): + body = { + "rebuild": { + "imageId": 2, + }, + } + req = webob.Request.blank('/v1.0/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_server_rebuild_bad_entity(self): + body = { + "rebuild": { + }, + } + + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) def test_delete_server_instance(self): req = webob.Request.blank('/v1.0/servers/1') -- cgit From e52cdaa75ac4b5c9ea37a8a8c9b1f02e8d0f638f Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 30 Mar 2011 15:33:52 -0400 Subject: Rough implementation of rebuild_instance in compute manager. --- nova/compute/manager.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 08b772517..7366785dd 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -152,6 +152,11 @@ class ComputeManager(manager.SchedulerDependentManager): state = power_state.FAILED self.db.instance_set_state(context, instance_id, state) + def _update_launched_at(self, context, instance_id): + """Update the launched_at parameter of the given instance.""" + data = {'launched_at': datetime.datetime.utcnow()} + self.db.instance_update(context, instance_id, data) + def get_console_topic(self, context, **kwargs): """Retrieves the console host for a project on this host Currently this is just set in the flags for each compute @@ -232,10 +237,7 @@ class ComputeManager(manager.SchedulerDependentManager): try: self.driver.spawn(instance_ref) - now = datetime.datetime.utcnow() - self.db.instance_update(context, - instance_id, - {'launched_at': now}) + self._update_launched_at(context, instance_id) except Exception: # pylint: disable=W0702 LOG.exception(_("Instance '%s' failed to spawn. Is virtualization" " enabled in the BIOS?"), instance_id, @@ -294,6 +296,32 @@ class ComputeManager(manager.SchedulerDependentManager): # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) + @exception.wrap_exception + @checks_instance_lock + def rebuild_instance(self, context, instance_id): + """Destroy and re-make this instance. + + A 'rebuild' effectively purges all existing data from the system and + remakes the VM with given 'metadata' and 'personalities'. + + :param context: `nova.RequestContext` object + :param instance_id: Instance identifier (integer) + """ + context = context.elevated() + + instance_ref = self.db.instance_get(context, instance_id) + LOG.audit(_("Rebuilding instance %s"), instance_id, context=context) + + # TODO(blamar): Detach volumes prior to rebuild. + + # NOTE(blamar): The driver interface seems to indicate `destroy` is an + # async call, but the implementations look sync... + self.driver.destroy(instance_ref) + self.driver.spawn(instance_ref) + + self._update_launched_at(context, instance_id) + self._update_state(context, instance_id) + @exception.wrap_exception @checks_instance_lock def reboot_instance(self, context, instance_id): -- cgit From 655906ee7be1d906033bde7887293e6d61bae3d6 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 30 Mar 2011 12:37:56 -0700 Subject: updated per code review, replaced NotFound with exception.NotFound --- nova/tests/test_cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 582e40e08..145da8ad2 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -36,12 +36,12 @@ from nova import rpc from nova import service from nova import test from nova import utils +from nova import exception from nova.auth import manager from nova.compute import power_state from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils from nova.image import local -from nova.exception import NotFound FLAGS = flags.FLAGS @@ -234,7 +234,7 @@ class CloudTestCase(test.TestCase): return [] self.stubs.Set(local.LocalImageService, 'detail', fake_detail_empty) - self.assertRaises(NotFound, deregister_image, + self.assertRaises(exception.NotFound, deregister_image, self.context, 'ami-bad001') def test_console_output(self): -- cgit From 8482d87e3fe380704fac121240ebd29b9057283c Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 30 Mar 2011 12:44:22 -0700 Subject: removed trailing whitespace --- nova/tests/test_cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 145da8ad2..cde8041f7 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -36,7 +36,7 @@ from nova import rpc from nova import service from nova import test from nova import utils -from nova import exception +from nova import exception from nova.auth import manager from nova.compute import power_state from nova.api.ec2 import cloud -- cgit From cee0e90c058c3e50a3388eb4960afeb21b441f6a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 30 Mar 2011 16:17:07 -0400 Subject: adding initial v1.1 rebuild action support --- nova/api/openstack/servers.py | 24 ++++++++++-- nova/compute/api.py | 7 +++- nova/tests/api/openstack/test_servers.py | 64 ++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4b2703549..edf98fe93 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -611,9 +611,7 @@ class ControllerV10(Controller): return faults.Fault(exc.HTTPBadRequest(msg)) image_id = input_dict['rebuild']['imageId'] - self.compute_api.rebuild(context, id, image_id) - return exc.HTTPAccepted() @@ -643,7 +641,27 @@ class ControllerV11(Controller): return common.limited_by_marker(items, req) def _action_rebuild(self, input_dict, req, id): - return faults.Fault(exc.HTTPNotImplemented()) + context = req.environ['nova.context'] + if (not 'rebuild' in input_dict + or not 'imageRef' in input_dict['rebuild']): + msg = _("No imageRef was specified") + return faults.Fault(exc.HTTPBadRequest(msg)) + + image_ref = input_dict['rebuild']['imageRef'] + image_id = common.get_id_from_href(image_ref) + + metadata = [] + if 'metadata' in input_dict['rebuild']: + try: + for k, v in input_dict['rebuild']['metadata'].items(): + metadata.append({'key': k, 'value': v}) + + except Exception: + msg = _("Improperly formatted metadata provided") + return exc.HTTPBadRequest(msg) + + self.compute_api.rebuild(context, id, image_id, metadata) + return exc.HTTPAccepted() class ServerCreateRequestXMLDeserializer(object): diff --git a/nova/compute/api.py b/nova/compute/api.py index 93a5e7855..fdfb8103b 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -482,12 +482,15 @@ class API(base.Base): def rebuild(self, context, instance_id, image_id, metadata=None): """Rebuild the given instance with the provided metadata.""" - return # default to an empty list metadata = metadata or [] #TODO: validate metadata + params = { + "image_id": image_id, + "metadata": metadata, + } self._cast_compute_message('rebuild_instance', context, - instance_id, metadata) + instance_id, params=params) def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process""" diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 87abc0067..00c6d850a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -803,6 +803,70 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_server_rebuild_accepted_minimum_v11(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_server_rebuild_accepted_with_metadata_v11(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "metadata": { + "open": "stack", + }, + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_server_rebuild_accepted_with_bad_metadata_v11(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "metadata": "stack", + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_rebuild_bad_entity_v11(self): + body = { + "rebuild": { + "imageId": 2, + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_delete_server_instance(self): req = webob.Request.blank('/v1.0/servers/1') req.method = 'DELETE' -- cgit From b5f0d2c22ce48d06dc502f7790ed48fdf007d7f6 Mon Sep 17 00:00:00 2001 From: Muneyuki Noguchi Date: Thu, 31 Mar 2011 17:39:00 +0900 Subject: Add volume.API.remove_from_compute instead of compute.API.remove_volume. --- nova/compute/api.py | 7 ------- nova/compute/manager.py | 12 +++++------- nova/volume/api.py | 7 +++++++ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 63983afd8..1dbd73f8f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -687,13 +687,6 @@ class API(base.Base): "volume_id": volume_id}}) return instance - def remove_volume(self, context, volume_id, host): - """Remove volume on specified compute host.""" - rpc.call(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "remove_volume", - "args": {'volume_id': volume_id}}) - def associate_floating_ip(self, context, instance_id, address): instance = self.get(context, instance_id) self.network_api.associate_floating_ip(context, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8ce8a5d86..85bcd7590 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -46,13 +46,13 @@ import functools from eventlet import greenthread -from nova import compute from nova import exception from nova import flags from nova import log as logging from nova import manager from nova import rpc from nova import utils +from nova import volume from nova.compute import power_state from nova.virt import driver @@ -1037,14 +1037,12 @@ class ComputeManager(manager.SchedulerDependentManager): 'host': host}) if dest: - # NOTE(noguchimn): We set image_service here - # not to import an image service object. - compute_api = compute.API(image_service=1) - for volume in instance_ref['volumes']: - volume_id = volume['id'] + volume_api = volume.API() + for volume_ref in instance_ref['volumes']: + volume_id = volume_ref['id'] self.db.volume_update(ctxt, volume_id, {'status': 'in-use'}) if dest: - compute_api.remove_volume(ctxt, volume_id, dest) + volume_api.remove_from_compute(ctxt, volume_id, dest) def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval.""" diff --git a/nova/volume/api.py b/nova/volume/api.py index 4b4bb9dc5..09befb647 100644 --- a/nova/volume/api.py +++ b/nova/volume/api.py @@ -103,3 +103,10 @@ class API(base.Base): # TODO(vish): abstract status checking? if volume['status'] == "available": raise exception.ApiError(_("Volume is already detached")) + + def remove_from_compute(self, context, volume_id, host): + """Remove volume from specified compute host.""" + rpc.call(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "remove_volume", + "args": {'volume_id': volume_id}}) -- cgit From 158d434b9ec3018909f2f90a1808e27e28e4f704 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 31 Mar 2011 09:49:03 -0400 Subject: Rebuild improvements. --- nova/compute/manager.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7366785dd..0e2a6deb9 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -152,9 +152,14 @@ class ComputeManager(manager.SchedulerDependentManager): state = power_state.FAILED self.db.instance_set_state(context, instance_id, state) - def _update_launched_at(self, context, instance_id): + def _update_launched_at(self, context, instance_id, launched_at=None): """Update the launched_at parameter of the given instance.""" - data = {'launched_at': datetime.datetime.utcnow()} + data = {'launched_at': launched_at or datetime.datetime.utcnow()} + self.db.instance_update(context, instance_id, data) + + def _update_image_id(self, context, instance_id, image_id): + """Update the image_id for the given instance.""" + data = {'image_id': image_id} self.db.instance_update(context, instance_id, data) def get_console_topic(self, context, **kwargs): @@ -298,7 +303,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def rebuild_instance(self, context, instance_id): + def rebuild_instance(self, context, instance_id, image_id, metadata): """Destroy and re-make this instance. A 'rebuild' effectively purges all existing data from the system and @@ -306,6 +311,8 @@ class ComputeManager(manager.SchedulerDependentManager): :param context: `nova.RequestContext` object :param instance_id: Instance identifier (integer) + :param image_id: Image identifier (integer) + :param metadata: Metadata dictionary """ context = context.elevated() @@ -314,11 +321,14 @@ class ComputeManager(manager.SchedulerDependentManager): # TODO(blamar): Detach volumes prior to rebuild. - # NOTE(blamar): The driver interface seems to indicate `destroy` is an - # async call, but the implementations look sync... self.driver.destroy(instance_ref) + + #self._update_state(context, instance_id) + instance_ref.image_id = image_id + self.driver.spawn(instance_ref) + self._update_image_id(context, instance_id, image_id) self._update_launched_at(context, instance_id) self._update_state(context, instance_id) -- cgit From 2d800666df9cb16e90a47c10dc7d5d7a800088d4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 10:18:44 -0400 Subject: adding metadata support for v1.1 --- nova/compute/api.py | 59 +++++++++++++++++--------------- nova/tests/api/openstack/test_servers.py | 2 +- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index fdfb8103b..43f972882 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -100,26 +100,7 @@ class API(base.Base): if len(content) > content_limit: raise quota.QuotaError(code="OnsetFileContentLimitExceeded") - def create(self, context, instance_type, - image_id, kernel_id=None, ramdisk_id=None, - min_count=1, max_count=1, - display_name='', display_description='', - key_name=None, key_data=None, security_group='default', - availability_zone=None, user_data=None, metadata=[], - injected_files=None): - """Create the number of instances requested if quota and - other arguments check out ok.""" - - type_data = instance_types.get_instance_type(instance_type) - num_instances = quota.allowed_instances(context, max_count, type_data) - if num_instances < min_count: - pid = context.project_id - LOG.warn(_("Quota exceeeded for %(pid)s," - " tried to run %(min_count)s instances") % locals()) - raise quota.QuotaError(_("Instance quota exceeded. You can only " - "run %s more instances of this type.") % - num_instances, "InstanceLimitExceeded") - + def _check_metadata_quota(self, context, metadata): num_metadata = len(metadata) quota_metadata = quota.allowed_metadata_items(context, num_metadata) if quota_metadata < num_metadata: @@ -130,6 +111,7 @@ class API(base.Base): LOG.warn(msg) raise quota.QuotaError(msg, "MetadataLimitExceeded") + def _check_metadata_item_length(self, context, metadata): # Because metadata is stored in the DB, we hard-code the size limits # In future, we may support more variable length strings, so we act # as if this is quota-controlled for forwards compatibility @@ -144,6 +126,29 @@ class API(base.Base): LOG.warn(msg) raise quota.QuotaError(msg, "MetadataLimitExceeded") + def create(self, context, instance_type, + image_id, kernel_id=None, ramdisk_id=None, + min_count=1, max_count=1, + display_name='', display_description='', + key_name=None, key_data=None, security_group='default', + availability_zone=None, user_data=None, metadata=[], + injected_files=None): + """Create the number of instances requested if quota and + other arguments check out ok.""" + + type_data = instance_types.get_instance_type(instance_type) + num_instances = quota.allowed_instances(context, max_count, type_data) + if num_instances < min_count: + pid = context.project_id + LOG.warn(_("Quota exceeeded for %(pid)s," + " tried to run %(min_count)s instances") % locals()) + raise quota.QuotaError(_("Instance quota exceeded. You can only " + "run %s more instances of this type.") % + num_instances, "InstanceLimitExceeded") + + self._check_metadata_quota(context, metadata) + self._check_metadata_item_length(context, metadata) + self._check_injected_file_quota(context, injected_files) image = self.image_service.show(context, image_id) @@ -482,15 +487,15 @@ class API(base.Base): def rebuild(self, context, instance_id, image_id, metadata=None): """Rebuild the given instance with the provided metadata.""" - # default to an empty list + metadata = metadata or [] - #TODO: validate metadata - params = { - "image_id": image_id, - "metadata": metadata, - } + self._check_metadata_quota(context, metadata) + self._check_metadata_item_length(context, metadata) + self._cast_compute_message('rebuild_instance', context, - instance_id, params=params) + instance_id, params={"image_id": image_id}) + + self.db.instance_update(context, instance_id, {"metadata": metadata}) def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process""" diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 00c6d850a..aab89b0b3 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -823,7 +823,7 @@ class ServersTest(test.TestCase): "rebuild": { "imageRef": "http://localhost/images/2", "metadata": { - "open": "stack", + "new": "metadata", }, }, } -- cgit From 8e079f75e6391a3fc181fce7b6d03919b9625737 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 31 Mar 2011 10:45:53 -0400 Subject: Update state between delete and spawn. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0e2a6deb9..943a455a6 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -323,7 +323,7 @@ class ComputeManager(manager.SchedulerDependentManager): self.driver.destroy(instance_ref) - #self._update_state(context, instance_id) + self._update_state(context, instance_id) instance_ref.image_id = image_id self.driver.spawn(instance_ref) -- cgit From 1ecd9b3d00f002799a9eab92f0179dcbea8b8c37 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 14:15:42 -0400 Subject: adding 'building' power state; testing for 409 from OSAPI when rebuild requested on server being rebuild --- nova/api/openstack/servers.py | 15 ++++++++-- nova/compute/api.py | 6 ++++ nova/compute/power_state.py | 21 ++++++++------ nova/exception.py | 4 +++ nova/tests/api/openstack/test_servers.py | 48 ++++++++++++++++++++++++++++++-- 5 files changed, 81 insertions(+), 13 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index edf98fe93..fc33a8257 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -611,7 +611,13 @@ class ControllerV10(Controller): return faults.Fault(exc.HTTPBadRequest(msg)) image_id = input_dict['rebuild']['imageId'] - self.compute_api.rebuild(context, id, image_id) + + try: + self.compute_api.rebuild(context, id, image_id) + except exception.BuildInProgress: + msg = _("Unable to rebuild server that is being rebuilt") + return faults.Fault(exc.HTTPConflict(msg)) + return exc.HTTPAccepted() @@ -660,7 +666,12 @@ class ControllerV11(Controller): msg = _("Improperly formatted metadata provided") return exc.HTTPBadRequest(msg) - self.compute_api.rebuild(context, id, image_id, metadata) + try: + self.compute_api.rebuild(context, id, image_id, metadata) + except exception.BuildInProgress: + msg = _("Unable to rebuild server that is being rebuilt") + return faults.Fault(exc.HTTPConflict(msg)) + return exc.HTTPAccepted() diff --git a/nova/compute/api.py b/nova/compute/api.py index 43f972882..32b283d31 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -34,6 +34,7 @@ from nova import rpc from nova import utils from nova import volume from nova.compute import instance_types +from nova.compute import power_state from nova.scheduler import api as scheduler_api from nova.db import base @@ -488,6 +489,11 @@ class API(base.Base): def rebuild(self, context, instance_id, image_id, metadata=None): """Rebuild the given instance with the provided metadata.""" + instance = db.api.instance_get(context, instance_id) + if instance["state"] == power_state.BUILDING: + msg = _("Instance already building") + raise exception.BuildInProgress(msg) + metadata = metadata or [] self._check_metadata_quota(context, metadata) self._check_metadata_item_length(context, metadata) diff --git a/nova/compute/power_state.py b/nova/compute/power_state.py index ef013b2ef..c468fe6b3 100644 --- a/nova/compute/power_state.py +++ b/nova/compute/power_state.py @@ -30,20 +30,23 @@ SHUTOFF = 0x05 CRASHED = 0x06 SUSPENDED = 0x07 FAILED = 0x08 +BUILDING = 0x09 # TODO(justinsb): Power state really needs to be a proper class, # so that we're not locked into the libvirt status codes and can put mapping # logic here rather than spread throughout the code _STATE_MAP = { - NOSTATE: 'pending', - RUNNING: 'running', - BLOCKED: 'blocked', - PAUSED: 'paused', - SHUTDOWN: 'shutdown', - SHUTOFF: 'shutdown', - CRASHED: 'crashed', - SUSPENDED: 'suspended', - FAILED: 'failed to spawn'} + NOSTATE: 'pending', + RUNNING: 'running', + BLOCKED: 'blocked', + PAUSED: 'paused', + SHUTDOWN: 'shutdown', + SHUTOFF: 'shutdown', + CRASHED: 'crashed', + SUSPENDED: 'suspended', + FAILED: 'failed to spawn', + BUILDING: 'building', +} def name(code): diff --git a/nova/exception.py b/nova/exception.py index 4e2bbdbaf..5fa7fb56e 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -96,6 +96,10 @@ class TimeoutException(Error): pass +class BuildInProgress(Error): + pass + + class DBError(Error): """Wraps an implementation specific exception""" def __init__(self, inner_exception): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index aab89b0b3..9e77738a6 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -31,6 +31,7 @@ from nova import flags from nova import test import nova.api.openstack from nova.api.openstack import servers +from nova.compute import power_state import nova.compute.api import nova.db.api from nova.db.sqlalchemy.models import Instance @@ -55,6 +56,12 @@ def return_server_with_addresses(private, public): return _return_server +def return_server_with_state(state): + def _return_server(context, id): + return stub_instance(id, state=state) + return _return_server + + def return_servers(context, user_id=1): return [stub_instance(i, user_id) for i in xrange(5)] @@ -71,7 +78,8 @@ def instance_address(context, instance_id): return None -def stub_instance(id, user_id=1, private_address=None, public_addresses=None): +def stub_instance(id, user_id=1, private_address=None, public_addresses=None, + state=0): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -89,7 +97,7 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None): "launch_index": 0, "key_name": "", "key_data": "", - "state": 0, + "state": state, "state_description": "", "memory_mb": 0, "vcpus": 0, @@ -789,6 +797,24 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) + def test_server_rebuild_rejected_when_building(self): + body = { + "rebuild": { + "imageId": 2, + }, + } + + new_return_server = return_server_with_state(power_state.BUILDING) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 409) + def test_server_rebuild_bad_entity(self): body = { "rebuild": { @@ -818,6 +844,24 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) + def test_server_rebuild_rejected_when_building_v11(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + }, + } + + new_return_server = return_server_with_state(power_state.BUILDING) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 409) + def test_server_rebuild_accepted_with_metadata_v11(self): body = { "rebuild": { -- cgit From 59e8112994e293fa1155b703abcc3e33d7cfc6c7 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 14:35:25 -0400 Subject: pep8 --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 32b283d31..17bec2545 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -34,7 +34,7 @@ from nova import rpc from nova import utils from nova import volume from nova.compute import instance_types -from nova.compute import power_state +from nova.compute import power_state from nova.scheduler import api as scheduler_api from nova.db import base -- cgit From 600dc802e3775ea6b4b940e03c82e8b8ac40191c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 15:34:33 -0400 Subject: adding servers view mapping for BUILDING power state --- nova/api/openstack/views/servers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 4e7f62eb3..b74c8d409 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -67,6 +67,8 @@ class ViewBuilder(object): power_state.SHUTOFF: 'active', power_state.CRASHED: 'error', power_state.FAILED: 'error'} + power_state.BUILDING: 'build', + } inst_dict = { 'id': int(inst['id']), -- cgit From 6e6288d6d42bc17508b5df9781c7f04104f34fb2 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 31 Mar 2011 15:37:38 -0400 Subject: Now using the new power state instead of string. --- nova/compute/manager.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 2c5285dff..94d4f1991 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -141,15 +141,21 @@ class ComputeManager(manager.SchedulerDependentManager): """ self.driver.init_host(host=self.host) - def _update_state(self, context, instance_id, desc=None): + def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" instance_ref = self.db.instance_get(context, instance_id) - try: - info = self.driver.get_info(instance_ref['name']) - state = info['state'] - except exception.NotFound: - state = power_state.FAILED - self.db.instance_set_state(context, instance_id, state, desc) + + if state is None: + try: + info = self.driver.get_info(instance_ref['name']) + except exception.NotFound: + info = None + state = power_state.FAILED + + if info is not None: + state = info['state'] + + self.db.instance_set_state(context, instance_id, state) def _update_launched_at(self, context, instance_id, launched_at=None): """Update the launched_at parameter of the given instance.""" @@ -318,7 +324,7 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.audit(_("Rebuilding instance %s"), instance_id, context=context) # TODO(blamar): Detach volumes prior to rebuild. - self._update_state(context, instance_id, "rebuilding") + self._update_state(context, instance_id, power_state.BUILDING) self.driver.destroy(instance_ref) instance_ref.image_id = image_id -- cgit From 29396c4c739b6b0a26a1b24beb86aa2e7e2bc474 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 15:37:44 -0400 Subject: Didn't run my code. Syntax error :( --- nova/api/openstack/views/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index b74c8d409..6496f0ee5 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -66,7 +66,7 @@ class ViewBuilder(object): power_state.SHUTDOWN: 'active', power_state.SHUTOFF: 'active', power_state.CRASHED: 'error', - power_state.FAILED: 'error'} + power_state.FAILED: 'error', power_state.BUILDING: 'build', } -- cgit From f37edcb18d27585ce6a2074a5f35eb7a84454dcf Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 31 Mar 2011 15:41:18 -0400 Subject: Adding explanation keyword to HTTPConflict --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fc33a8257..563acc59e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -616,7 +616,7 @@ class ControllerV10(Controller): self.compute_api.rebuild(context, id, image_id) except exception.BuildInProgress: msg = _("Unable to rebuild server that is being rebuilt") - return faults.Fault(exc.HTTPConflict(msg)) + return faults.Fault(exc.HTTPConflict(explanation=msg)) return exc.HTTPAccepted() @@ -670,7 +670,7 @@ class ControllerV11(Controller): self.compute_api.rebuild(context, id, image_id, metadata) except exception.BuildInProgress: msg = _("Unable to rebuild server that is being rebuilt") - return faults.Fault(exc.HTTPConflict(msg)) + return faults.Fault(exc.HTTPConflict(explanation=msg)) return exc.HTTPAccepted() -- cgit From 7688cbb07ffcfd6446dc9ede60fb9eb610809c1d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 31 Mar 2011 16:46:08 -0400 Subject: Removal of instance_set_state from driver code, it shouldnt be there, but instead should be in the compute manager. --- nova/compute/manager.py | 15 ++++----------- nova/virt/libvirt_conn.py | 4 ++-- nova/virt/xenapi/vmops.py | 26 ++++++++++---------------- 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 94d4f1991..db1d6950e 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -240,21 +240,16 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id) # TODO(vish) check to make sure the availability zone matches - self.db.instance_set_state(context, - instance_id, - power_state.NOSTATE, - 'spawning') + self._update_state(context, instance_id, power_state.BUILDING) try: self.driver.spawn(instance_ref) self._update_launched_at(context, instance_id) - except Exception: # pylint: disable=W0702 + except Exception as ex: # pylint: disable=W0702 + LOG.debug(ex) LOG.exception(_("Instance '%s' failed to spawn. Is virtualization" " enabled in the BIOS?"), instance_id, context=context) - self.db.instance_set_state(context, - instance_id, - power_state.SHUTDOWN) self._update_state(context, instance_id) @@ -1133,9 +1128,7 @@ class ComputeManager(manager.SchedulerDependentManager): if vm_state != db_state: LOG.info(_("DB/VM state mismatch. Changing state from " "'%(db_state)s' to '%(vm_state)s'") % locals()) - self.db.instance_set_state(context, - db_instance['id'], - vm_state) + self._update_state(context, db_instance['id'], vm_state) if vm_state == power_state.SHUTOFF: # TODO(soren): This is what the compute manager does when you diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f998a592b..bc9a031f9 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -597,8 +597,8 @@ class LibvirtConnection(driver.ComputeDriver): try: state = self.get_info(name)['state'] except (exception.NotFound, libvirt.libvirtError) as ex: - msg = _("Error while waiting for VM to run: %s") % ex - LOG.debug(msg) + msg = _("Error while waiting for VM '%(_id)s' to run: %(ex)s") + LOG.debug(msg % locals()) timer.stop() if state == power_state.RUNNING: diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c96c35a6e..fb3ca5306 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -206,33 +206,27 @@ class VMOps(object): # NOTE(armando): Do we really need to do this in virt? # NOTE(tr3buchet): not sure but wherever we do it, we need to call # reset_network afterwards - timer = utils.LoopingCall(f=None) def _wait_for_boot(): try: state = self.get_info(instance_name)['state'] - db.instance_set_state(context.get_admin_context(), - instance['id'], state) - if state == power_state.RUNNING: - LOG.debug(_('Instance %s: booted'), instance_name) - timer.stop() - _inject_files() - return True - except Exception, exc: - LOG.warn(exc) - LOG.exception(_('instance %s: failed to boot'), - instance_name) - db.instance_set_state(context.get_admin_context(), - instance['id'], - power_state.SHUTDOWN) + except self.XenAPI.Failure as ex: + msg = _("Error while waiting for VM '%(instance_name)s' " + "to boot: %(ex)s") % locals() + LOG.debug(msg) timer.stop() return False - timer.f = _wait_for_boot + if state == power_state.RUNNING: + LOG.debug(_('VM %s is now running.') % name) + timer.stop() + _inject_files() + return True # call to reset network to configure network from xenstore self.reset_network(instance, vm_ref) + timer = utils.LoopingCall(f=_wait_for_boot) return timer.start(interval=0.5, now=True) def _get_vm_opaque_ref(self, instance_or_vm): -- cgit From dcb94be18b1f8b3591d7304208b64a296cdd71f6 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 1 Apr 2011 14:16:17 -0400 Subject: Poller needs to check for BUILDING not NOSTATE now, since we're being more explict about what is going on. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index db1d6950e..1e0997a97 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1104,7 +1104,7 @@ class ComputeManager(manager.SchedulerDependentManager): if vm_instance is None: # NOTE(justinsb): We have to be very careful here, because a # concurrent operation could be in progress (e.g. a spawn) - if db_state == power_state.NOSTATE: + if db_state == power_state.BUILDING: # Assume that NOSTATE => spawning # TODO(justinsb): This does mean that if we crash during a # spawn, the machine will never leave the spawning state, -- cgit From a8d186d212ffbc628fc2b2672eca1e0557c57414 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sun, 3 Apr 2011 03:45:33 +0400 Subject: split up to_xml to creation xml_info and filling the template --- nova/tests/test_virt.py | 20 ++++++-------------- nova/virt/libvirt_conn.py | 12 +++++++----- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 958c8e3e2..62afcd1f1 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -31,9 +31,7 @@ from nova import test from nova import utils from nova.api.ec2 import cloud from nova.auth import manager -from nova.compute import manager as compute_manager from nova.compute import power_state -from nova.db.sqlalchemy import models from nova.virt import libvirt_conn libvirt = None @@ -269,7 +267,7 @@ class LibvirtConnTestCase(test.TestCase): self.assertTrue(len(target) > 0) def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, - rescue=False): + rescue=False, network_info=None): user_context = context.RequestContext(project=self.project, user=self.user) instance_ref = db.instance_create(user_context, instance) @@ -327,19 +325,13 @@ class LibvirtConnTestCase(test.TestCase): check = (lambda t: t.find('./os/initrd'), None) check_list.append(check) + parameter = './devices/interface/filterref/parameter' common_checks = [ (lambda t: t.find('.').tag, 'domain'), - (lambda t: t.find( - './devices/interface/filterref/parameter').get('name'), 'IP'), - (lambda t: t.find( - './devices/interface/filterref/parameter').get( - 'value'), '10.11.12.13'), - (lambda t: t.findall( - './devices/interface/filterref/parameter')[1].get( - 'name'), 'DHCPSERVER'), - (lambda t: t.findall( - './devices/interface/filterref/parameter')[1].get( - 'value'), '10.0.0.1'), + (lambda t: t.find(parameter).get('name'), 'IP'), + (lambda t: t.find(parameter).get('value'), '10.11.12.13'), + (lambda t: t.findall(parameter)[1].get('name'), 'DHCPSERVER'), + (lambda t: t.findall(parameter)[1].get('value'), '10.0.0.1'), (lambda t: t.find('./devices/serial/source').get( 'path').split('/')[1], 'console.log'), (lambda t: t.find('./memory').text, '2097152')] diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f34ea7225..910d8a634 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -936,7 +936,7 @@ class LibvirtConnection(driver.ComputeDriver): return result - def to_xml(self, instance, rescue=False, network_info=None): + def _prepare_xml_info(self, instance, rescue=False, network_info=None): # TODO(termie): cache? LOG.debug(_('instance %s: starting toXML method'), instance['name']) @@ -947,8 +947,7 @@ class LibvirtConnection(driver.ComputeDriver): nics = [] for (network, mapping) in network_info: - nics.append(self._get_nic_for_xml(network, - mapping)) + nics.append(self._get_nic_for_xml(network, mapping)) # FIXME(vish): stick this in db instance_type_name = instance['instance_type'] instance_type = instance_types.get_instance_type(instance_type_name) @@ -979,10 +978,13 @@ class LibvirtConnection(driver.ComputeDriver): xml_info['ramdisk'] = xml_info['basepath'] + "/ramdisk" xml_info['disk'] = xml_info['basepath'] + "/disk" + + return xml_info + def to_xml(self, instance, rescue=False, network_info=None): + xml_info = self._prepare_xml_info(instance, rescue, network_info) xml = str(Template(self.libvirt_xml, searchList=[xml_info])) - LOG.debug(_('instance %s: finished toXML method'), - instance['name']) + LOG.debug(_('instance %s: finished toXML method'), instance['name']) return xml def get_info(self, instance_name): -- cgit From 74d9a325a452fb927e5edddca3f1b7edd35d1496 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sun, 3 Apr 2011 21:18:35 +0400 Subject: added preparing_xml test --- nova/tests/test_virt.py | 41 +++++++++++++++++++++++++++++++++++++++++ nova/virt/libvirt_conn.py | 24 +++++++----------------- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 62afcd1f1..319544099 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -192,6 +192,47 @@ class LibvirtConnTestCase(test.TestCase): return db.service_create(context.get_admin_context(), service_ref) + + def _create_network_info(self, count=1): + fake = 'fake' + fake_ip = '0.0.0.0/0' + network = {'gateway': fake, + 'gateway_v6': fake, + 'bridge': fake, + 'cidr': fake_ip, + 'cidr_v6': fake_ip} + mapping = {'mac': fake, + 'ips': [{'ip': fake_ip}]} + + return [(network, mapping) for x in xrange(0, count)] + + def test_preparing_xml_info(self): + conn = libvirt_conn.LibvirtConnection(True) + instance_ref = db.instance_create(self.context, self.test_instance) + + result = conn._prepare_xml_info(instance_ref, False) + self.assertFalse(result['nics']) + + result = conn._prepare_xml_info(instance_ref, False, + self._create_network_info()) + self.assertTrue(len(result['nics']) == 1) + + result = conn._prepare_xml_info(instance_ref, False, + self._create_network_info(2)) + self.assertTrue(len(result['nics']) == 2) + + def test_get_nic_for_xml(self): + conn = libvirt_conn.LibvirtConnection(True) + network, mapping = self._create_network_info()[0] + FLAGS.use_ipv6 = False + params_1 = conn._get_nic_for_xml(network, mapping)['extra_params'] + FLAGS.use_ipv6 = True + params_2 = conn._get_nic_for_xml(network, mapping)['extra_params'] + self.assertTrue(params_1.find('PROJNETV6') == -1) + self.assertTrue(params_1.find('PROJMASKV6') == -1) + self.assertTrue(params_2.find('PROJNETV6') > -1) + self.assertTrue(params_2.find('PROJMASKV6') > -1) + def test_xml_and_uri_no_ramdisk_no_kernel(self): instance_data = dict(self.test_instance) self._check_xml_and_uri(instance_data, diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 910d8a634..8af5eb025 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -899,26 +899,16 @@ class LibvirtConnection(driver.ComputeDriver): mac_id = mapping['mac'].replace(':', '') if FLAGS.allow_project_net_traffic: + template = "\n" + net, mask = _get_net_and_mask(network['cidr']) + values = [("PROJNET", net), ("PROJMASK", mask)] if FLAGS.use_ipv6: - net, mask = _get_net_and_mask(network['cidr']) net_v6, prefixlen_v6 = _get_net_and_prefixlen( network['cidr_v6']) - extra_params = ("\n" - "\n" - "\n" - "\n") % \ - (net, mask, net_v6, prefixlen_v6) - else: - net, mask = _get_net_and_mask(network['cidr']) - extra_params = ("\n" - "\n") % \ - (net, mask) + values.extend([("PROJNETV6", net_v6), + ("PROJMASKV6", prefixlen_v6)]) + + extra_params = "".join([template % value for value in values]) else: extra_params = "\n" -- cgit From 8969cb1f22a7760dc7e17c578a686f088b1a8d89 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sun, 3 Apr 2011 22:50:38 +0400 Subject: add multi_nic_test --- nova/tests/test_virt.py | 14 ++++++++++++-- nova/virt/libvirt_conn.py | 3 +-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 319544099..b6482503e 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -192,7 +192,6 @@ class LibvirtConnTestCase(test.TestCase): return db.service_create(context.get_admin_context(), service_ref) - def _create_network_info(self, count=1): fake = 'fake' fake_ip = '0.0.0.0/0' @@ -224,6 +223,7 @@ class LibvirtConnTestCase(test.TestCase): def test_get_nic_for_xml(self): conn = libvirt_conn.LibvirtConnection(True) network, mapping = self._create_network_info()[0] + backup = FLAGS.use_ipv6 FLAGS.use_ipv6 = False params_1 = conn._get_nic_for_xml(network, mapping)['extra_params'] FLAGS.use_ipv6 = True @@ -232,6 +232,7 @@ class LibvirtConnTestCase(test.TestCase): self.assertTrue(params_1.find('PROJMASKV6') == -1) self.assertTrue(params_2.find('PROJNETV6') > -1) self.assertTrue(params_2.find('PROJMASKV6') > -1) + FLAGS.use_ipv6 = backup def test_xml_and_uri_no_ramdisk_no_kernel(self): instance_data = dict(self.test_instance) @@ -268,6 +269,15 @@ class LibvirtConnTestCase(test.TestCase): instance_data = dict(self.test_instance) self._check_xml_and_container(instance_data) + def test_multi_nic(self): + instance_data = dict(self.test_instance) + network_info = self._create_network_info(2) + conn = libvirt_conn.LibvirtConnection(True) + instance_ref = db.instance_create(self.context, instance_data) + xml = conn.to_xml(instance_ref, False, network_info) + tree = xml_to_tree(xml) + self.assertEquals(len(tree.findall("./devices/interface")), 2) + def _check_xml_and_container(self, instance): user_context = context.RequestContext(project=self.project, user=self.user) @@ -308,7 +318,7 @@ class LibvirtConnTestCase(test.TestCase): self.assertTrue(len(target) > 0) def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, - rescue=False, network_info=None): + rescue=False): user_context = context.RequestContext(project=self.project, user=self.user) instance_ref = db.instance_create(user_context, instance) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 8af5eb025..5c7540927 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -908,7 +908,7 @@ class LibvirtConnection(driver.ComputeDriver): values.extend([("PROJNETV6", net_v6), ("PROJMASKV6", prefixlen_v6)]) - extra_params = "".join([template % value for value in values]) + extra_params = "".join([template % value for value in values]) else: extra_params = "\n" @@ -968,7 +968,6 @@ class LibvirtConnection(driver.ComputeDriver): xml_info['ramdisk'] = xml_info['basepath'] + "/ramdisk" xml_info['disk'] = xml_info['basepath'] + "/disk" - return xml_info def to_xml(self, instance, rescue=False, network_info=None): -- cgit From c1120caaa8c8ed8902b5634da56b2bd5478662e1 Mon Sep 17 00:00:00 2001 From: Muneyuki Noguchi Date: Mon, 4 Apr 2011 10:25:58 +0900 Subject: Use keyword arguments. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c5a71d244..c03c2ae1d 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1475,7 +1475,7 @@ class LibvirtConnection(driver.ComputeDriver): FLAGS.live_migration_bandwidth) except Exception: - recover_method(ctxt, instance_ref, None, dest) + recover_method(ctxt, instance_ref, dest=dest) raise # Waiting for completion of live_migration. -- cgit From 80549a0085e7c3a90b117b4c9df5a77b4ecd0843 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 4 Apr 2011 18:33:50 +0400 Subject: improving tests --- nova/tests/test_virt.py | 76 +++++++++++++++++++++++++++++++++-------------- nova/virt/libvirt_conn.py | 44 +++++++++++++-------------- 2 files changed, 75 insertions(+), 45 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index b6482503e..ae813cb80 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -44,6 +44,22 @@ def _concurrency(wait, done, target): done.send() +def _create_network_info(count=1): + fake = 'fake' + fake_ip = '0.0.0.0/0' + fake_ip_2 = '0.0.0.1/0' + fake_ip_3 = '0.0.0.1/0' + network = {'gateway': fake, + 'gateway_v6': fake, + 'bridge': fake, + 'cidr': fake_ip, + 'cidr_v6': fake_ip} + mapping = {'mac': fake, + 'ips': [{'ip': fake_ip}, {'ip': fake_ip}], + 'ip6s': [{'ip': fake_ip}, {'ip': fake_ip_2}, {'ip': fake_ip_3}]} + return [(network, mapping) for x in xrange(0, count)] + + class CacheConcurrencyTestCase(test.TestCase): def setUp(self): super(CacheConcurrencyTestCase, self).setUp() @@ -192,19 +208,6 @@ class LibvirtConnTestCase(test.TestCase): return db.service_create(context.get_admin_context(), service_ref) - def _create_network_info(self, count=1): - fake = 'fake' - fake_ip = '0.0.0.0/0' - network = {'gateway': fake, - 'gateway_v6': fake, - 'bridge': fake, - 'cidr': fake_ip, - 'cidr_v6': fake_ip} - mapping = {'mac': fake, - 'ips': [{'ip': fake_ip}]} - - return [(network, mapping) for x in xrange(0, count)] - def test_preparing_xml_info(self): conn = libvirt_conn.LibvirtConnection(True) instance_ref = db.instance_create(self.context, self.test_instance) @@ -213,16 +216,16 @@ class LibvirtConnTestCase(test.TestCase): self.assertFalse(result['nics']) result = conn._prepare_xml_info(instance_ref, False, - self._create_network_info()) + _create_network_info()) self.assertTrue(len(result['nics']) == 1) result = conn._prepare_xml_info(instance_ref, False, - self._create_network_info(2)) + _create_network_info(2)) self.assertTrue(len(result['nics']) == 2) def test_get_nic_for_xml(self): conn = libvirt_conn.LibvirtConnection(True) - network, mapping = self._create_network_info()[0] + network, mapping = _create_network_info()[0] backup = FLAGS.use_ipv6 FLAGS.use_ipv6 = False params_1 = conn._get_nic_for_xml(network, mapping)['extra_params'] @@ -271,12 +274,19 @@ class LibvirtConnTestCase(test.TestCase): def test_multi_nic(self): instance_data = dict(self.test_instance) - network_info = self._create_network_info(2) + network_info = _create_network_info(2) conn = libvirt_conn.LibvirtConnection(True) instance_ref = db.instance_create(self.context, instance_data) xml = conn.to_xml(instance_ref, False, network_info) tree = xml_to_tree(xml) - self.assertEquals(len(tree.findall("./devices/interface")), 2) + interfaces = tree.findall("./devices/interface") + self.assertEquals(len(interfaces), 2) + parameters = interfaces[0].findall('./filterref/parameter') + self.assertEquals(interfaces[0].get('type'), 'bridge') + self.assertEquals(parameters[0].get('name'), 'IP') + self.assertEquals(parameters[0].get('value'), '0.0.0.0/0') + self.assertEquals(parameters[1].get('name'), 'DHCPSERVER') + self.assertEquals(parameters[1].get('value'), 'fake') def _check_xml_and_container(self, instance): user_context = context.RequestContext(project=self.project, @@ -656,11 +666,14 @@ class IptablesFirewallTestCase(test.TestCase): '# Completed on Tue Jan 18 23:47:56 2011', ] + def _create_instance_ref(self): + return db.instance_create(self.context, + {'user_id': 'fake', + 'project_id': 'fake', + 'mac_address': '56:12:12:12:12:12'}) + def test_static_filters(self): - instance_ref = db.instance_create(self.context, - {'user_id': 'fake', - 'project_id': 'fake', - 'mac_address': '56:12:12:12:12:12'}) + instance_ref = self._create_instance_ref() ip = '10.11.12.13' network_ref = db.project_get_network(self.context, @@ -771,6 +784,25 @@ class IptablesFirewallTestCase(test.TestCase): "TCP port 80/81 acceptance rule wasn't added") db.instance_destroy(admin_ctxt, instance_ref['id']) + def test_filters_for_instance(self): + network_info = _create_network_info() + rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) + self.assertEquals(len(rulesv4), 2) + self.assertEquals(len(rulesv6), 3) + + def multinic_iptables_test(self): + instance_ref = self._create_instance_ref() + network_info = _create_network_info() + ipv4_len = len(self.fw.iptables.ipv4['filter'].rules) + ipv6_len = len(self.fw.iptables.ipv6['filter'].rules) + inst_ipv4, inst_ipv6 = self.fw.instance_rules(instance_ref, + network_info) + self.fw.add_filters_for_instance(instance_ref, network_info) + ipv4 = self.fw.iptables.ipv4['filter'].rules + ipv6 = self.fw.iptables.ipv6['filter'].rules + self.assertEquals(len(ipv4) - len(inst_ipv4) - ipv4_len, 2) + self.assertEquals(len(ipv6) - len(inst_ipv6) - ipv6_len, 3) + class NWFilterTestCase(test.TestCase): def setUp(self): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 5c7540927..92519da65 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1877,34 +1877,21 @@ class IptablesFirewallDriver(FirewallDriver): self.add_filters_for_instance(instance, network_info) self.iptables.apply() - def add_filters_for_instance(self, instance, network_info=None): - if not network_info: - network_info = _get_network_info(instance) - chain_name = self._instance_chain_name(instance) - - self.iptables.ipv4['filter'].add_chain(chain_name) + def _create_filter(self, ips, chain_name): + return ['-d %s -j $%s' % (ip, chain_name) for ip in ips] + def _filters_for_instance(self, chain_name, network_info): ips_v4 = [ip['ip'] for (_, mapping) in network_info - for ip in mapping['ips']] - - for ipv4_address in ips_v4: - self.iptables.ipv4['filter'].add_rule('local', - '-d %s -j $%s' % - (ipv4_address, chain_name)) - - if FLAGS.use_ipv6: - self.iptables.ipv6['filter'].add_chain(chain_name) - ips_v6 = [ip['ip'] for (_, mapping) in network_info - for ip in mapping['ip6s']] + for ip in mapping['ips']] + ipv4_rules = self._create_filter(ips_v4, chain_name) - for ipv6_address in ips_v6: - self.iptables.ipv6['filter'].add_rule('local', - '-d %s -j $%s' % - (ipv6_address, - chain_name)) + ips_v6 = [ip['ip'] for (_, mapping) in network_info + for ip in mapping['ip6s']] - ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info) + ipv6_rules = self._create_filter(ips_v6, chain_name) + return ipv4_rules, ipv6_rules + def _add_filters(self, chain_name, ipv4_rules, ipv6_rules): for rule in ipv4_rules: self.iptables.ipv4['filter'].add_rule(chain_name, rule) @@ -1912,6 +1899,17 @@ class IptablesFirewallDriver(FirewallDriver): for rule in ipv6_rules: self.iptables.ipv6['filter'].add_rule(chain_name, rule) + def add_filters_for_instance(self, instance, network_info=None): + chain_name = self._instance_chain_name(instance) + if FLAGS.use_ipv6: + self.iptables.ipv6['filter'].add_chain(chain_name) + self.iptables.ipv4['filter'].add_chain(chain_name) + ipv4_rules, ipv6_rules = self._filters_for_instance(chain_name, + network_info) + self._add_filters('local', ipv4_rules, ipv6_rules) + ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info) + self._add_filters(chain_name, ipv4_rules, ipv6_rules) + def remove_filters_for_instance(self, instance): chain_name = self._instance_chain_name(instance) -- cgit From 917f7aafbfa0a797687d10a600a218517f9b75e0 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 4 Apr 2011 22:22:27 +0400 Subject: add test for NWFilterFirewall --- nova/tests/test_virt.py | 19 +++++++++++---- nova/virt/libvirt_conn.py | 60 ++++++++++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index ae813cb80..b3d701efe 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -884,6 +884,12 @@ class NWFilterTestCase(test.TestCase): return db.security_group_get_by_name(self.context, 'fake', 'testgroup') + def _create_instance(self): + return db.instance_create(self.context, + {'user_id': 'fake', + 'project_id': 'fake', + 'mac_address': '00:A0:C9:14:C8:29'}) + def test_creates_base_rule_first(self): # These come pre-defined by libvirt self.defined_filters = ['no-mac-spoofing', @@ -912,10 +918,7 @@ class NWFilterTestCase(test.TestCase): self.fake_libvirt_connection.nwfilterDefineXML = _filterDefineXMLMock - instance_ref = db.instance_create(self.context, - {'user_id': 'fake', - 'project_id': 'fake', - 'mac_address': '00:A0:C9:14:C8:29'}) + instance_ref = self._create_instance() inst_id = instance_ref['id'] ip = '10.11.12.13' @@ -955,3 +958,11 @@ class NWFilterTestCase(test.TestCase): _ensure_all_called() self.teardown_security_group() db.instance_destroy(admin_ctxt, instance_ref['id']) + + + def test_create_network_filters(self): + instance_ref = self._create_instance() + network_info = _create_network_info(3) + result = \ + self.fw._create_network_filters(instance_ref, network_info, "fake") + self.assertEquals(len(result), 3) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 6c99e5448..57d0f4355 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1740,10 +1740,7 @@ class NWFilterFirewall(FirewallDriver): """ if not network_info: network_info = _get_network_info(instance) - if instance['image_id'] == FLAGS.vpn_image_id: - base_filter = 'nova-vpn' - else: - base_filter = 'nova-base' + ctxt = context.get_admin_context() @@ -1755,41 +1752,60 @@ class NWFilterFirewall(FirewallDriver): 'nova-base-ipv6', 'nova-allow-dhcp-server'] + if FLAGS.use_ipv6: + networks = [network for (network, _) in network_info if + network['gateway_v6']] + + if networks: + instance_secgroup_filter_children.\ + append('nova-allow-ra-server') + for security_group in \ db.security_group_get_by_instance(ctxt, instance['id']): self.refresh_security_group_rules(security_group['id']) - instance_secgroup_filter_children += [('nova-secgroup-%s' % - security_group['id'])] + instance_secgroup_filter_children.append('nova-secgroup-%s' % + security_group['id']) self._define_filter( self._filter_container(instance_secgroup_filter_name, instance_secgroup_filter_children)) - for (network, mapping) in network_info: - nic_id = mapping['mac'].replace(':', '') - instance_filter_name = self._instance_filter_name(instance, nic_id) - instance_filter_children = \ - [base_filter, instance_secgroup_filter_name] + network_filters = self.\ + _create_network_filters(instance, network_info, + instance_secgroup_filter_name) - if FLAGS.use_ipv6: - gateway_v6 = network['gateway_v6'] + for (name, children) in network_filters: + self._define_filters(name, children) - if gateway_v6: - instance_secgroup_filter_children += \ - ['nova-allow-ra-server'] + + def _create_network_filters(self, instance, network_info, + instance_secgroup_filter_name): + if instance['image_id'] == FLAGS.vpn_image_id: + base_filter = 'nova-vpn' + else: + base_filter = 'nova-base' + + result = [] + for (_, mapping) in network_info: + nic_id = mapping['mac'].replace(':', '') + instance_filter_name = self._instance_filter_name(instance, nic_id) + instance_filter_children = [base_filter, + instance_secgroup_filter_name] if FLAGS.allow_project_net_traffic: - instance_filter_children += ['nova-project'] + instance_filter_children.append('nova-project') if FLAGS.use_ipv6: - instance_filter_children += ['nova-project-v6'] + instance_filter_children.append('nova-project-v6') - self._define_filter( - self._filter_container(instance_filter_name, - instance_filter_children)) + result.append((instance_filter_name, instance_filter_children)) - return + return result + + def _define_filters(self, filter_name, filter_children): + self._define_filter(self._filter_container(filter_name, + filter_children)) def refresh_security_group_rules(self, security_group_id): return self._define_filter( -- cgit From e057d7fd01def4db0c77b962fea925177de9a91f Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 4 Apr 2011 15:20:09 -0400 Subject: fixing log message --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index fb3ca5306..1dc5624eb 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -218,7 +218,7 @@ class VMOps(object): return False if state == power_state.RUNNING: - LOG.debug(_('VM %s is now running.') % name) + LOG.debug(_('VM %s is now running.') % instance_name) timer.stop() _inject_files() return True -- cgit From 5e74b5a5f121c9f0be2c529b76878615812d9483 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 4 Apr 2011 23:43:26 +0400 Subject: splitting test_get_nic_for_xml into two functions --- nova/tests/test_virt.py | 23 ++++++++++++----------- nova/virt/libvirt_conn.py | 2 -- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index b3d701efe..061797b04 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -226,16 +226,18 @@ class LibvirtConnTestCase(test.TestCase): def test_get_nic_for_xml(self): conn = libvirt_conn.LibvirtConnection(True) network, mapping = _create_network_info()[0] - backup = FLAGS.use_ipv6 - FLAGS.use_ipv6 = False - params_1 = conn._get_nic_for_xml(network, mapping)['extra_params'] - FLAGS.use_ipv6 = True - params_2 = conn._get_nic_for_xml(network, mapping)['extra_params'] - self.assertTrue(params_1.find('PROJNETV6') == -1) - self.assertTrue(params_1.find('PROJMASKV6') == -1) - self.assertTrue(params_2.find('PROJNETV6') > -1) - self.assertTrue(params_2.find('PROJMASKV6') > -1) - FLAGS.use_ipv6 = backup + self.flags(use_ipv6=False) + params = conn._get_nic_for_xml(network, mapping)['extra_params'] + self.assertTrue(params.find('PROJNETV6') == -1) + self.assertTrue(params.find('PROJMASKV6') == -1) + + def test_get_nic_for_xml_v6(self): + conn = libvirt_conn.LibvirtConnection(True) + network, mapping = _create_network_info()[0] + self.flags(use_ipv6=True) + params = conn._get_nic_for_xml(network, mapping)['extra_params'] + self.assertTrue(params.find('PROJNETV6') > -1) + self.assertTrue(params.find('PROJMASKV6') > -1) def test_xml_and_uri_no_ramdisk_no_kernel(self): instance_data = dict(self.test_instance) @@ -959,7 +961,6 @@ class NWFilterTestCase(test.TestCase): self.teardown_security_group() db.instance_destroy(admin_ctxt, instance_ref['id']) - def test_create_network_filters(self): instance_ref = self._create_instance() network_info = _create_network_info(3) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 57d0f4355..0ca2cce9a 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1741,7 +1741,6 @@ class NWFilterFirewall(FirewallDriver): if not network_info: network_info = _get_network_info(instance) - ctxt = context.get_admin_context() instance_secgroup_filter_name = \ @@ -1779,7 +1778,6 @@ class NWFilterFirewall(FirewallDriver): for (name, children) in network_filters: self._define_filters(name, children) - def _create_network_filters(self, instance, network_info, instance_secgroup_filter_name): if instance['image_id'] == FLAGS.vpn_image_id: -- cgit From 24c2da40549e882be716e1897fd81aef8f172e53 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 5 Apr 2011 14:47:06 -0400 Subject: adding support for OSAPI v1.1 limits resource --- nova/api/openstack/__init__.py | 9 ++- nova/api/openstack/limits.py | 26 ++++--- nova/tests/api/openstack/test_limits.py | 121 ++++++++++++++++++++++++++++---- 3 files changed, 131 insertions(+), 25 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 7545eb0c9..0e0a0646e 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -111,9 +111,6 @@ class APIRouter(wsgi.Router): parent_resource=dict(member_name='server', collection_name='servers')) - _limits = limits.LimitsController() - mapper.resource("limit", "limits", controller=_limits) - super(APIRouter, self).__init__(mapper) @@ -144,6 +141,9 @@ class APIRouterV10(APIRouter): parent_resource=dict(member_name='server', collection_name='servers')) + mapper.resource("limit", "limits", + controller=limits.LimitsControllerV10()) + class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" @@ -172,3 +172,6 @@ class APIRouterV11(APIRouter): mapper.resource("flavor", "flavors", controller=flavors.ControllerV11(), collection={'detail': 'GET'}) + + mapper.resource("limit", "limits", + controller=limits.LimitsControllerV11()) diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index efc7d193d..f9e047e97 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -32,6 +32,7 @@ from webob.dec import wsgify from nova import wsgi from nova.api.openstack import faults +from nova.api.openstack.views import limits as limits_views from nova.wsgi import Controller from nova.wsgi import Middleware @@ -51,8 +52,8 @@ class LimitsController(Controller): _serialization_metadata = { "application/xml": { "attributes": { - "limit": ["verb", "URI", "regex", "value", "unit", - "resetTime", "remaining", "name"], + "limit": ["verb", "URI", "uri", "regex", "value", "unit", + "resetTime", "next-available", "remaining", "name"], }, "plurals": { "rate": "limit", @@ -67,12 +68,21 @@ class LimitsController(Controller): abs_limits = {} rate_limits = req.environ.get("nova.limits", []) - return { - "limits": { - "rate": rate_limits, - "absolute": abs_limits, - }, - } + builder = self._get_view_builder(req) + return builder.build(rate_limits, abs_limits) + + def _get_view_builder(self, req): + raise NotImplementedError() + + +class LimitsControllerV10(LimitsController): + def _get_view_builder(self, req): + return limits_views.ViewBuilderV10() + + +class LimitsControllerV11(LimitsController): + def _get_view_builder(self, req): + return limits_views.ViewBuilderV11() class Limit(object): diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 05cfacc60..f557c0599 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -28,15 +28,14 @@ import webob from xml.dom.minidom import parseString from nova.api.openstack import limits -from nova.api.openstack.limits import Limit TEST_LIMITS = [ - Limit("GET", "/delayed", "^/delayed", 1, limits.PER_MINUTE), - Limit("POST", "*", ".*", 7, limits.PER_MINUTE), - Limit("POST", "/servers", "^/servers", 3, limits.PER_MINUTE), - Limit("PUT", "*", "", 10, limits.PER_MINUTE), - Limit("PUT", "/servers", "^/servers", 5, limits.PER_MINUTE), + limits.Limit("GET", "/delayed", "^/delayed", 1, limits.PER_MINUTE), + limits.Limit("POST", "*", ".*", 7, limits.PER_MINUTE), + limits.Limit("POST", "/servers", "^/servers", 3, limits.PER_MINUTE), + limits.Limit("PUT", "*", "", 10, limits.PER_MINUTE), + limits.Limit("PUT", "/servers", "^/servers", 5, limits.PER_MINUTE), ] @@ -58,15 +57,15 @@ class BaseLimitTestSuite(unittest.TestCase): return self.time -class LimitsControllerTest(BaseLimitTestSuite): +class LimitsControllerV10Test(BaseLimitTestSuite): """ - Tests for `limits.LimitsController` class. + Tests for `limits.LimitsControllerV10` class. """ def setUp(self): """Run before each test.""" BaseLimitTestSuite.setUp(self) - self.controller = limits.LimitsController() + self.controller = limits.LimitsControllerV10() def _get_index_request(self, accept_header="application/json"): """Helper to set routing arguments.""" @@ -81,8 +80,8 @@ class LimitsControllerTest(BaseLimitTestSuite): def _populate_limits(self, request): """Put limit info into a request.""" _limits = [ - Limit("GET", "*", ".*", 10, 60).display(), - Limit("POST", "*", ".*", 5, 60 * 60).display(), + limits.Limit("GET", "*", ".*", 10, 60).display(), + limits.Limit("POST", "*", ".*", 5, 60 * 60).display(), ] request.environ["nova.limits"] = _limits return request @@ -163,6 +162,100 @@ class LimitsControllerTest(BaseLimitTestSuite): self.assertEqual(expected.toxml(), body.toxml()) +class LimitsControllerV11Test(BaseLimitTestSuite): + """ + Tests for `limits.LimitsControllerV11` class. + """ + + def setUp(self): + """Run before each test.""" + BaseLimitTestSuite.setUp(self) + self.controller = limits.LimitsControllerV11() + + def _get_index_request(self, accept_header="application/json"): + """Helper to set routing arguments.""" + request = webob.Request.blank("/") + request.accept = accept_header + request.environ["wsgiorg.routing_args"] = (None, { + "action": "index", + "controller": "", + }) + return request + + def _populate_limits(self, request): + """Put limit info into a request.""" + _limits = [ + limits.Limit("GET", "*", ".*", 10, 60).display(), + limits.Limit("POST", "*", ".*", 5, 60 * 60).display(), + limits.Limit("GET", "changes-since*", "changes-since", + 5, 60).display(), + ] + request.environ["nova.limits"] = _limits + return request + + def test_empty_index_json(self): + """Test getting empty limit details in JSON.""" + request = self._get_index_request() + response = request.get_response(self.controller) + expected = { + "limits": { + "rate": [], + "absolute": {}, + }, + } + body = json.loads(response.body) + self.assertEqual(expected, body) + + def test_index_json(self): + """Test getting limit details in JSON.""" + request = self._get_index_request() + request = self._populate_limits(request) + response = request.get_response(self.controller) + expected = { + "limits": { + "rate": [ + { + "regex": "changes-since", + "uri": "changes-since*", + "limits": [ + { + "verb": "GET", + "next-available": 0, + "unit": "MINUTE", + "value": 5, + "remaining": 5, + }, + ], + }, + { + "regex": ".*", + "uri": "*", + "limits": [ + { + "verb": "GET", + "next-available": 0, + "unit": "MINUTE", + "value": 10, + "remaining": 10, + }, + { + "verb": "POST", + "next-available": 0, + "unit": "HOUR", + "value": 5, + "remaining": 5, + }, + ], + }, + + ], + "absolute": {}, + }, + } + body = json.loads(response.body) + self.assertEqual(expected, body) + + class LimitMiddlewareTest(BaseLimitTestSuite): """ Tests for the `limits.RateLimitingMiddleware` class. @@ -177,7 +270,7 @@ class LimitMiddlewareTest(BaseLimitTestSuite): """Prepare middleware for use through fake WSGI app.""" BaseLimitTestSuite.setUp(self) _limits = [ - Limit("GET", "*", ".*", 1, 60), + limits.Limit("GET", "*", ".*", 1, 60), ] self.app = limits.RateLimitingMiddleware(self._empty_app, _limits) @@ -230,7 +323,7 @@ class LimitTest(BaseLimitTestSuite): def test_GET_no_delay(self): """Test a limit handles 1 GET per second.""" - limit = Limit("GET", "*", ".*", 1, 1) + limit = limits.Limit("GET", "*", ".*", 1, 1) delay = limit("GET", "/anything") self.assertEqual(None, delay) self.assertEqual(0, limit.next_request) @@ -238,7 +331,7 @@ class LimitTest(BaseLimitTestSuite): def test_GET_delay(self): """Test two calls to 1 GET per second limit.""" - limit = Limit("GET", "*", ".*", 1, 1) + limit = limits.Limit("GET", "*", ".*", 1, 1) delay = limit("GET", "/anything") self.assertEqual(None, delay) -- cgit From 4a8a08790803d574ecd1dce4b3765cbce7e1043a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 12:56:25 -0700 Subject: fixed comment --- nova/auth/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 12ded1207..f2451702c 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -372,7 +372,7 @@ class AuthManager(object): (Project.safe_id(project) if project else 'None'))) def _clear_mc_key(self, user, role, project=None): - # (anthony) it would be better to delete the key + # NOTE(anthony): it would be better to delete the key self.mc.set(self._build_mc_key(user, role, project), None) def _has_role(self, user, role, project=None): -- cgit From 36857e5091234940e3ac68d154c019fbd5d28af5 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 15:58:19 -0700 Subject: remove -None for user roles --- nova/auth/manager.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index f2451702c..3de2ceffe 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -368,8 +368,10 @@ class AuthManager(object): return True def _build_mc_key(self, user, role, project=None): - return str("rolecache-%s-%s-%s" % (User.safe_id(user), role, - (Project.safe_id(project) if project else 'None'))) + role_key = str("rolecache-%s-%s" % (User.safe_id(user), role)) + if project: + return "%s-%s" % (role_key, Project.safe_id(project)) + return role_key def _clear_mc_key(self, user, role, project=None): # NOTE(anthony): it would be better to delete the key -- cgit From dfcdafde2dc202aa3325cd1cea8d809f56fdde57 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Tue, 5 Apr 2011 19:21:05 -0700 Subject: return image create response as image dict --- nova/api/openstack/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index e77100d7b..e3f93f635 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -130,7 +130,7 @@ class Controller(wsgi.Controller): raise webob.exc.HTTPBadRequest() image = self._compute_service.snapshot(context, server_id, image_name) - return self.get_builder(req).build(image, detail=True) + return dict(image=self.get_builder(req).build(image, detail=True)) def get_builder(self, request): """Indicates that you must use a Controller subclass.""" -- cgit From 01051c71c8f405e9f40a088509d09f171aea1c7d Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Thu, 7 Apr 2011 02:07:19 +0900 Subject: Enable RightAWS style signing on server_string without port number portion. --- nova/auth/manager.py | 9 +++++++++ nova/utils.py | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 486845399..b51cc7f4b 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -315,6 +315,15 @@ class AuthManager(object): LOG.debug('expected_signature: %s', expected_signature) LOG.debug('signature: %s', signature) if signature != expected_signature: + secondary = utils.get_secondary_server_string(server_string) + if secondary is not '': + secondary_signature = signer.Signer( + user.secret.encode()).generate(params, verb, + secondary, path) + LOG.debug('secondary_signature: %s', secondary_signature) + if signature == secondary_signature: + return (user, project) + # NOTE(itoumsn): RightAWS success case. LOG.audit(_("Invalid signature for user %s"), user.name) raise exception.NotAuthorized(_('Signature does not match')) return (user, project) diff --git a/nova/utils.py b/nova/utils.py index 3f6f9fc8a..8fd464452 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -714,3 +714,24 @@ def check_isinstance(obj, cls): raise Exception(_("Expected object of type: %s") % (str(cls))) # TODO(justinsb): Can we make this better?? return cls() # Ugly PyLint hack + +def get_secondary_server_string(str): + """Returns host part only of the given server_string if it's a combination + of host part and port. Otherwise, return null string.""" + + # First of all, exclude pure IPv6 address (w/o port). + if netaddr.valid_ipv6(str): + return '' + + # Next, check if this is IPv6 address with port number combination. + if str.find("]:") != -1: + [address, sep, port] = str.replace('[', '', 1).partition(']:') + return address + + # Third, check if this is a combination of general address and port + if str.find(':') == -1: + return '' + + # This must be a combination of host part and port + [address, sep, port] = str.partition(':') + return address -- cgit From 430975c2f7e354838a26cd81e59b5c0423a2c8fe Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 6 Apr 2011 19:13:18 -0700 Subject: ApiError code should default to None, and will only display a code if one exists. Prior was output an 'ApiError: ApiError: error message' string, which is confusing --- nova/exception.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index 4e2bbdbaf..c0bc68b6c 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -46,10 +46,14 @@ class Error(Exception): class ApiError(Error): - def __init__(self, message='Unknown', code='ApiError'): + def __init__(self, message='Unknown', code=None): self.message = message self.code = code - super(ApiError, self).__init__('%s: %s' % (code, message)) + if code: + outstr = '%s: %s' % (code, message) + else: + outstr = '%s' % message + super(ApiError, self).__init__(outstr) class NotFound(Error): -- cgit From 8decff01ad8ee7e8f7d4103a727321b162280cbe Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Thu, 7 Apr 2011 11:42:02 +0900 Subject: pep8 cleanup. --- nova/auth/manager.py | 2 +- nova/utils.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index b51cc7f4b..f1d4a1e39 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -315,7 +315,7 @@ class AuthManager(object): LOG.debug('expected_signature: %s', expected_signature) LOG.debug('signature: %s', signature) if signature != expected_signature: - secondary = utils.get_secondary_server_string(server_string) + secondary = utils.get_secondary_server_string(server_string) if secondary is not '': secondary_signature = signer.Signer( user.secret.encode()).generate(params, verb, diff --git a/nova/utils.py b/nova/utils.py index 8fd464452..3e938247f 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -715,6 +715,7 @@ def check_isinstance(obj, cls): # TODO(justinsb): Can we make this better?? return cls() # Ugly PyLint hack + def get_secondary_server_string(str): """Returns host part only of the given server_string if it's a combination of host part and port. Otherwise, return null string.""" @@ -724,14 +725,14 @@ def get_secondary_server_string(str): return '' # Next, check if this is IPv6 address with port number combination. - if str.find("]:") != -1: + if str.find("]:") != -1: [address, sep, port] = str.replace('[', '', 1).partition(']:') - return address + return address # Third, check if this is a combination of general address and port - if str.find(':') == -1: - return '' + if str.find(':') == -1: + return '' # This must be a combination of host part and port [address, sep, port] = str.partition(':') - return address + return address -- cgit From 4c1c0b8357e2cffd5f9a2a1240439e1871f845f2 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 6 Apr 2011 19:43:58 -0700 Subject: add the tests --- nova/tests/test_exception.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 nova/tests/test_exception.py diff --git a/nova/tests/test_exception.py b/nova/tests/test_exception.py new file mode 100644 index 000000000..65df20a61 --- /dev/null +++ b/nova/tests/test_exception.py @@ -0,0 +1,35 @@ +# 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 test +from nova import exception + + +class ApiErrorTestCase(test.TestCase): + + def test_return_valid_error(self): + # without 'code' arg + err = exception.ApiError('fake error') + self.assertEqual(err.__str__(), 'fake error') + self.assertEqual(err.code, None) + self.assertEqual(err.message, 'fake error') + # with 'code' arg + err = exception.ApiError('fake error', 'blah code') + self.assertEqual(err.__str__(), 'blah code: fake error') + self.assertEqual(err.code, 'blah code') + self.assertEqual(err.message, 'fake error') -- cgit From 1ee150c449e630c6409798399eccb577c8273c70 Mon Sep 17 00:00:00 2001 From: Muneyuki Noguchi Date: Thu, 7 Apr 2011 12:35:45 +0900 Subject: Make description of volume_id more generic. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 85bcd7590..383eda0dc 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -786,7 +786,7 @@ class ComputeManager(manager.SchedulerDependentManager): """Remove volume on compute host. :param context: security context - :param volume_id: nova.db.sqlalchemy.models.Volume.id + :param volume_id: volume ID """ self.volume_manager.remove_compute_volume(context, volume_id) -- cgit From 8c4fa0f16ac170662e113edfdc0f8d3c8863f082 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Thu, 7 Apr 2011 23:48:00 +0900 Subject: Blush up a bit. --- nova/auth/manager.py | 14 +++++++------- nova/utils.py | 8 +++++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index f1d4a1e39..c8a3a46a2 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -315,15 +315,15 @@ class AuthManager(object): LOG.debug('expected_signature: %s', expected_signature) LOG.debug('signature: %s', signature) if signature != expected_signature: - secondary = utils.get_secondary_server_string(server_string) - if secondary is not '': - secondary_signature = signer.Signer( + host_only = utils.get_host_only_server_string(server_string) + # If the given server_string contains port num, try without it. + if host_only is not '': + host_only_signature = signer.Signer( user.secret.encode()).generate(params, verb, - secondary, path) - LOG.debug('secondary_signature: %s', secondary_signature) - if signature == secondary_signature: + host_only, path) + LOG.debug('host_only_signature: %s', host_only_signature) + if signature == host_only_signature: return (user, project) - # NOTE(itoumsn): RightAWS success case. LOG.audit(_("Invalid signature for user %s"), user.name) raise exception.NotAuthorized(_('Signature does not match')) return (user, project) diff --git a/nova/utils.py b/nova/utils.py index 3e938247f..8b7cbf30c 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -716,9 +716,11 @@ def check_isinstance(obj, cls): return cls() # Ugly PyLint hack -def get_secondary_server_string(str): - """Returns host part only of the given server_string if it's a combination - of host part and port. Otherwise, return null string.""" +def get_host_only_server_string(str): + """ + Returns host part only of the given server_string if it's a combination + of host part and port. Otherwise, return null string. + """ # First of all, exclude pure IPv6 address (w/o port). if netaddr.valid_ipv6(str): -- cgit From a1572a4f234bdeda1d25250de62b5892d8f47891 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 7 Apr 2011 15:00:35 -0700 Subject: clarified nova-manage instance_type create error output on duplicate flavorid --- bin/nova-manage | 10 +++++++--- nova/compute/instance_types.py | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 25695482f..a999571b2 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -819,6 +819,7 @@ class InstanceTypeCommands(object): """Class for managing instance types / flavors.""" def _print_instance_types(self, n, val): + """helper method to print out instance_types values""" deleted = ('', ', inactive')[val["deleted"] == 1] print ("%s: Memory: %sMB, VCPUS: %s, Storage: %sGB, FlavorID: %s, " "Swap: %sGB, RXTX Quota: %sGB, RXTX Cap: %sMB%s") % ( @@ -836,11 +837,14 @@ class InstanceTypeCommands(object): instance_types.create(name, memory, vcpus, local_gb, flavorid, swap, rxtx_quota, rxtx_cap) except exception.InvalidInputException: - print "Must supply valid parameters to create instance type" + print "Must supply valid parameters to create instance_type" print e sys.exit(1) - except exception.DBError, e: - print "DB Error: %s" % e + except exception.ApiError, e: + print e + print "Please ensure instance_type name and flavor id are unique" + print "Here are the already defined instance_type names and flavorids:" + self.list("--all") sys.exit(2) except: print "Unknown error" diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index fa02a5dfa..bfa120675 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -60,7 +60,9 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, rxtx_cap=rxtx_cap)) except exception.DBError, e: LOG.exception(_('DB error: %s' % e)) - raise exception.ApiError(_("Cannot create instance type: %s" % name)) + raise exception.ApiError( + _("Cannot create instance_type with name %s and flavorid %s"\ + % (name, flavorid))) def destroy(name): -- cgit From 59b460e98c5b8f718a654539c5788e8775126dfd Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 7 Apr 2011 15:08:29 -0700 Subject: slight typo --- bin/nova-manage | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index a999571b2..5369a98b3 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -841,7 +841,6 @@ class InstanceTypeCommands(object): print e sys.exit(1) except exception.ApiError, e: - print e print "Please ensure instance_type name and flavor id are unique" print "Here are the already defined instance_type names and flavorids:" self.list("--all") -- cgit From 86ffed4e988025023b570b9e6e87f89b6075c7b0 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 7 Apr 2011 17:38:24 -0700 Subject: reminde admins of --purge option --- bin/nova-manage | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index e63898979..73da83767 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -841,8 +841,10 @@ class InstanceTypeCommands(object): print e sys.exit(1) except exception.ApiError, e: - print "Please ensure instance_type name and flavor id are unique" - print "Here are the already defined instance_type names and flavorids:" + print "\n\nPlease ensure instance_type name and flavor id are unique." + print "To complete remove a instance_type, use the --purge flag:" + print "\n # nova-manage instance_type delete --purge\n" + print "Currently defined instance_type names and flavorids:" self.list("--all") sys.exit(2) except: -- cgit From c47c545b293d5b73f46ff18ace2f4b9db61a771f Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Sun, 10 Apr 2011 02:49:38 +0900 Subject: debug tree status checkpoint. --- nova/virt/libvirt_conn.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 2be190256..300672ae5 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -471,6 +471,9 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def reboot(self, instance): + # NOTE(itoumsn): self.shutdown() and wait instead of destroy would be + # better because we cannot ensure flushing dirty buffers + # in the guest OS. But, in case of KVM, shutdown often fails... self.destroy(instance, False) xml = self.to_xml(instance) self.firewall_driver.setup_basic_filtering(instance) @@ -496,7 +499,26 @@ class LibvirtConnection(driver.ComputeDriver): timer.stop() timer.f = _wait_for_reboot - return timer.start(interval=0.5, now=True) + timer_result=timer.start(interval=0.5, now=True) + + # Fix lp747922 + instance_id = instance['id'] + for vol in db.volume_get_all_by_instance(context.get_admin_context(), + instance_id): +# LOG.debug(_("re-attaching: %s") % vol['ec2_id']) +# instance-id : instance-00000001 +# device_path : /dev/etherd/e0.1, /dev/mapper/nova--volumes-volume--00000001 +# mountpoint : sdh +# BTW, is iSCSI working? + LOG.debug(_("instance_id: %s, volume_id: %s, mountpoint: %s") % + (instance_id, vol['id'], vol['mountpoint'])) +# self.attach_volume(instance['name'], vol['id'], vol['mountpoint']) + + self.attach_volume(instance['name'], + '/dev/mapper/nova--volumes-volume--00000001', + 'vdb') + + return timer_result @exception.wrap_exception def pause(self, instance, callback): -- cgit From 92df1dc754126895a052bb648c1613201455f714 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Tue, 12 Apr 2011 02:09:29 +0900 Subject: debug tree status checkpoint 2. --- nova/virt/libvirt_conn.py | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 567e17a91..0b1ed2a93 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -487,6 +487,28 @@ class LibvirtConnection(driver.ComputeDriver): instance['id'], state) if state == power_state.RUNNING: LOG.debug(_('instance %s: rebooted'), instance['name']) + + # Fix lp747922 + instance_id = instance['id'] + for vol in db.volume_get_all_by_instance( + context.get_admin_context(), instance_id): + # LOG.debug(_("re-attaching: %s") % vol['ec2_id']) + # instance-id : instance-00000001 + # device_path : /dev/etherd/e0.1, etc + # mountpoint : /dev/sdh + # dev_path is not stored anywhere, and it has driver + # specific format. Therefore, noway other than calling + # discover_driver here. + dev_path = nova.volume.driver.discover_volume(self, + context, + vol) + LOG.debug(_("instance_id: %s, volume_id: %s, mountpoint: %s") % + (instance_id, dev_path, vol['mountpoint'])) + self.attach_volume(instance['name'], + dev_path, + vol['mountpoint']); + # Fix lp747922 + timer.stop() except Exception, exn: LOG.exception(_('_wait_for_reboot failed: %s'), exn) @@ -497,24 +519,6 @@ class LibvirtConnection(driver.ComputeDriver): timer.f = _wait_for_reboot timer_result=timer.start(interval=0.5, now=True) - - # Fix lp747922 - instance_id = instance['id'] - for vol in db.volume_get_all_by_instance(context.get_admin_context(), - instance_id): -# LOG.debug(_("re-attaching: %s") % vol['ec2_id']) -# instance-id : instance-00000001 -# device_path : /dev/etherd/e0.1, /dev/mapper/nova--volumes-volume--00000001 -# mountpoint : sdh -# BTW, is iSCSI working? - LOG.debug(_("instance_id: %s, volume_id: %s, mountpoint: %s") % - (instance_id, vol['id'], vol['mountpoint'])) -# self.attach_volume(instance['name'], vol['id'], vol['mountpoint']) - - self.attach_volume(instance['name'], - '/dev/mapper/nova--volumes-volume--00000001', - 'vdb') - return timer_result @exception.wrap_exception -- cgit From 2295e47b1f85fb199c7e4bf514f6781d7033dd77 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Tue, 12 Apr 2011 02:30:31 +0900 Subject: A minor blush up. --- nova/virt/libvirt_conn.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index b9f6c482e..d47e8f422 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -566,8 +566,7 @@ class LibvirtConnection(driver.ComputeDriver): timer.stop() timer.f = _wait_for_reboot - timer_result=timer.start(interval=0.5, now=True) - return timer_result + return timer.start(interval=0.5, now=True) @exception.wrap_exception def pause(self, instance, callback): -- cgit From 7a2f3d6007a1da365a008cca163cf493668a71de Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Tue, 12 Apr 2011 02:32:19 +0900 Subject: A minor blush up. --- nova/virt/libvirt_conn.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d47e8f422..7670d3989 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -535,12 +535,10 @@ class LibvirtConnection(driver.ComputeDriver): instance['id'], state) if state == power_state.RUNNING: LOG.debug(_('instance %s: rebooted'), instance['name']) - # Fix lp747922 instance_id = instance['id'] for vol in db.volume_get_all_by_instance( context.get_admin_context(), instance_id): - # LOG.debug(_("re-attaching: %s") % vol['ec2_id']) # instance-id : instance-00000001 # device_path : /dev/etherd/e0.1, etc # mountpoint : /dev/sdh -- cgit From b6975a79e91a531ea7501aeb0dbf6c7c07a6722b Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Tue, 12 Apr 2011 03:13:58 +0900 Subject: Minor blush ups. --- nova/virt/libvirt_conn.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 7670d3989..0a9ee688a 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -536,7 +536,7 @@ class LibvirtConnection(driver.ComputeDriver): if state == power_state.RUNNING: LOG.debug(_('instance %s: rebooted'), instance['name']) # Fix lp747922 - instance_id = instance['id'] + instance_id = instance['id'] for vol in db.volume_get_all_by_instance( context.get_admin_context(), instance_id): # instance-id : instance-00000001 @@ -548,11 +548,11 @@ class LibvirtConnection(driver.ComputeDriver): dev_path = nova.volume.driver.discover_volume(self, context, vol) - LOG.debug(_("instance_id: %s, volume_id: %s, mountpoint: %s") % - (instance_id, dev_path, vol['mountpoint'])) + LOG.debug( + _("Re-attaching %(dev_path)s to %(mountpoint)s") % + (dev_path, vol['mountpoint'])) self.attach_volume(instance['name'], - dev_path, - vol['mountpoint']); + dev_path, vol['mountpoint']) # Fix lp747922 timer.stop() -- cgit From 9ce66a4a09094d2b0403deea77416149aa789f3c Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 11 Apr 2011 22:35:09 +0400 Subject: Floating ips auto assignment --- nova/compute/manager.py | 21 +++++++++++++++++++++ nova/db/api.py | 3 +++ nova/db/sqlalchemy/api.py | 12 ++++++++++++ 3 files changed, 36 insertions(+) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 68b163355..86273b6b4 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -73,6 +73,8 @@ flags.DEFINE_integer('live_migration_retry_count', 30, flags.DEFINE_integer("rescue_timeout", 0, "Automatically unrescue an instance after N seconds." " Set to 0 to disable.") +flags.DEFINE_bool('auto_assign_floating_ip',False, 'Autoassigning floating' + ' ip to VM') LOG = logging.getLogger('nova.compute.manager') @@ -224,6 +226,16 @@ class ComputeManager(manager.SchedulerDependentManager): self.network_manager.setup_compute_network(context, instance_id) + if FLAGS.auto_assign_floating_ip: + public_ip = rpc.call(context, + FLAGS.network_topic, + {"method": "allocate_floating_ip", + "args": {"project_id": context.project_id}}) + self.network_manager.associate_floating_ip(context, + instance_id=instance_id, + address=public_ip) + + # TODO(vish) check to make sure the availability zone matches self.db.instance_set_state(context, instance_id, @@ -271,6 +283,15 @@ class ComputeManager(manager.SchedulerDependentManager): network_topic, {"method": "disassociate_floating_ip", "args": {"floating_address": address}}) + + if FLAGS.auto_assign_floating_ip: + LOG.debug(_("Deallocating floating ip %s"), + floating_ip['address'], context=context) + rpc.cast(context, + FLAGS.network_topic, + {"method": "deallocate_floating_ip", + "args": {"floating_address": + floating_ip['address']}}) address = fixed_ip['address'] if address: diff --git a/nova/db/api.py b/nova/db/api.py index 63901e94d..859acc146 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -290,6 +290,9 @@ def floating_ip_update(context, address, values): """Update a floating ip by address or raise if it doesn't exist.""" return IMPL.floating_ip_update(context, address, values) +def floating_ip_set_auto_assigned(context, address): + """Set auto_assigned flag to floating ip""" + return IMPL.floating_ip_set_auto_assigned(context, address) #################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e675022e9..28126a517 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -489,6 +489,7 @@ def floating_ip_deallocate(context, address): address, session=session) floating_ip_ref['project_id'] = None + floating_ip_ref['auto_assigned'] = False floating_ip_ref.save(session=session) @@ -522,6 +523,17 @@ def floating_ip_disassociate(context, address): return fixed_ip_address +@require_context +def floating_ip_set_auto_assigned(context, address): + session = get_session() + with session.begin(): + floating_ip_ref = floating_ip_get_by_address(context, + address, + session=session) + floating_ip_ref.auto_assigned = True + floating_ip_ref.save(session=session) + + @require_admin_context def floating_ip_get_all(context): session = get_session() -- cgit From 4a2c973fe5c7cf68ff7f45a4927dc6d2e0a3986b Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Tue, 12 Apr 2011 02:44:44 +0400 Subject: migaration and pep8 fixes --- nova/api/openstack/contrib/volumes.py | 3 +- nova/compute/manager.py | 4 +-- nova/db/api.py | 2 ++ .../015_add_auto_assign_to_floating_ips.py | 38 ++++++++++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index 6efacce52..18de2ec71 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -322,8 +322,7 @@ class Volumes(extensions.ExtensionDescriptor): # Does this matter? res = extensions.ResourceExtension('volumes', VolumeController(), - collection_actions={'detail': 'GET'} - ) + collection_actions={'detail': 'GET'}) resources.append(res) res = extensions.ResourceExtension('volume_attachments', diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 86273b6b4..af3551708 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -73,7 +73,7 @@ flags.DEFINE_integer('live_migration_retry_count', 30, flags.DEFINE_integer("rescue_timeout", 0, "Automatically unrescue an instance after N seconds." " Set to 0 to disable.") -flags.DEFINE_bool('auto_assign_floating_ip',False, 'Autoassigning floating' +flags.DEFINE_bool('auto_assign_floating_ip', False, 'Autoassigning floating' ' ip to VM') LOG = logging.getLogger('nova.compute.manager') @@ -235,7 +235,6 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id=instance_id, address=public_ip) - # TODO(vish) check to make sure the availability zone matches self.db.instance_set_state(context, instance_id, @@ -283,7 +282,6 @@ class ComputeManager(manager.SchedulerDependentManager): network_topic, {"method": "disassociate_floating_ip", "args": {"floating_address": address}}) - if FLAGS.auto_assign_floating_ip: LOG.debug(_("Deallocating floating ip %s"), floating_ip['address'], context=context) diff --git a/nova/db/api.py b/nova/db/api.py index 859acc146..6b465c021 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -290,12 +290,14 @@ def floating_ip_update(context, address, values): """Update a floating ip by address or raise if it doesn't exist.""" return IMPL.floating_ip_update(context, address, values) + def floating_ip_set_auto_assigned(context, address): """Set auto_assigned flag to floating ip""" return IMPL.floating_ip_set_auto_assigned(context, address) #################### + def migration_update(context, id, values): """Update a migration instance""" return IMPL.migration_update(context, id, values) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py new file mode 100644 index 000000000..b7e480e67 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -0,0 +1,38 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# Copyright 2011 Grid Dynamics +# +# 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 sqlalchemy import * +from sqlalchemy.sql import text +from migrate import * + + +meta = MetaData() + +c_auto_assigned = Column('auto_assigned',Boolean, default=False) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + + floating_ips = Table('floating_ips', meta, autoload=True, + autoload_with=migrate_engine) + + floating_ips.create_column(c_auto_assigned) + + \ No newline at end of file -- cgit From fa4aeb9af8d00ecff6620646c142e5ff68e1cd5e Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Tue, 12 Apr 2011 03:07:22 +0400 Subject: pep8 fixes --- .../migrate_repo/versions/015_add_auto_assign_to_floating_ips.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py index b7e480e67..6829b5d6e 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -22,7 +22,7 @@ from migrate import * meta = MetaData() -c_auto_assigned = Column('auto_assigned',Boolean, default=False) +c_auto_assigned = Column('auto_assigned', Boolean, default=False) def upgrade(migrate_engine): @@ -33,6 +33,4 @@ def upgrade(migrate_engine): floating_ips = Table('floating_ips', meta, autoload=True, autoload_with=migrate_engine) - floating_ips.create_column(c_auto_assigned) - - \ No newline at end of file + floating_ips.create_column(c_auto_assigned) \ No newline at end of file -- cgit From b2f693f63d73e3e51cb3be40b5deae720c773340 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 09:23:52 -0400 Subject: Reverted some superfluous changes to make MP more concise. --- nova/compute/api.py | 52 ++++++++++++++++++------------------ nova/compute/manager.py | 11 ++++---- nova/virt/libvirt_conn.py | 67 +++++++++-------------------------------------- 3 files changed, 45 insertions(+), 85 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 020d3b06d..5ec88adbd 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -105,32 +105,6 @@ class API(base.Base): if len(content) > content_limit: raise quota.QuotaError(code="OnsetFileContentLimitExceeded") - def _check_metadata_quota(self, context, metadata): - num_metadata = len(metadata) - quota_metadata = quota.allowed_metadata_items(context, num_metadata) - if quota_metadata < num_metadata: - pid = context.project_id - msg = (_("Quota exceeeded for %(pid)s," - " tried to set %(num_metadata)s metadata properties") - % locals()) - LOG.warn(msg) - raise quota.QuotaError(msg, "MetadataLimitExceeded") - - def _check_metadata_item_length(self, context, metadata): - # Because metadata is stored in the DB, we hard-code the size limits - # In future, we may support more variable length strings, so we act - # as if this is quota-controlled for forwards compatibility - for metadata_item in metadata: - k = metadata_item['key'] - v = metadata_item['value'] - if len(k) > 255 or len(v) > 255: - pid = context.project_id - msg = (_("Quota exceeeded for %(pid)s," - " metadata property key or value too long") - % locals()) - LOG.warn(msg) - raise quota.QuotaError(msg, "MetadataLimitExceeded") - def create(self, context, instance_type, image_id, kernel_id=None, ramdisk_id=None, min_count=1, max_count=1, @@ -267,6 +241,32 @@ class API(base.Base): return [dict(x.iteritems()) for x in instances] + def _check_metadata_quota(self, context, metadata): + num_metadata = len(metadata) + quota_metadata = quota.allowed_metadata_items(context, num_metadata) + if quota_metadata < num_metadata: + pid = context.project_id + msg = (_("Quota exceeeded for %(pid)s," + " tried to set %(num_metadata)s metadata properties") + % locals()) + LOG.warn(msg) + raise quota.QuotaError(msg, "MetadataLimitExceeded") + + def _check_metadata_item_length(self, context, metadata): + # Because metadata is stored in the DB, we hard-code the size limits + # In future, we may support more variable length strings, so we act + # as if this is quota-controlled for forwards compatibility + for metadata_item in metadata: + k = metadata_item['key'] + v = metadata_item['value'] + if len(k) > 255 or len(v) > 255: + pid = context.project_id + msg = (_("Quota exceeeded for %(pid)s," + " metadata property key or value too long") + % locals()) + LOG.warn(msg) + raise quota.QuotaError(msg, "MetadataLimitExceeded") + def has_finished_migration(self, context, instance_id): """Retrieves whether or not a finished migration exists for an instance""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 51f5322cd..04f2abc49 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -150,10 +150,11 @@ class ComputeManager(manager.SchedulerDependentManager): info = self.driver.get_info(instance_ref['name']) except exception.NotFound: info = None - state = power_state.FAILED if info is not None: state = info['state'] + else: + state = power_state.FAILED self.db.instance_set_state(context, instance_id, state) @@ -246,10 +247,10 @@ class ComputeManager(manager.SchedulerDependentManager): self.driver.spawn(instance_ref) self._update_launched_at(context, instance_id) except Exception as ex: # pylint: disable=W0702 - LOG.debug(ex) - LOG.exception(_("Instance '%s' failed to spawn. Is virtualization" - " enabled in the BIOS?"), instance_id, - context=context) + msg = _("Instance '%(instance_id)s' failed to spawn. Is " + "virtualization enabled in the BIOS? Details: " + "%(ex)s") % locals() + LOG.exception(msg) self._update_state(context, instance_id) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9714773b2..53382a315 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -309,27 +309,6 @@ class LibvirtConnection(driver.ComputeDriver): return infos def destroy(self, instance, cleanup=True): -<<<<<<< TREE - """Delete the VM instance from the hypervisor. - - :param instance: Object representing the instance to destroy - :param cleanup: Should we erase all of the VM's associated files? - """ - name = instance['name'] - - try: - virt_dom = self._conn.lookupByName(name) - except libvirt.libvirtError as ex: - msg = _("Instance %s not found.") % name - raise exception.NotFound(msg) - - try: - virt_dom.destroy() - except libvirt.libvirtError as ex: - # If the instance is already terminated, we're still happy - msg = _("Error encountered during `libvirt.destroy`: %s") % ex - LOG.debug(msg) -======= instance_name = instance['name'] # TODO(justinsb): Refactor all lookupByName calls for error-handling @@ -624,38 +603,16 @@ class LibvirtConnection(driver.ComputeDriver): # for xenapi(tr3buchet) @exception.wrap_exception def spawn(self, instance, network_info=None): - """Create the given VM instance using the libvirt connection. - - :param instance: Object representing the instance to create - :param network_info: Associated network information - """ - _id = instance['id'] - name = instance['name'] xml = self.to_xml(instance, network_info) self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) self._create_image(instance, xml, network_info) -<<<<<<< TREE - - try: - self._conn.createXML(xml, 0) - except libvirt.libvirtError as ex: - msg = _("Error encountered creating VM '%(name)s': %(ex)s") - LOG.error(msg % locals()) - return False - - LOG.debug(_("VM %s successfully created.") % name) - -======= domain = self._create_new_domain(xml) LOG.debug(_("instance %s: is running"), instance['name']) ->>>>>>> MERGE-SOURCE self.firewall_driver.apply_instance_filter(instance) -<<<<<<< TREE -======= if FLAGS.start_guests_on_host_boot: LOG.debug(_("instance %s: setting autostart ON") % instance['name']) @@ -663,21 +620,23 @@ class LibvirtConnection(driver.ComputeDriver): timer = utils.LoopingCall(f=None) ->>>>>>> MERGE-SOURCE def _wait_for_boot(): - """Check to see if the VM is running.""" try: - state = self.get_info(name)['state'] - except (exception.NotFound, libvirt.libvirtError) as ex: - msg = _("Error while waiting for VM '%(_id)s' to run: %(ex)s") - LOG.debug(msg % locals()) - timer.stop() - - if state == power_state.RUNNING: - LOG.debug(_('VM %s is now running.') % name) + state = self.get_info(instance['name'])['state'] + db.instance_set_state(context.get_admin_context(), + instance['id'], state) + if state == power_state.RUNNING: + LOG.debug(_('instance %s: booted'), instance['name']) + timer.stop() + except: + LOG.exception(_('instance %s: failed to boot'), + instance['name']) + db.instance_set_state(context.get_admin_context(), + instance['id'], + power_state.SHUTDOWN) timer.stop() - timer = utils.LoopingCall(f=_wait_for_boot) + timer.f = _wait_for_boot return timer.start(interval=0.5, now=True) def _flush_xen_console(self, virsh_output): -- cgit From 764862180657dbc16b2d57d3b2027c23b86ea649 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 09:34:52 -0400 Subject: Reverted some superfluous changes to make MP more concise. --- nova/virt/xenapi/vmops.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 6ed065280..135e59a34 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -206,27 +206,27 @@ class VMOps(object): # NOTE(armando): Do we really need to do this in virt? # NOTE(tr3buchet): not sure but wherever we do it, we need to call # reset_network afterwards + timer = utils.LoopingCall(f=None) def _wait_for_boot(): try: state = self.get_info(instance_name)['state'] - except self.XenAPI.Failure as ex: - msg = _("Error while waiting for VM '%(instance_name)s' " - "to boot: %(ex)s") % locals() - LOG.debug(msg) + if state == power_state.RUNNING: + LOG.debug(_('Instance %s: booted'), instance_name) + timer.stop() + _inject_files() + return True + except Exception, exc: + LOG.warn(exc) + LOG.exception(_('Instance %s: failed to boot'), instance_name) timer.stop() return False - if state == power_state.RUNNING: - LOG.debug(_('VM %s is now running.') % instance_name) - timer.stop() - _inject_files() - return True + timer.f = _wait_for_boot # call to reset network to configure network from xenstore self.reset_network(instance, vm_ref) - timer = utils.LoopingCall(f=_wait_for_boot) return timer.start(interval=0.5, now=True) def _get_vm_opaque_ref(self, instance_or_vm): -- cgit From ae30b0a83469b15d1986fdbbef4f1dee52d68c17 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 09:37:06 -0400 Subject: Removed extra call from try/except. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 04f2abc49..a8bb091ec 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -245,13 +245,13 @@ class ComputeManager(manager.SchedulerDependentManager): try: self.driver.spawn(instance_ref) - self._update_launched_at(context, instance_id) except Exception as ex: # pylint: disable=W0702 msg = _("Instance '%(instance_id)s' failed to spawn. Is " "virtualization enabled in the BIOS? Details: " "%(ex)s") % locals() LOG.exception(msg) + self._update_launched_at(context, instance_id) self._update_state(context, instance_id) @exception.wrap_exception -- cgit From cebc98176926f57016a508d5c59b11f55dfcf2b3 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 10:19:37 -0400 Subject: Commit for merge of metadata_quotas preq. --- nova/api/openstack/servers.py | 103 ++++++++++++++++++++++--------- nova/compute/api.py | 20 ++++-- nova/tests/api/openstack/test_servers.py | 1 + 3 files changed, 90 insertions(+), 34 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c9f0e1b1c..61e08211d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -606,22 +606,32 @@ class ControllerV10(Controller): except exception.TimeoutException: return exc.HTTPRequestTimeout() - def _action_rebuild(self, input_dict, req, id): - context = req.environ['nova.context'] - if (not 'rebuild' in input_dict - or not 'imageId' in input_dict['rebuild']): - msg = _("No imageId was specified") - return faults.Fault(exc.HTTPBadRequest(msg)) + def _action_rebuild(self, info, request, instance_id): + context = request.environ['nova.context'] + instance_id = int(instance_id) - image_id = input_dict['rebuild']['imageId'] + try: + image_id = info["rebuild"]["imageId"] + except (KeyError, TypeError): + msg = _("Could not parse imageId from request.") + LOG.debug(msg) + return faults.Fault(exc.HTTPBadRequest(explanation=msg)) try: - self.compute_api.rebuild(context, id, image_id) + self.compute_api.rebuild(context, instance_id, image_id) except exception.BuildInProgress: - msg = _("Unable to rebuild server that is being rebuilt") + msg = _("Instance %d is currently being rebuilt.") % instance_id + LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) + except exception.Error as ex: + msg = _("Error encountered attempting to rebuild instance " + "%(instance_id): %(ex)") % locals() + LOG.error(msg) + raise - return exc.HTTPAccepted() + response = exc.HTTPAccepted() + response.empty_body = True + return response class ControllerV11(Controller): @@ -662,33 +672,66 @@ class ControllerV11(Controller): def _limit_items(self, items, req): return common.limited_by_marker(items, req) - def _action_rebuild(self, input_dict, req, id): - context = req.environ['nova.context'] - if (not 'rebuild' in input_dict - or not 'imageRef' in input_dict['rebuild']): - msg = _("No imageRef was specified") - return faults.Fault(exc.HTTPBadRequest(msg)) - - image_ref = input_dict['rebuild']['imageRef'] - image_id = common.get_id_from_href(image_ref) + def _check_metadata(self, metadata): + """Ensure that the metadata given is of the correct type.""" + try: + metadata.iteritems() + except AttributeError as ex: + msg = _("Unable to parse metadata key/value pairs.") + LOG.debug(msg) + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + + def _check_personalities(self, personalities): + """Ensure the given personalities have valid paths and contents.""" + for personality in personalities: + try: + path = personality["path"] + contents = personality["contents"] + except (KeyError, TypeError): + msg = _("Unable to parse personality path/contents.") + LOG.info(msg) + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) - metadata = [] - if 'metadata' in input_dict['rebuild']: try: - for k, v in input_dict['rebuild']['metadata'].items(): - metadata.append({'key': k, 'value': v}) + base64.b64decode(contents) + except TypeError: + msg = _("Personality content could not be Base64 decoded.") + LOG.info(msg) + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + + def _action_rebuild(self, info, request, instance_id): + context = request.environ['nova.context'] + instance_id = int(instance_id) + + try: + image_ref = info["rebuild"]["imageRef"] + except (KeyError, TypeError): + msg = _("Could not parse imageRef from request.") + return faults.Fault(exc.HTTPBadRequest(explanation=msg)) - except Exception: - msg = _("Improperly formatted metadata provided") - return exc.HTTPBadRequest(msg) + image_id = common.get_id_from_href(image_ref) + personalities = info["rebuild"].get("personality", []) + metadata = info["rebuild"].get("metadata", {}) + + self._check_metadata(metadata) + self._check_personalities(personalities) try: - self.compute_api.rebuild(context, id, image_id, metadata) + args = [context, instance_id, image_id, metadata, personalities] + self.compute_api.rebuild(*args) except exception.BuildInProgress: - msg = _("Unable to rebuild server that is being rebuilt") + msg = _("Instance %d is currently being rebuilt.") % instance_id + LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - - return exc.HTTPAccepted() + except exception.Error as ex: + msg = _("Error encountered attempting to rebuild instance " + "%(instance_id): %(ex)") % locals() + LOG.error(msg) + raise + + response = exc.HTTPAccepted() + response.empty_body = True + return response def get_default_xmlns(self, req): return common.XML_NS_V11 diff --git a/nova/compute/api.py b/nova/compute/api.py index 5ec88adbd..e5065c3a5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -497,10 +497,11 @@ class API(base.Base): """Reboot the given instance.""" self._cast_compute_message('reboot_instance', context, instance_id) - def rebuild(self, context, instance_id, image_id, metadata=None): + def rebuild(self, context, instance_id, image_id, metadata=None, + files_to_inject=None): """Rebuild the given instance with the provided metadata.""" - instance = db.api.instance_get(context, instance_id) + if instance["state"] == power_state.BUILDING: msg = _("Instance already building") raise exception.BuildInProgress(msg) @@ -509,11 +510,22 @@ class API(base.Base): self._check_metadata_quota(context, metadata) self._check_metadata_item_length(context, metadata) - self._cast_compute_message('rebuild_instance', context, - instance_id, params={"image_id": image_id}) + files_to_inject = files_to_inject or [] + self._check_injected_file_quota(context, files_to_inject) + self._check_injected_file_format(context, files_to_inject) self.db.instance_update(context, instance_id, {"metadata": metadata}) + rebuild_params = { + "image_id": image_id, + "injected_files": files_to_inject, + } + + self._cast_compute_message('rebuild_instance', + context, + instance_id, + params=rebuild_params) + def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process""" context = context.elevated() diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 424ed2ec6..ccbdc4b38 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -981,6 +981,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) + self.assertEqual(res.body, "") def test_server_rebuild_rejected_when_building(self): body = { -- cgit From 5de1825e2c1d1cdc63790f61e05b1f8b05ded1b3 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 11:09:31 -0400 Subject: Cleanup after prereq merge. --- nova/compute/api.py | 42 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index effe68d6d..c99d7f828 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -105,6 +105,15 @@ class API(base.Base): if len(content) > content_limit: raise quota.QuotaError(code="OnsetFileContentLimitExceeded") + def _check_injected_file_format(self, injected_files): + """Ensure given injected files are in the correct format.""" + for _, content in injected_files: + try: + base64.b64decode(content) + except TypeError: + msg = _("File contents must be base64 encoded.") + raise exception.Error(msg) + def _check_metadata_properties_quota(self, context, metadata={}): """Enforce quota limits on metadata properties""" num_metadata = len(metadata) @@ -263,32 +272,6 @@ class API(base.Base): return [dict(x.iteritems()) for x in instances] - def _check_metadata_quota(self, context, metadata): - num_metadata = len(metadata) - quota_metadata = quota.allowed_metadata_items(context, num_metadata) - if quota_metadata < num_metadata: - pid = context.project_id - msg = (_("Quota exceeeded for %(pid)s," - " tried to set %(num_metadata)s metadata properties") - % locals()) - LOG.warn(msg) - raise quota.QuotaError(msg, "MetadataLimitExceeded") - - def _check_metadata_item_length(self, context, metadata): - # Because metadata is stored in the DB, we hard-code the size limits - # In future, we may support more variable length strings, so we act - # as if this is quota-controlled for forwards compatibility - for metadata_item in metadata: - k = metadata_item['key'] - v = metadata_item['value'] - if len(k) > 255 or len(v) > 255: - pid = context.project_id - msg = (_("Quota exceeeded for %(pid)s," - " metadata property key or value too long") - % locals()) - LOG.warn(msg) - raise quota.QuotaError(msg, "MetadataLimitExceeded") - def has_finished_migration(self, context, instance_id): """Retrieves whether or not a finished migration exists for an instance""" @@ -528,13 +511,12 @@ class API(base.Base): msg = _("Instance already building") raise exception.BuildInProgress(msg) - metadata = metadata or [] - self._check_metadata_quota(context, metadata) - self._check_metadata_item_length(context, metadata) + metadata = metadata or {} + self._check_metadata_properties_quota(context, metadata) files_to_inject = files_to_inject or [] self._check_injected_file_quota(context, files_to_inject) - self._check_injected_file_format(context, files_to_inject) + self._check_injected_file_format(files_to_inject) self.db.instance_update(context, instance_id, {"metadata": metadata}) -- cgit From 2576c733c05dfd9872423f52319c28a65834ee61 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 11:13:31 -0400 Subject: Dangerous whitespace mistake! :) --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 135e59a34..7f9814a10 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -222,7 +222,7 @@ class VMOps(object): timer.stop() return False - timer.f = _wait_for_boot + timer.f = _wait_for_boot # call to reset network to configure network from xenstore self.reset_network(instance, vm_ref) -- cgit From e5e1863349a1842d3f6ca452a59e574c03102ebf Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 12 Apr 2011 11:47:08 -0400 Subject: Added some tests. --- nova/compute/api.py | 1 + nova/tests/api/openstack/test_servers.py | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/nova/compute/api.py b/nova/compute/api.py index c99d7f828..3bf18c667 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -20,6 +20,7 @@ Handles all requests relating to instances (guest vms). """ +import base64 import datetime import re import time diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index ccbdc4b38..0aa2ba3ac 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1097,6 +1097,44 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_server_rebuild_bad_personality_v11(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "personality": [{ + "path": "/path/to/file", + "contents": "INVALID b64", + }] + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_rebuild_personality_v11(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "personality": [{ + "path": "/path/to/file", + "contents": base64.b64encode("Test String"), + }] + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + def test_delete_server_instance(self): req = webob.Request.blank('/v1.0/servers/1') req.method = 'DELETE' -- cgit From 32d081f8f0a50b87f7b5d3f5bab4cf4ba92b1b4d Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Wed, 13 Apr 2011 02:11:36 +0900 Subject: Blushed up a little bit. --- nova/auth/manager.py | 2 +- nova/utils.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index c8a3a46a2..01aa87e31 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -317,7 +317,7 @@ class AuthManager(object): if signature != expected_signature: host_only = utils.get_host_only_server_string(server_string) # If the given server_string contains port num, try without it. - if host_only is not '': + if host_only != '': host_only_signature = signer.Signer( user.secret.encode()).generate(params, verb, host_only, path) diff --git a/nova/utils.py b/nova/utils.py index 8b7cbf30c..369b5265c 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -716,25 +716,25 @@ def check_isinstance(obj, cls): return cls() # Ugly PyLint hack -def get_host_only_server_string(str): +def get_host_only_server_string(server_str): """ Returns host part only of the given server_string if it's a combination of host part and port. Otherwise, return null string. """ # First of all, exclude pure IPv6 address (w/o port). - if netaddr.valid_ipv6(str): + if netaddr.valid_ipv6(server_str): return '' # Next, check if this is IPv6 address with port number combination. - if str.find("]:") != -1: - [address, sep, port] = str.replace('[', '', 1).partition(']:') + if server_str.find("]:") != -1: + [address, sep, port] = server_str.replace('[', '', 1).partition(']:') return address # Third, check if this is a combination of general address and port - if str.find(':') == -1: + if server_str.find(':') == -1: return '' # This must be a combination of host part and port - [address, sep, port] = str.partition(':') + [address, sep, port] = server_str.partition(':') return address -- cgit From a4791a2d2b4f44c636b7f7694e92bed615309070 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Wed, 13 Apr 2011 02:41:33 +0900 Subject: Rework importing volume_manager. --- nova/virt/libvirt_conn.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 0a9ee688a..990779d47 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -33,7 +33,8 @@ Supports KVM, LXC, QEMU, UML, and XEN. :rescue_ramdisk_id: Rescue ari image (default: ari-rescue). :injected_network_template: Template file for injected network :allow_project_net_traffic: Whether to allow in project network traffic - +:volume_manager: Name of class that handles persistent storage, loaded by + :func:`nova.utils.import_object` """ import multiprocessing @@ -218,6 +219,8 @@ class LibvirtConnection(driver.ComputeDriver): fw_class = utils.import_class(FLAGS.firewall_driver) self.firewall_driver = fw_class(get_connection=self._get_connection) + # NOTE(itoumsn): This is an ugly hack to re-attach volumes on reboot. + self.volume_manager = utils.import_object(FLAGS.volume_manager) def init_host(self, host): # Adopt existing VM's running here @@ -539,15 +542,12 @@ class LibvirtConnection(driver.ComputeDriver): instance_id = instance['id'] for vol in db.volume_get_all_by_instance( context.get_admin_context(), instance_id): - # instance-id : instance-00000001 - # device_path : /dev/etherd/e0.1, etc - # mountpoint : /dev/sdh # dev_path is not stored anywhere, and it has driver - # specific format. Therefore, noway other than calling - # discover_driver here. - dev_path = nova.volume.driver.discover_volume(self, - context, - vol) + # specific format. Furthermore, compute node specific. + # Therefore, noway other than calling discover_driver + # here. + dev_path = self.volume_manager.driver.discover_volume( + context, vol) LOG.debug( _("Re-attaching %(dev_path)s to %(mountpoint)s") % (dev_path, vol['mountpoint'])) -- cgit From 1ca1e83040cb2899c108415c899eee54c760afe3 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Wed, 13 Apr 2011 14:51:26 +0900 Subject: iSCSI/KVM test completed. --- nova/virt/libvirt_conn.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 990779d47..fae48ba4a 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -538,23 +538,23 @@ class LibvirtConnection(driver.ComputeDriver): instance['id'], state) if state == power_state.RUNNING: LOG.debug(_('instance %s: rebooted'), instance['name']) - # Fix lp747922 + # Re-attach volumes instance_id = instance['id'] for vol in db.volume_get_all_by_instance( context.get_admin_context(), instance_id): - # dev_path is not stored anywhere, and it has driver - # specific format. Furthermore, compute node specific. - # Therefore, noway other than calling discover_driver - # here. + # NOTE(itoumsn): dev_path is not stored anywhere, + # and it has driver specific format. Furthermore, it's + # also compute node specific in general. + # Therefore, no way other than calling + # undiscover/discover_driver here at this moment. + self.volume_manager.driver.undiscover_volume(vol) dev_path = self.volume_manager.driver.discover_volume( context, vol) LOG.debug( - _("Re-attaching %(dev_path)s to %(mountpoint)s") % + _("Re-attaching %s to %s") % (dev_path, vol['mountpoint'])) self.attach_volume(instance['name'], dev_path, vol['mountpoint']) - # Fix lp747922 - timer.stop() except Exception, exn: LOG.exception(_('_wait_for_reboot failed: %s'), exn) -- cgit From ea07b74b8b0fd912555b4193f6e29a2dcd86f4b0 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Thu, 14 Apr 2011 02:22:41 +0900 Subject: An ultimate workaround workd... :( --- nova/virt/libvirt_conn.py | 93 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 16 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index fae48ba4a..870deae31 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -33,8 +33,6 @@ Supports KVM, LXC, QEMU, UML, and XEN. :rescue_ramdisk_id: Rescue ari image (default: ari-rescue). :injected_network_template: Template file for injected network :allow_project_net_traffic: Whether to allow in project network traffic -:volume_manager: Name of class that handles persistent storage, loaded by - :func:`nova.utils.import_object` """ import multiprocessing @@ -219,8 +217,6 @@ class LibvirtConnection(driver.ComputeDriver): fw_class = utils.import_class(FLAGS.firewall_driver) self.firewall_driver = fw_class(get_connection=self._get_connection) - # NOTE(itoumsn): This is an ugly hack to re-attach volumes on reboot. - self.volume_manager = utils.import_object(FLAGS.volume_manager) def init_host(self, host): # Adopt existing VM's running here @@ -522,8 +518,66 @@ class LibvirtConnection(driver.ComputeDriver): # NOTE(itoumsn): self.shutdown() and wait instead of destroy would be # better because we cannot ensure flushing dirty buffers # in the guest OS. But, in case of KVM, shutdown often fails... +# instance_id = instance['id'] +# volume_list = [] +# dev_path_list = {} +# vols = db.volume_get_all_by_instance(context.get_admin_context(), +# instance_id) +# LOG.debug(_("DEBUG: vols %s") % vols) +# for vol in vols: +# LOG.debug(_("DEBUG: reboot: %s %s") % (vol['mountpoint'], +# vol['mountpoint'].rpartition("/")[2])) +# volume_list.append(vol['mountpoint'].partition("/")[2]) +# +# if len(volume_list) != 0: +# LOG.debug(_("DEBUG: volume_list is not empty")) +# virt_dom = self._conn.lookupByName(instance['name']) +# xml = virt_dom.XMLDesc(0) +# try: +# doc = libxml2.parseDoc(xml) +# except: +# LOG.exception(_('Failed to get xml description %s'), +# instance_id) +# ctx = doc.xpathNewContext() +# try: +# ret = ctx.xpathEval('/domain/devices/disk') +# for node in ret: +# LOG.debug(_("DEBUG: node.name %s") % node.name) +# target_dev = '' +# source_dev = '' +# for child in node.children: +# LOG.debug( +# _("child.name: %s prop.dev %s") % +# (child.name, child.prop('dev'))) +# +# if child.name == 'source': +# source_dev = child.prop('dev') +# elif child.name == 'target': +# target_dev = child.prop('dev') +# LOG.debug( +# _("source: %s target %s") % +# (source_dev, target_dev)) +# +# if target_dev in volume_list: +# dev_path_list[target_dev] = source_dev +# LOG.debug( +# _("append to dev_path_list source: %s target: %s") % +# (source_dev, target_dev)) +# else: +# LOG.debug(_("DEBUG: %s not found in volume_list") % +# (target_dev)) +# finally: +# LOG.debug(_("DEBUG: finally block")) +# if ctx != None: +# ctx.xpathFreeContext() +# if doc != None: +# doc.freeDoc() + + virt_dom = self._conn.lookupByName(instance['name']) + xml = virt_dom.XMLDesc(0) + self.destroy(instance, False) - xml = self.to_xml(instance) +# xml = self.to_xml(instance) self.firewall_driver.setup_basic_filtering(instance) self.firewall_driver.prepare_instance_filter(instance) self._create_new_domain(xml) @@ -539,22 +593,29 @@ class LibvirtConnection(driver.ComputeDriver): if state == power_state.RUNNING: LOG.debug(_('instance %s: rebooted'), instance['name']) # Re-attach volumes - instance_id = instance['id'] - for vol in db.volume_get_all_by_instance( - context.get_admin_context(), instance_id): +# for mp in dev_path_list.iterkeys(): +# LOG.debug( +# _("Re-attaching %s to %s") % +# (dev_path_list[mp], mp)) +# self.attach_volume(instance['name'], +# dev_path_list[mp], mp) +# +# instance_id = instance['id'] +# for vol in db.volume_get_all_by_instance( +# context.get_admin_context(), instance_id): # NOTE(itoumsn): dev_path is not stored anywhere, # and it has driver specific format. Furthermore, it's # also compute node specific in general. # Therefore, no way other than calling # undiscover/discover_driver here at this moment. - self.volume_manager.driver.undiscover_volume(vol) - dev_path = self.volume_manager.driver.discover_volume( - context, vol) - LOG.debug( - _("Re-attaching %s to %s") % - (dev_path, vol['mountpoint'])) - self.attach_volume(instance['name'], - dev_path, vol['mountpoint']) +# self.volume_manager.driver.undiscover_volume(vol) +# dev_path = self.volume_manager.driver.discover_volume( +# context, vol) +# LOG.debug( +# _("Re-attaching %s to %s") % +# (dev_path, vol['mountpoint'])) +# self.attach_volume(instance['name'], +# dev_path, vol['mountpoint']) timer.stop() except Exception, exn: LOG.exception(_('_wait_for_reboot failed: %s'), exn) -- cgit From 2d1235ea404d55f1cdf764798d7a071b3b60dc7e Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Thu, 14 Apr 2011 02:38:14 +0900 Subject: Debug code clean up. --- nova/virt/libvirt_conn.py | 91 ++++------------------------------------------- 1 file changed, 7 insertions(+), 84 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 870deae31..f273d47ce 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -33,6 +33,7 @@ Supports KVM, LXC, QEMU, UML, and XEN. :rescue_ramdisk_id: Rescue ari image (default: ari-rescue). :injected_network_template: Template file for injected network :allow_project_net_traffic: Whether to allow in project network traffic + """ import multiprocessing @@ -515,69 +516,15 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def reboot(self, instance): - # NOTE(itoumsn): self.shutdown() and wait instead of destroy would be - # better because we cannot ensure flushing dirty buffers - # in the guest OS. But, in case of KVM, shutdown often fails... -# instance_id = instance['id'] -# volume_list = [] -# dev_path_list = {} -# vols = db.volume_get_all_by_instance(context.get_admin_context(), -# instance_id) -# LOG.debug(_("DEBUG: vols %s") % vols) -# for vol in vols: -# LOG.debug(_("DEBUG: reboot: %s %s") % (vol['mountpoint'], -# vol['mountpoint'].rpartition("/")[2])) -# volume_list.append(vol['mountpoint'].partition("/")[2]) -# -# if len(volume_list) != 0: -# LOG.debug(_("DEBUG: volume_list is not empty")) -# virt_dom = self._conn.lookupByName(instance['name']) -# xml = virt_dom.XMLDesc(0) -# try: -# doc = libxml2.parseDoc(xml) -# except: -# LOG.exception(_('Failed to get xml description %s'), -# instance_id) -# ctx = doc.xpathNewContext() -# try: -# ret = ctx.xpathEval('/domain/devices/disk') -# for node in ret: -# LOG.debug(_("DEBUG: node.name %s") % node.name) -# target_dev = '' -# source_dev = '' -# for child in node.children: -# LOG.debug( -# _("child.name: %s prop.dev %s") % -# (child.name, child.prop('dev'))) -# -# if child.name == 'source': -# source_dev = child.prop('dev') -# elif child.name == 'target': -# target_dev = child.prop('dev') -# LOG.debug( -# _("source: %s target %s") % -# (source_dev, target_dev)) -# -# if target_dev in volume_list: -# dev_path_list[target_dev] = source_dev -# LOG.debug( -# _("append to dev_path_list source: %s target: %s") % -# (source_dev, target_dev)) -# else: -# LOG.debug(_("DEBUG: %s not found in volume_list") % -# (target_dev)) -# finally: -# LOG.debug(_("DEBUG: finally block")) -# if ctx != None: -# ctx.xpathFreeContext() -# if doc != None: -# doc.freeDoc() - virt_dom = self._conn.lookupByName(instance['name']) + # NOTE(itoumsn): Use XML delived from the running instance + # instead of using to_xml(instance). This is almost the ultimate + # stupid workaround. xml = virt_dom.XMLDesc(0) - + # NOTE(itoumsn): self.shutdown() and wait instead of self.destroy() is + # better because we cannot ensure flushing dirty buffers + # in the guest OS. But, in case of KVM, shutdown() does not work... self.destroy(instance, False) -# xml = self.to_xml(instance) self.firewall_driver.setup_basic_filtering(instance) self.firewall_driver.prepare_instance_filter(instance) self._create_new_domain(xml) @@ -592,30 +539,6 @@ class LibvirtConnection(driver.ComputeDriver): instance['id'], state) if state == power_state.RUNNING: LOG.debug(_('instance %s: rebooted'), instance['name']) - # Re-attach volumes -# for mp in dev_path_list.iterkeys(): -# LOG.debug( -# _("Re-attaching %s to %s") % -# (dev_path_list[mp], mp)) -# self.attach_volume(instance['name'], -# dev_path_list[mp], mp) -# -# instance_id = instance['id'] -# for vol in db.volume_get_all_by_instance( -# context.get_admin_context(), instance_id): - # NOTE(itoumsn): dev_path is not stored anywhere, - # and it has driver specific format. Furthermore, it's - # also compute node specific in general. - # Therefore, no way other than calling - # undiscover/discover_driver here at this moment. -# self.volume_manager.driver.undiscover_volume(vol) -# dev_path = self.volume_manager.driver.discover_volume( -# context, vol) -# LOG.debug( -# _("Re-attaching %s to %s") % -# (dev_path, vol['mountpoint'])) -# self.attach_volume(instance['name'], -# dev_path, vol['mountpoint']) timer.stop() except Exception, exn: LOG.exception(_('_wait_for_reboot failed: %s'), exn) -- cgit From 3d72f59530b1c974dca498fbca44e5720547fc61 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Wed, 13 Apr 2011 11:51:03 -0700 Subject: fixed error message i18n-ization. added test. --- nova/compute/instance_types.py | 8 ++++---- nova/tests/test_instance_types.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 70b43540f..158cf1e9d 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -59,10 +59,10 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, rxtx_quota=rxtx_quota, rxtx_cap=rxtx_cap)) except exception.DBError, e: - LOG.exception(_('DB error: %s' % e)) - raise exception.ApiError( - _("Cannot create instance_type with name %s and flavorid %s"\ - % (name, flavorid))) + LOG.exception(_('DB error: %s') % e) + raise exception.ApiError(_("Cannot create instance_type with\ + name %(name)s and flavorid %(flavorid)s") % + locals()) def destroy(name): diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py index 5d6d5e1f4..ec3bc5bbf 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -88,3 +88,13 @@ class InstanceTypeTestCase(test.TestCase): """Ensures that instance type creation fails with invalid args""" self.assertRaises(exception.ApiError, instance_types.destroy, "sfsfsdfdfs") + + def test_repeated_inst_types_should_raise_api_error(self): + """Ensures that instance duplicates raises ApiError""" + new_name = self.name + "dup" + instance_types.create(new_name, 256, 1, 120, self.flavorid + 1) + instance_types.destroy(new_name) + self.assertRaises( + exception.ApiError, + instance_types.create, new_name, 256, 1, 120, self.flavorid) + \ No newline at end of file -- cgit From 6a20cba0ea3c1e9945897ec27646d74d597492d7 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Wed, 13 Apr 2011 12:01:59 -0700 Subject: pep8 --- bin/nova-manage | 3 ++- nova/tests/test_instance_types.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 750cd2596..8ec3a1e64 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -832,7 +832,8 @@ class InstanceTypeCommands(object): print e sys.exit(1) except exception.ApiError, e: - print "\n\nPlease ensure instance_type name and flavor id are unique." + print "\n\n" + print "Please ensure instance_type name and flavorid are unique." print "To complete remove a instance_type, use the --purge flag:" print "\n # nova-manage instance_type delete --purge\n" print "Currently defined instance_type names and flavorids:" diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py index ec3bc5bbf..dd7d0737e 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -97,4 +97,3 @@ class InstanceTypeTestCase(test.TestCase): self.assertRaises( exception.ApiError, instance_types.create, new_name, 256, 1, 120, self.flavorid) - \ No newline at end of file -- cgit From b501eb0748ba629a4a742431a42af591f94b6b4c Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Thu, 14 Apr 2011 10:47:37 +0900 Subject: Updated following to RIck's comments. --- nova/auth/manager.py | 15 ++++++++------- nova/utils.py | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 01aa87e31..dc37ae063 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -300,9 +300,9 @@ class AuthManager(object): if check_type == 's3': sign = signer.Signer(user.secret.encode()) expected_signature = sign.s3_authorization(headers, verb, path) - LOG.debug('user.secret: %s', user.secret) - LOG.debug('expected_signature: %s', expected_signature) - LOG.debug('signature: %s', signature) + LOG.debug(_('user.secret: %s'), user.secret) + LOG.debug(_('expected_signature: %s'), expected_signature) + LOG.debug(_('signature: %s'), signature) if signature != expected_signature: LOG.audit(_("Invalid signature for user %s"), user.name) raise exception.NotAuthorized(_('Signature does not match')) @@ -311,9 +311,9 @@ class AuthManager(object): # secret isn't unicode expected_signature = signer.Signer(user.secret.encode()).generate( params, verb, server_string, path) - LOG.debug('user.secret: %s', user.secret) - LOG.debug('expected_signature: %s', expected_signature) - LOG.debug('signature: %s', signature) + LOG.debug(_('user.secret: %s'), user.secret) + LOG.debug(_('expected_signature: %s'), expected_signature) + LOG.debug(_('signature: %s'), signature) if signature != expected_signature: host_only = utils.get_host_only_server_string(server_string) # If the given server_string contains port num, try without it. @@ -321,7 +321,8 @@ class AuthManager(object): host_only_signature = signer.Signer( user.secret.encode()).generate(params, verb, host_only, path) - LOG.debug('host_only_signature: %s', host_only_signature) + LOG.debug(_('host_only_signature: %s'), + host_only_signature) if signature == host_only_signature: return (user, project) LOG.audit(_("Invalid signature for user %s"), user.name) diff --git a/nova/utils.py b/nova/utils.py index 369b5265c..b0f961b90 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -736,5 +736,5 @@ def get_host_only_server_string(server_str): return '' # This must be a combination of host part and port - [address, sep, port] = server_str.partition(':') + (address, port) = server_str.split(':') return address -- cgit From 1b460de2f881d3cda0fd58bacedc3886020e4ca7 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Thu, 14 Apr 2011 17:12:54 +0400 Subject: bugfix --- nova/compute/manager.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index af3551708..63d374326 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -226,15 +226,6 @@ class ComputeManager(manager.SchedulerDependentManager): self.network_manager.setup_compute_network(context, instance_id) - if FLAGS.auto_assign_floating_ip: - public_ip = rpc.call(context, - FLAGS.network_topic, - {"method": "allocate_floating_ip", - "args": {"project_id": context.project_id}}) - self.network_manager.associate_floating_ip(context, - instance_id=instance_id, - address=public_ip) - # TODO(vish) check to make sure the availability zone matches self.db.instance_set_state(context, instance_id, @@ -255,6 +246,16 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id, power_state.SHUTDOWN) + if not FLAGS.stub_network: + if FLAGS.auto_assign_floating_ip: + public_ip = rpc.call(context, + FLAGS.network_topic, + {"method": "allocate_floating_ip", + "args": {"project_id": context.project_id}}) + self.network_manager.associate_floating_ip(context, + floating_address=public_ip, + fixed_address=address) + self._update_state(context, instance_id) @exception.wrap_exception -- cgit From 76e643dc0b6b8b6e2ad499034f4d4491380e91ba Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Thu, 14 Apr 2011 21:23:40 +0400 Subject: bugfix --- nova/compute/manager.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 63d374326..94fee36a5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -252,9 +252,34 @@ class ComputeManager(manager.SchedulerDependentManager): FLAGS.network_topic, {"method": "allocate_floating_ip", "args": {"project_id": context.project_id}}) - self.network_manager.associate_floating_ip(context, - floating_address=public_ip, - fixed_address=address) + + fixed_ip = self.db.fixed_ip_get_by_address(context, address) + floating_ip = self.db.floating_ip_get_by_address(context, + public_ip) + # Check if the floating ip address is allocated + if floating_ip['project_id'] is None: + raise exception.Error(_("Address (%s) is not allocated") % + floating_ip['address']) + # Check if the floating ip address is allocated + # to the same project + if floating_ip['project_id'] != context.project_id: + LOG.warn(_("Address (%(address)s) is not allocated to your" + " project (%(project)s)"), + {'address': floating_ip['address'], + 'project': context.project_id}) + raise exception.Error(_("Address (%(address)s) is not " + "allocated to your project" + "(%(project)s)") % + {'address': floating_ip['address'], + 'project': context.project_id}) + + host = fixed_ip['network']['host'] + rpc.cast(context, + self.db.queue_get_for(context, + FLAGS.network_topic, host), + {"method": "associate_floating_ip", + "args": {"floating_address": floating_ip['address'], + "fixed_address": fixed_ip['address']}}) self._update_state(context, instance_id) -- cgit From 4b0785632ba626d34a8a9fae5e0a5c742660e2dc Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 14 Apr 2011 17:34:09 -0400 Subject: initial roundup of all 'exception.Invalid' cases --- nova/api/openstack/servers.py | 4 +- nova/exception.py | 90 +++++++++++++++++++++++++++++++++++++++++-- nova/network/vmwareapi_net.py | 16 +++----- nova/scheduler/driver.py | 27 ++++--------- nova/virt/libvirt_conn.py | 19 ++++----- nova/virt/vmwareapi/vmops.py | 13 ++++--- 6 files changed, 116 insertions(+), 53 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 43e0c7963..2332b1af7 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -561,9 +561,7 @@ class Controller(common.OpenstackController): """ image_id = image_meta['id'] if image_meta['status'] != 'active': - raise exception.Invalid( - _("Cannot build from image %(image_id)s, status not active") % - locals()) + raise exception.InstanceNotRunning(image_id=image_id) if image_meta.get('container_format') != 'ami': return None, None diff --git a/nova/exception.py b/nova/exception.py index 4e2bbdbaf..cbfbb2920 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -80,10 +80,6 @@ class NotEmpty(Error): pass -class Invalid(Error): - pass - - class InvalidInputException(Error): pass @@ -127,3 +123,89 @@ def wrap_exception(f): raise _wrap.func_name = f.func_name return _wrap + + +class NovaException(Exception): + message = "" + + def __init__(self, **kwargs): + self._error_string = _(self.message) % kwargs + + def __str__(self): + return self._error_string + + +#TODO: EOL this exception! +class Invalid(NovaException): + pass + + +class InstanceNotRunning(Invalid): + message = "Instance is not 'running'." + + +class InstanceNotSuspended(Invalid): + message = "Instance is not 'suspended'." + + +class InstanceSuspendFailure(Invalid): + message = "Failed to suspend instance: %(reason)s." + + +class InstanceResumeFailure(Invalid): + message = "Failed to resume server: %(reson)s." + + +class InstanceRebootFailure(Invalid): + message = "Failed to reboot instance: %(reason)s." + + +class ServiceUnavailable(Invalid): + message = "Service is unavailable at this time." + + +class VolumeServiceUnavailable(ServiceUnavailable): + message = "Volume service is unavailable at this time." + + +class ComputeServiceUnavailable(ServiceUnavailable): + message = "Compute service on %(host)s is unavailable at this time." + + +class UnableToMigrateToSelf(Invalid): + message = "Unable to migrate instance (%(instance_id)s) " \ + "to current host (%(host)s." + + +class SourceHostUnavailable(Invalid): + message = "Original compute host is unavailable at this time." + + +class InvalidHypervisorType(Invalid): + message = "The supplied hypervisor type of %(type)s is invalid." + + +class DestinationHypervisorTooOld(Invalid): + message = "The instance requires a newer hypervisor version than " \ + "has been provided." + + +class InvalidDevicePath(Invalid): + message = "The supplied device path (%(path)s) is invalid." + + +class InvalidCPUInfo(Invalid): + message = "Unacceptable CPU info: %(reason)s." + + +class InvalidVLANTag(Invalid): + message = "VLAN tag is not appropriate for the port group " \ + "%(bridge)s. Expected VLAN tag is %(tag)s, " \ + "but the one associated with the port group is %(pgroup)s." + + +class InvalidVLANPortGroup(Invalid): + message = "vSwitch which contains the port group %(bridge)s is " \ + "not associated with the desired physical adapter. " \ + "Expected vSwitch is %(expected)s, but the one associated " \ + "is %(actual)s." diff --git a/nova/network/vmwareapi_net.py b/nova/network/vmwareapi_net.py index 93e6584f0..6e1ed480b 100644 --- a/nova/network/vmwareapi_net.py +++ b/nova/network/vmwareapi_net.py @@ -75,17 +75,13 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): pg_vlanid, pg_vswitch = \ network_utils.get_vlanid_and_vswitch_for_portgroup(session, bridge) - # Check if the vsiwtch associated is proper + # Check if the vswitch associated is proper if pg_vswitch != vswitch_associated: - raise exception.Invalid(_("vSwitch which contains the port group " - "%(bridge)s is not associated with the desired " - "physical adapter. Expected vSwitch is " - "%(vswitch_associated)s, but the one associated" - " is %(pg_vswitch)s") % locals()) + raise exception.InvalidVLANPortGroup(bridge=bridge, + expected=vswitch_associated, + actual=pg_vswitch) # Check if the vlan id is proper for the port group if pg_vlanid != vlan_num: - raise exception.Invalid(_("VLAN tag is not appropriate for the " - "port group %(bridge)s. Expected VLAN tag is " - "%(vlan_num)s, but the one associated with the " - "port group is %(pg_vlanid)s") % locals()) + raise exception.InvalidVLANTag(bridge=bridge, tag=vlan_num, + pgroup=pg_vlanid) diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index ce05d9f6a..02e65357c 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -129,15 +129,14 @@ class Scheduler(object): if (power_state.RUNNING != instance_ref['state'] or \ 'running' != instance_ref['state_description']): ec2_id = instance_ref['hostname'] - raise exception.Invalid(_('Instance(%s) is not running') % ec2_id) + raise exception.InstanceNotRunning(instance_id=ec2_id) # Checing volume node is running when any volumes are mounted # to the instance. if len(instance_ref['volumes']) != 0: services = db.service_get_all_by_topic(context, 'volume') if len(services) < 1 or not self.service_is_up(services[0]): - raise exception.Invalid(_("volume node is not alive" - "(time synchronize problem?)")) + raise exception.VolumeServiceUnavailable() # Checking src host exists and compute node src = instance_ref['host'] @@ -145,8 +144,7 @@ class Scheduler(object): # Checking src host is alive. if not self.service_is_up(services[0]): - raise exception.Invalid(_("%s is not alive(time " - "synchronize problem?)") % src) + raise exception.ComputeServiceUnavailable(host=src) def _live_migration_dest_check(self, context, instance_ref, dest): """Live migration check routine (for destination host). @@ -163,17 +161,14 @@ class Scheduler(object): # Checking dest host is alive. if not self.service_is_up(dservice_ref): - raise exception.Invalid(_("%s is not alive(time " - "synchronize problem?)") % dest) + raise exception.ComputeServiceUnavailable(host=dest) # Checking whether The host where instance is running # and dest is not same. src = instance_ref['host'] if dest == src: ec2_id = instance_ref['hostname'] - raise exception.Invalid(_("%(dest)s is where %(ec2_id)s is " - "running now. choose other host.") - % locals()) + raise exception.UnableToMigrateToSelf(instance_id=ec2_id, host=dest) # Checking dst host still has enough capacities. self.assert_compute_node_has_enough_resources(context, @@ -204,26 +199,20 @@ class Scheduler(object): oservice_refs = db.service_get_all_compute_by_host(context, instance_ref['launched_on']) except exception.NotFound: - raise exception.Invalid(_("host %s where instance was launched " - "does not exist.") - % instance_ref['launched_on']) + raise exception.SourceHostUnavailable() oservice_ref = oservice_refs[0]['compute_node'][0] # Checking hypervisor is same. orig_hypervisor = oservice_ref['hypervisor_type'] dest_hypervisor = dservice_ref['hypervisor_type'] if orig_hypervisor != dest_hypervisor: - raise exception.Invalid(_("Different hypervisor type" - "(%(orig_hypervisor)s->" - "%(dest_hypervisor)s)')" % locals())) + raise exception.InvalidHypervisorType() # Checkng hypervisor version. orig_hypervisor = oservice_ref['hypervisor_version'] dest_hypervisor = dservice_ref['hypervisor_version'] if orig_hypervisor > dest_hypervisor: - raise exception.Invalid(_("Older hypervisor version" - "(%(orig_hypervisor)s->" - "%(dest_hypervisor)s)") % locals()) + raise exception.DestinationHypervisorTooOld() # Checking cpuinfo. try: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 94410003e..78190cb6d 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -421,7 +421,7 @@ class LibvirtConnection(driver.ComputeDriver): name, mount_device) else: - raise exception.Invalid(_("Invalid device path %s") % device_path) + raise exception.InvalidDevicePath(path=device_path) virt_dom.attachDevice(xml) @@ -1302,9 +1302,9 @@ class LibvirtConnection(driver.ComputeDriver): xml = libxml2.parseDoc(xml) nodes = xml.xpathEval('//host/cpu') if len(nodes) != 1: - raise exception.Invalid(_("Invalid xml. '' must be 1," - "but %d\n") % len(nodes) - + xml.serialize()) + reason = _("'' must be 1, but %d\n") % len(nodes) + reason += xml.serialize() + raise exception.InvalidCPUInfo(reason=reason) cpu_info = dict() @@ -1333,9 +1333,8 @@ class LibvirtConnection(driver.ComputeDriver): tkeys = topology.keys() if set(tkeys) != set(keys): ks = ', '.join(keys) - raise exception.Invalid(_("Invalid xml: topology" - "(%(topology)s) must have " - "%(ks)s") % locals()) + reason = _("topology (%(topology)s) must have %(ks)s") + raise exception.InvalidCPUInfo(reason=reason % locals()) feature_nodes = xml.xpathEval('//host/cpu/feature') features = list() @@ -1390,9 +1389,7 @@ class LibvirtConnection(driver.ComputeDriver): try: service_ref = db.service_get_all_compute_by_host(ctxt, host)[0] except exception.NotFound: - raise exception.Invalid(_("Cannot update compute manager " - "specific info, because no service " - "record was found.")) + raise exception.ComputeServiceUnavailable() # Updating host information dic = {'vcpus': self.get_vcpu_total(), @@ -1445,7 +1442,7 @@ class LibvirtConnection(driver.ComputeDriver): raise if ret <= 0: - raise exception.Invalid(m % locals()) + raise exception.InvalidCPUInfo(reason=m % locals()) return diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index cf6c88bbd..f2f68ec22 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -501,8 +501,8 @@ class VMWareVMOps(object): # Raise an exception if the VM is not powered On. if pwr_state not in ["poweredOn"]: - raise exception.Invalid(_("instance - %s not poweredOn. So can't " - "be rebooted.") % instance.name) + reason = _("instance is not powered on") + raise exception.InstanceRebootFailure(reason=reason) # If latest vmware tools are installed in the VM, and that the tools # are running, then only do a guest reboot. Otherwise do a hard reset. @@ -620,8 +620,9 @@ class VMWareVMOps(object): LOG.debug(_("Suspended the VM %s ") % instance.name) # Raise Exception if VM is poweredOff elif pwr_state == "poweredOff": - raise exception.Invalid(_("instance - %s is poweredOff and hence " - " can't be suspended.") % instance.name) + reason = _("instance is poweredOff and can not be suspended.") + raise exception.InstanceSuspendFailure(reason=reason) + LOG.debug(_("VM %s was already in suspended state. So returning " "without doing anything") % instance.name) @@ -643,8 +644,8 @@ class VMWareVMOps(object): self._wait_with_callback(instance.id, suspend_task, callback) LOG.debug(_("Resumed the VM %s ") % instance.name) else: - raise exception.Invalid(_("instance - %s not in Suspended state " - "and hence can't be Resumed.") % instance.name) + reason = _("instance is not in a suspended state") + raise exception.InstanceResumeFailure(reason=reason) def get_info(self, instance_name): """Return data about the VM instance.""" -- cgit From ad138a5a50868531f34ba358600f1270588ce80b Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 15 Apr 2011 14:24:17 -0400 Subject: correcting tests; pep8 --- nova/api/openstack/servers.py | 3 +- nova/exception.py | 20 ++++++++----- nova/scheduler/driver.py | 3 +- nova/tests/test_scheduler.py | 69 +++++++++++++------------------------------ nova/tests/test_virt.py | 2 +- nova/virt/libvirt_conn.py | 2 +- 6 files changed, 39 insertions(+), 60 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 2332b1af7..1cb42355a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -561,7 +561,8 @@ class Controller(common.OpenstackController): """ image_id = image_meta['id'] if image_meta['status'] != 'active': - raise exception.InstanceNotRunning(image_id=image_id) + raise exception.ImageUnacceptable(image_id=image_id, + reason=_("status is not active")) if image_meta.get('container_format') != 'ami': return None, None diff --git a/nova/exception.py b/nova/exception.py index cbfbb2920..29d6b13e5 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -141,23 +141,23 @@ class Invalid(NovaException): class InstanceNotRunning(Invalid): - message = "Instance is not 'running'." + message = "Instance %(instance_id)s is not running." class InstanceNotSuspended(Invalid): - message = "Instance is not 'suspended'." + message = "Instance %(instance_id)s is not suspended." class InstanceSuspendFailure(Invalid): - message = "Failed to suspend instance: %(reason)s." + message = "Failed to suspend instance: %(reason)s" class InstanceResumeFailure(Invalid): - message = "Failed to resume server: %(reson)s." + message = "Failed to resume server: %(reason)s." class InstanceRebootFailure(Invalid): - message = "Failed to reboot instance: %(reason)s." + message = "Failed to reboot instance: %(reason)s" class ServiceUnavailable(Invalid): @@ -169,7 +169,7 @@ class VolumeServiceUnavailable(ServiceUnavailable): class ComputeServiceUnavailable(ServiceUnavailable): - message = "Compute service on %(host)s is unavailable at this time." + message = "Compute service is unavailable at this time." class UnableToMigrateToSelf(Invalid): @@ -182,7 +182,7 @@ class SourceHostUnavailable(Invalid): class InvalidHypervisorType(Invalid): - message = "The supplied hypervisor type of %(type)s is invalid." + message = "The supplied hypervisor type of is invalid." class DestinationHypervisorTooOld(Invalid): @@ -195,7 +195,7 @@ class InvalidDevicePath(Invalid): class InvalidCPUInfo(Invalid): - message = "Unacceptable CPU info: %(reason)s." + message = "Unacceptable CPU info: %(reason)s" class InvalidVLANTag(Invalid): @@ -209,3 +209,7 @@ class InvalidVLANPortGroup(Invalid): "not associated with the desired physical adapter. " \ "Expected vSwitch is %(expected)s, but the one associated " \ "is %(actual)s." + + +class ImageUnacceptable(Invalid): + message = "Image %(image_id)s is unacceptable: %(reason)s" diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index 02e65357c..87b10e940 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -168,7 +168,8 @@ class Scheduler(object): src = instance_ref['host'] if dest == src: ec2_id = instance_ref['hostname'] - raise exception.UnableToMigrateToSelf(instance_id=ec2_id, host=dest) + raise exception.UnableToMigrateToSelf(instance_id=ec2_id, + host=dest) # Checking dst host still has enough capacities. self.assert_compute_node_has_enough_resources(context, diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index ae56a1a16..4a2b15f56 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -698,14 +698,10 @@ class SimpleDriverTestCase(test.TestCase): 'topic': 'volume', 'report_count': 0} s_ref = db.service_create(self.context, dic) - try: - self.scheduler.driver.schedule_live_migration(self.context, - instance_id, - i_ref['host']) - except exception.Invalid, e: - c = (e.message.find('volume node is not alive') >= 0) + self.assertRaises(exception.VolumeServiceUnavailable, + self.scheduler.driver.schedule_live_migration, + self.context, instance_id, i_ref['host']) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) db.volume_destroy(self.context, v_ref['id']) @@ -718,13 +714,10 @@ class SimpleDriverTestCase(test.TestCase): s_ref = self._create_compute_service(created_at=t, updated_at=t, host=i_ref['host']) - try: - self.scheduler.driver._live_migration_src_check(self.context, - i_ref) - except exception.Invalid, e: - c = (e.message.find('is not alive') >= 0) + self.assertRaises(exception.ComputeServiceUnavailable, + self.scheduler.driver._live_migration_src_check, + self.context, i_ref) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) @@ -749,14 +742,10 @@ class SimpleDriverTestCase(test.TestCase): s_ref = self._create_compute_service(created_at=t, updated_at=t, host=i_ref['host']) - try: - self.scheduler.driver._live_migration_dest_check(self.context, - i_ref, - i_ref['host']) - except exception.Invalid, e: - c = (e.message.find('is not alive') >= 0) + self.assertRaises(exception.ComputeServiceUnavailable, + self.scheduler.driver._live_migration_dest_check, + self.context, i_ref, i_ref['host']) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) @@ -766,14 +755,10 @@ class SimpleDriverTestCase(test.TestCase): i_ref = db.instance_get(self.context, instance_id) s_ref = self._create_compute_service(host=i_ref['host']) - try: - self.scheduler.driver._live_migration_dest_check(self.context, - i_ref, - i_ref['host']) - except exception.Invalid, e: - c = (e.message.find('choose other host') >= 0) + self.assertRaises(exception.UnableToMigrateToSelf, + self.scheduler.driver._live_migration_dest_check, + self.context, i_ref, i_ref['host']) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) @@ -837,14 +822,10 @@ class SimpleDriverTestCase(test.TestCase): "args": {'filename': fpath}}) self.mox.ReplayAll() - try: - self.scheduler.driver._live_migration_common_check(self.context, - i_ref, - dest) - except exception.Invalid, e: - c = (e.message.find('does not exist') >= 0) + self.assertRaises(exception.SourceHostUnavailable, + self.scheduler.driver._live_migration_common_check, + self.context, i_ref, dest) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) @@ -865,14 +846,10 @@ class SimpleDriverTestCase(test.TestCase): driver.mounted_on_same_shared_storage(mox.IgnoreArg(), i_ref, dest) self.mox.ReplayAll() - try: - self.scheduler.driver._live_migration_common_check(self.context, - i_ref, - dest) - except exception.Invalid, e: - c = (e.message.find(_('Different hypervisor type')) >= 0) + self.assertRaises(exception.InvalidHypervisorType, + self.scheduler.driver._live_migration_common_check, + self.context, i_ref, dest) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) db.service_destroy(self.context, s_ref2['id']) @@ -895,14 +872,10 @@ class SimpleDriverTestCase(test.TestCase): driver.mounted_on_same_shared_storage(mox.IgnoreArg(), i_ref, dest) self.mox.ReplayAll() - try: - self.scheduler.driver._live_migration_common_check(self.context, - i_ref, - dest) - except exception.Invalid, e: - c = (e.message.find(_('Older hypervisor version')) >= 0) + self.assertRaises(exception.DestinationHypervisorTooOld, + self.scheduler.driver._live_migration_common_check, + self.context, i_ref, dest) - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) db.service_destroy(self.context, s_ref2['id']) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index aeaea91c7..fe0ea5d6e 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -451,7 +451,7 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_conn.LibvirtConnection(False) - self.assertRaises(exception.Invalid, + self.assertRaises(exception.ComputeServiceUnavailable, conn.update_available_resource, self.context, 'dummy') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 78190cb6d..2b78dceb2 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1389,7 +1389,7 @@ class LibvirtConnection(driver.ComputeDriver): try: service_ref = db.service_get_all_compute_by_host(ctxt, host)[0] except exception.NotFound: - raise exception.ComputeServiceUnavailable() + raise exception.ComputeServiceUnavailable(host=host) # Updating host information dic = {'vcpus': self.get_vcpu_total(), -- cgit From 876216838843044adba401c1f44f18dd97b0e01d Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 15 Apr 2011 14:33:24 -0400 Subject: adding documentation & error handling --- nova/exception.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index 29d6b13e5..7d1b44c35 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -126,16 +126,28 @@ def wrap_exception(f): class NovaException(Exception): - message = "" + """Base Nova Exception + + To correctly use this class, inherit from it and define + a 'message' property. That message will get printf'd + with the keyword arguments provided to the constructor. + + """ + message = "An unknown exception occurred." def __init__(self, **kwargs): - self._error_string = _(self.message) % kwargs + try: + self._error_string = _(self.message) % kwargs + + except Exception: + # at least get the core message out if something happened + self._error_string = _(self.message) def __str__(self): return self._error_string -#TODO: EOL this exception! +#TODO(bcwaldon): EOL this exception! class Invalid(NovaException): pass -- cgit From aacb64391a8d9802365746308f9ece8e73dc9dae Mon Sep 17 00:00:00 2001 From: Cerberus Date: Fri, 15 Apr 2011 17:32:15 -0500 Subject: Rename the id --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 39d7af9c1..839b84790 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -550,7 +550,7 @@ class ComputeManager(manager.SchedulerDependentManager): instance_type = self.db.instance_type_get_by_flavor_id(context, migration_ref['new_flavor_id']) self.db.instance_update(context, instance_id, - dict(instance_type=instance_type['name'], + dict(instance_type_id=instance_type['id'], memory_mb=instance_type['memory_mb'], vcpus=instance_type['vcpus'], local_gb=instance_type['local_gb'])) -- cgit From 9a0d079cfe28d6d8d4e909f68541efda5ad3a3c5 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 18 Apr 2011 21:06:29 +0400 Subject: not performing floating ip operation with auto allocated ips --- nova/compute/manager.py | 3 ++- nova/db/sqlalchemy/api.py | 2 ++ nova/db/sqlalchemy/models.py | 1 + nova/network/api.py | 6 ++++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 94fee36a5..829d59170 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -308,7 +308,8 @@ class ComputeManager(manager.SchedulerDependentManager): network_topic, {"method": "disassociate_floating_ip", "args": {"floating_address": address}}) - if FLAGS.auto_assign_floating_ip: + if FLAGS.auto_assign_floating_ip \ + and floating_ip.get('auto_assigned'): LOG.debug(_("Deallocating floating ip %s"), floating_ip['address'], context=context) rpc.cast(context, diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 28126a517..6b2caf46c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -461,6 +461,7 @@ def floating_ip_count_by_project(context, project_id): session = get_session() return session.query(models.FloatingIp).\ filter_by(project_id=project_id).\ + filter_by(auto_assigned=False).\ filter_by(deleted=False).\ count() @@ -560,6 +561,7 @@ def floating_ip_get_all_by_project(context, project_id): return session.query(models.FloatingIp).\ options(joinedload_all('fixed_ip.instance')).\ filter_by(project_id=project_id).\ + filter_by(auto_assigned=False).\ filter_by(deleted=False).\ all() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f79d0f16c..36a084a1d 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -592,6 +592,7 @@ class FloatingIp(BASE, NovaBase): 'FloatingIp.deleted == False)') project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) + auto_assigned = Column(Boolean, default=False, nullable=False) class ConsolePool(BASE, NovaBase): diff --git a/nova/network/api.py b/nova/network/api.py index c56e3062b..c5f76a14e 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -53,6 +53,8 @@ class API(base.Base): def release_floating_ip(self, context, address): floating_ip = self.db.floating_ip_get_by_address(context, address) + if floating_ip.get('auto_assigned'): + return # NOTE(vish): We don't know which network host should get the ip # when we deallocate, so just send it to any one. This # will probably need to move into a network supervisor @@ -66,6 +68,8 @@ class API(base.Base): if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) + if floating_ip.get('auto_assigned'): + return # Check if the floating ip address is allocated if floating_ip['project_id'] is None: raise exception.ApiError(_("Address (%s) is not allocated") % @@ -92,6 +96,8 @@ class API(base.Base): def disassociate_floating_ip(self, context, address): floating_ip = self.db.floating_ip_get_by_address(context, address) + if floating_ip.get('auto_assigned'): + return if not floating_ip.get('fixed_ip'): raise exception.ApiError('Address is not associated.') # NOTE(vish): Get the topic from the host name of the network of -- cgit From 841d25c1c9ab840ed39261a3bb234b981d9c337a Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 18 Apr 2011 22:02:12 +0400 Subject: pep8 fixed --- .../migrate_repo/versions/015_add_auto_assign_to_floating_ips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py index 6829b5d6e..f3767b29f 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -33,4 +33,4 @@ def upgrade(migrate_engine): floating_ips = Table('floating_ips', meta, autoload=True, autoload_with=migrate_engine) - floating_ips.create_column(c_auto_assigned) \ No newline at end of file + floating_ips.create_column(c_auto_assigned) -- cgit From 2ef03c6a0a8c5705249c3b5be755e0a13ca39332 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 18 Apr 2011 22:02:54 -0400 Subject: Implement get_host_ip_addr in the libvirt compute driver. --- nova/tests/test_virt.py | 12 ++++++++++++ nova/virt/libvirt_conn.py | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index aeaea91c7..d97801484 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -18,6 +18,7 @@ import eventlet import mox import os import re +import socket import sys from xml.etree.ElementTree import fromstring as xml_to_tree @@ -549,6 +550,17 @@ class LibvirtConnTestCase(test.TestCase): db.volume_destroy(self.context, volume_ref['id']) db.instance_destroy(self.context, instance_ref['id']) + def test_get_host_ip_addr(self): + + def getHostname(): + return socket.gethostname() + + self.create_fake_libvirt_mock(getHostname=getHostname) + self.mox.ReplayAll() + conn = libvirt_conn.LibvirtConnection(False) + ip = conn.get_host_ip_addr() + self.assertTrue(ip is not None) + def tearDown(self): self.manager.delete_project(self.project) self.manager.delete_user(self.user) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d212be3c9..511bfde36 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -40,6 +40,7 @@ import multiprocessing import os import random import shutil +import socket import subprocess import sys import tempfile @@ -732,6 +733,11 @@ class LibvirtConnection(driver.ComputeDriver): subprocess.Popen(cmd, shell=True) return {'token': token, 'host': host, 'port': port} + def get_host_ip_addr(self): + hostname = self._conn.getHostname() + ip = socket.gethostbyname(hostname) + return ip + @exception.wrap_exception def get_vnc_console(self, instance): def get_vnc_port_for_instance(instance_name): -- cgit From 50bd39e0413c2231ebdf9f4c9fb7e58d27624250 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Tue, 19 Apr 2011 16:57:17 +0400 Subject: refractoring --- nova/compute/manager.py | 50 ++++++++++--------------------------------------- nova/network/api.py | 15 +++++++++------ 2 files changed, 19 insertions(+), 46 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index db97ae690..313b9e0e1 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -54,6 +54,7 @@ from nova import rpc from nova import utils from nova.compute import power_state from nova.virt import driver +from nova.network import api as network_api FLAGS = flags.FLAGS flags.DEFINE_string('instances_path', '$state_path/instances', @@ -134,6 +135,7 @@ class ComputeManager(manager.SchedulerDependentManager): self.network_manager = utils.import_object(FLAGS.network_manager) self.volume_manager = utils.import_object(FLAGS.volume_manager) + self.network_api = network_api.API() super(ComputeManager, self).__init__(service_name="compute", *args, **kwargs) @@ -248,39 +250,15 @@ class ComputeManager(manager.SchedulerDependentManager): if not FLAGS.stub_network: if FLAGS.auto_assign_floating_ip: - public_ip = rpc.call(context, - FLAGS.network_topic, - {"method": "allocate_floating_ip", - "args": {"project_id": context.project_id}}) + public_ip = self.network_api.allocate_floating_ip(context) fixed_ip = self.db.fixed_ip_get_by_address(context, address) floating_ip = self.db.floating_ip_get_by_address(context, public_ip) - # Check if the floating ip address is allocated - if floating_ip['project_id'] is None: - raise exception.Error(_("Address (%s) is not allocated") % - floating_ip['address']) - # Check if the floating ip address is allocated - # to the same project - if floating_ip['project_id'] != context.project_id: - LOG.warn(_("Address (%(address)s) is not allocated to your" - " project (%(project)s)"), - {'address': floating_ip['address'], - 'project': context.project_id}) - raise exception.Error(_("Address (%(address)s) is not " - "allocated to your project" - "(%(project)s)") % - {'address': floating_ip['address'], - 'project': context.project_id}) - - host = fixed_ip['network']['host'] - rpc.cast(context, - self.db.queue_get_for(context, - FLAGS.network_topic, host), - {"method": "associate_floating_ip", - "args": {"floating_address": floating_ip['address'], - "fixed_address": fixed_ip['address']}}) + self.network_api.associate_floating_ip(context, floating_ip, + fixed_ip, + affect_auto_assigned=True) self._update_state(context, instance_id) @exception.wrap_exception @@ -301,22 +279,14 @@ class ComputeManager(manager.SchedulerDependentManager): # NOTE(vish): Right now we don't really care if the ip is # disassociated. We may need to worry about # checking this later. - network_topic = self.db.queue_get_for(context, - FLAGS.network_topic, - floating_ip['host']) - rpc.cast(context, - network_topic, - {"method": "disassociate_floating_ip", - "args": {"floating_address": address}}) + self.network_api.disassociate_floating_ip(context, address, + affect_auto_assigned=True) if FLAGS.auto_assign_floating_ip \ and floating_ip.get('auto_assigned'): LOG.debug(_("Deallocating floating ip %s"), floating_ip['address'], context=context) - rpc.cast(context, - FLAGS.network_topic, - {"method": "deallocate_floating_ip", - "args": {"floating_address": - floating_ip['address']}}) + self.network_api.release_floating_ip(context, address, + affect_auto_assigned=True) address = fixed_ip['address'] if address: diff --git a/nova/network/api.py b/nova/network/api.py index c5f76a14e..61db646ae 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -51,9 +51,10 @@ class API(base.Base): {"method": "allocate_floating_ip", "args": {"project_id": context.project_id}}) - def release_floating_ip(self, context, address): + def release_floating_ip(self, context, address, + affect_auto_assigned = False): floating_ip = self.db.floating_ip_get_by_address(context, address) - if floating_ip.get('auto_assigned'): + if not affect_auto_assigned and floating_ip.get('auto_assigned'): return # NOTE(vish): We don't know which network host should get the ip # when we deallocate, so just send it to any one. This @@ -64,11 +65,12 @@ class API(base.Base): {"method": "deallocate_floating_ip", "args": {"floating_address": floating_ip['address']}}) - def associate_floating_ip(self, context, floating_ip, fixed_ip): + def associate_floating_ip(self, context, floating_ip, fixed_ip, + affect_auto_assigned = False): if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) - if floating_ip.get('auto_assigned'): + if not affect_auto_assigned and floating_ip.get('auto_assigned'): return # Check if the floating ip address is allocated if floating_ip['project_id'] is None: @@ -94,9 +96,10 @@ class API(base.Base): "args": {"floating_address": floating_ip['address'], "fixed_address": fixed_ip['address']}}) - def disassociate_floating_ip(self, context, address): + def disassociate_floating_ip(self, context, address, + affect_auto_assigned = False): floating_ip = self.db.floating_ip_get_by_address(context, address) - if floating_ip.get('auto_assigned'): + if not affect_auto_assigned and floating_ip.get('auto_assigned'): return if not floating_ip.get('fixed_ip'): raise exception.ApiError('Address is not associated.') -- cgit From 9c6cbd448088f5096bba9866d8057300256c6d34 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Apr 2011 09:48:07 -0400 Subject: moving dynamic i18n to static --- nova/exception.py | 52 ++++++++++++++++++++--------------------- nova/tests/test_localization.py | 1 + nova/virt/vmwareapi/vmops.py | 2 +- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index 7d1b44c35..50d7984dc 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -137,11 +137,11 @@ class NovaException(Exception): def __init__(self, **kwargs): try: - self._error_string = _(self.message) % kwargs + self._error_string = self.message % kwargs except Exception: # at least get the core message out if something happened - self._error_string = _(self.message) + self._error_string = self.message def __str__(self): return self._error_string @@ -153,75 +153,75 @@ class Invalid(NovaException): class InstanceNotRunning(Invalid): - message = "Instance %(instance_id)s is not running." + message = _("Instance %(instance_id)s is not running.") class InstanceNotSuspended(Invalid): - message = "Instance %(instance_id)s is not suspended." + message = _("Instance %(instance_id)s is not suspended.") class InstanceSuspendFailure(Invalid): - message = "Failed to suspend instance: %(reason)s" + message = _("Failed to suspend instance") + ": %(reason)s" class InstanceResumeFailure(Invalid): - message = "Failed to resume server: %(reason)s." + message = _("Failed to resume server") + ": %(reason)s." class InstanceRebootFailure(Invalid): - message = "Failed to reboot instance: %(reason)s" + message = _("Failed to reboot instance") + ": %(reason)s" class ServiceUnavailable(Invalid): - message = "Service is unavailable at this time." + message = _("Service is unavailable at this time.") class VolumeServiceUnavailable(ServiceUnavailable): - message = "Volume service is unavailable at this time." + message = _("Volume service is unavailable at this time.") class ComputeServiceUnavailable(ServiceUnavailable): - message = "Compute service is unavailable at this time." + message = _("Compute service is unavailable at this time.") class UnableToMigrateToSelf(Invalid): - message = "Unable to migrate instance (%(instance_id)s) " \ - "to current host (%(host)s." + message = _("Unable to migrate instance (%(instance_id)s) " + "to current host (%(host)s).") class SourceHostUnavailable(Invalid): - message = "Original compute host is unavailable at this time." + message = _("Original compute host is unavailable at this time.") class InvalidHypervisorType(Invalid): - message = "The supplied hypervisor type of is invalid." + message = _("The supplied hypervisor type of is invalid.") class DestinationHypervisorTooOld(Invalid): - message = "The instance requires a newer hypervisor version than " \ - "has been provided." + message = _("The instance requires a newer hypervisor version than " + "has been provided.") class InvalidDevicePath(Invalid): - message = "The supplied device path (%(path)s) is invalid." + message = _("The supplied device path (%(path)s) is invalid.") class InvalidCPUInfo(Invalid): - message = "Unacceptable CPU info: %(reason)s" + message = _("Unacceptable CPU info") + ": %(reason)s" class InvalidVLANTag(Invalid): - message = "VLAN tag is not appropriate for the port group " \ - "%(bridge)s. Expected VLAN tag is %(tag)s, " \ - "but the one associated with the port group is %(pgroup)s." + message = _("VLAN tag is not appropriate for the port group " + "%(bridge)s. Expected VLAN tag is %(tag)s, " + "but the one associated with the port group is %(pgroup)s.") class InvalidVLANPortGroup(Invalid): - message = "vSwitch which contains the port group %(bridge)s is " \ - "not associated with the desired physical adapter. " \ - "Expected vSwitch is %(expected)s, but the one associated " \ - "is %(actual)s." + message = _("vSwitch which contains the port group %(bridge)s is " + "not associated with the desired physical adapter. " + "Expected vSwitch is %(expected)s, but the one associated " + "is %(actual)s.") class ImageUnacceptable(Invalid): - message = "Image %(image_id)s is unacceptable: %(reason)s" + message = _("Image %(image_id)s is unacceptable") + ": %(reason)s" diff --git a/nova/tests/test_localization.py b/nova/tests/test_localization.py index a25809a79..37cc22b1d 100644 --- a/nova/tests/test_localization.py +++ b/nova/tests/test_localization.py @@ -59,6 +59,7 @@ class LocalizationTestCase(test.TestCase): pos = 0 while parenCount > 0: char = txt[pos] + print char if char == "(": parenCount += 1 elif char == ")": diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index f2f68ec22..b700c438f 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -620,7 +620,7 @@ class VMWareVMOps(object): LOG.debug(_("Suspended the VM %s ") % instance.name) # Raise Exception if VM is poweredOff elif pwr_state == "poweredOff": - reason = _("instance is poweredOff and can not be suspended.") + reason = _("instance is powered off and can not be suspended.") raise exception.InstanceSuspendFailure(reason=reason) LOG.debug(_("VM %s was already in suspended state. So returning " -- cgit From 4466f775a70162f8a140afbe19a56d7290b014d3 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Apr 2011 09:48:44 -0400 Subject: removing rogue print --- nova/tests/test_localization.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/test_localization.py b/nova/tests/test_localization.py index 37cc22b1d..a25809a79 100644 --- a/nova/tests/test_localization.py +++ b/nova/tests/test_localization.py @@ -59,7 +59,6 @@ class LocalizationTestCase(test.TestCase): pos = 0 while parenCount > 0: char = txt[pos] - print char if char == "(": parenCount += 1 elif char == ")": -- cgit From 7b5cf70cc9339028b1be9569e5754b997c7dae83 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Apr 2011 09:50:53 -0400 Subject: multi-line string spacing --- nova/exception.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index 50d7984dc..80c8dedfe 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -186,7 +186,7 @@ class ComputeServiceUnavailable(ServiceUnavailable): class UnableToMigrateToSelf(Invalid): message = _("Unable to migrate instance (%(instance_id)s) " - "to current host (%(host)s).") + "to current host (%(host)s).") class SourceHostUnavailable(Invalid): @@ -199,7 +199,7 @@ class InvalidHypervisorType(Invalid): class DestinationHypervisorTooOld(Invalid): message = _("The instance requires a newer hypervisor version than " - "has been provided.") + "has been provided.") class InvalidDevicePath(Invalid): @@ -212,15 +212,15 @@ class InvalidCPUInfo(Invalid): class InvalidVLANTag(Invalid): message = _("VLAN tag is not appropriate for the port group " - "%(bridge)s. Expected VLAN tag is %(tag)s, " - "but the one associated with the port group is %(pgroup)s.") + "%(bridge)s. Expected VLAN tag is %(tag)s, " + "but the one associated with the port group is %(pgroup)s.") class InvalidVLANPortGroup(Invalid): message = _("vSwitch which contains the port group %(bridge)s is " - "not associated with the desired physical adapter. " - "Expected vSwitch is %(expected)s, but the one associated " - "is %(actual)s.") + "not associated with the desired physical adapter. " + "Expected vSwitch is %(expected)s, but the one associated " + "is %(actual)s.") class ImageUnacceptable(Invalid): -- cgit From f5ef0e4bf39e01b46db241f5766db60059d52df3 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Apr 2011 10:55:47 -0400 Subject: one last i18n string --- nova/exception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/exception.py b/nova/exception.py index 80c8dedfe..6948a93c1 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -133,7 +133,7 @@ class NovaException(Exception): with the keyword arguments provided to the constructor. """ - message = "An unknown exception occurred." + message = _("An unknown exception occurred.") def __init__(self, **kwargs): try: -- cgit From da99e8e6b143cd2051c23f14d4d46602f16f7ba3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 09:16:25 -0700 Subject: add instructions for setting up interfaces --- doc/source/devref/cloudpipe.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/source/devref/cloudpipe.rst b/doc/source/devref/cloudpipe.rst index 95570aa1b..a1f6c6450 100644 --- a/doc/source/devref/cloudpipe.rst +++ b/doc/source/devref/cloudpipe.rst @@ -68,6 +68,12 @@ Making a cloudpipe image is relatively easy. :language: bash :linenos: +# setup network interfaces. + +.. literalinclude:: interfaces + :language: bash + :linenos: + # register the image and set the image id in your flagfile:: --vpn_image_id=ami-xxxxxxxx -- cgit From 8b21dd6634cc32c43d0bebf3dede40b4b28c0a78 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 09:16:45 -0700 Subject: add include file for doc interfaces --- doc/source/devref/interfaces | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 doc/source/devref/interfaces diff --git a/doc/source/devref/interfaces b/doc/source/devref/interfaces new file mode 100644 index 000000000..2aae39558 --- /dev/null +++ b/doc/source/devref/interfaces @@ -0,0 +1,18 @@ +# The loopback network interface +auto lo +iface lo inet loopback + +# The primary network interface +auto br0 +iface br0 inet dhcp + bridge_ports eth0 + bridge_fd 9 ## from the libvirt docs (forward delay time) + bridge_hello 2 ## from the libvirt docs (hello time) + bridge_maxage 12 ## from the libvirt docs (maximum message age) + bridge_stp off ## from the libvirt docs (spanning tree protocol) + +iface eth0 inet manual + up ifconfig $IFACE 0.0.0.0 up + up ip link set $IFACE promisc on + down ip link set $IFACE promisc off + down ifconfig $IFACE down -- cgit From 745351d1e2a98a98de0a5f955385a92c01110684 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 09:19:52 -0700 Subject: Fixes cloudpipe to get the proper ip address. * Changes FLAGS.vpn_image_id to integer * Converts to str when comparing because instance['image_id'] is a str * Removes unused method from db * Converts integer_id to ami when launching * Adds docs for setting up interface in cloudpipe image --- nova/api/ec2/admin.py | 2 +- nova/api/ec2/cloud.py | 4 ++-- nova/cloudpipe/pipelib.py | 2 ++ nova/compute/manager.py | 2 +- nova/db/api.py | 5 ----- nova/db/sqlalchemy/api.py | 9 +-------- nova/flags.py | 2 +- nova/virt/libvirt_conn.py | 2 +- 8 files changed, 9 insertions(+), 19 deletions(-) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 6a5609d4a..ea94d9c1f 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -266,7 +266,7 @@ class AdminController(object): def _vpn_for(self, context, project_id): """Get the VPN instance for a project ID.""" for instance in db.instance_get_all_by_project(context, project_id): - if (instance['image_id'] == FLAGS.vpn_image_id + if (instance['image_id'] == str(FLAGS.vpn_image_id) and not instance['state_description'] in ['shutting_down', 'shutdown']): return instance diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 10b1d0ac5..d9c1af072 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -703,7 +703,7 @@ class CloudController(object): instances = self.compute_api.get_all(context, **kwargs) for instance in instances: if not context.is_admin: - if instance['image_id'] == FLAGS.vpn_image_id: + if instance['image_id'] == str(FLAGS.vpn_image_id): continue i = {} instance_id = instance['id'] @@ -898,7 +898,7 @@ class CloudController(object): return image_type @staticmethod - def _image_ec2_id(image_id, image_type='ami'): + def image_ec2_id(image_id, image_type='ami'): """Returns image ec2_id using id and three letter type.""" template = image_type + '-%08x' return ec2utils.id_to_ec2_id(int(image_id), template=template) diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index dc6f55af2..2c8912422 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -37,6 +37,7 @@ from nova import utils from nova.auth import manager # TODO(eday): Eventually changes these to something not ec2-specific from nova.api.ec2 import cloud +from nova.api FLAGS = flags.FLAGS @@ -101,6 +102,7 @@ class CloudPipe(object): key_name = self.setup_key_pair(ctxt) group_name = self.setup_security_group(ctxt) + ec2_id = self.controller.image_ec2_id(FLAGS.vpn_image_id) reservation = self.controller.run_instances(ctxt, user_data=self.get_encoded_zip(project_id), max_count=1, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 39d7af9c1..05b168e13 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -209,7 +209,7 @@ class ComputeManager(manager.SchedulerDependentManager): power_state.NOSTATE, 'networking') - is_vpn = instance_ref['image_id'] == FLAGS.vpn_image_id + is_vpn = instance_ref['image_id'] == str(FLAGS.vpn_image_id) # NOTE(vish): This could be a cast because we don't do anything # with the address currently, but I'm leaving it as # a call to ensure that network setup completes. We diff --git a/nova/db/api.py b/nova/db/api.py index 63901e94d..030d2f434 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -455,11 +455,6 @@ def instance_get_project_vpn(context, project_id): return IMPL.instance_get_project_vpn(context, project_id) -def instance_is_vpn(context, instance_id): - """True if instance is a vpn.""" - return IMPL.instance_is_vpn(context, instance_id) - - def instance_set_state(context, instance_id, state, description=None): """Set the state of an instance.""" return IMPL.instance_set_state(context, instance_id, state, description) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e675022e9..e9450e392 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -940,7 +940,7 @@ def instance_get_project_vpn(context, project_id): options(joinedload('security_groups')).\ options(joinedload('instance_type')).\ filter_by(project_id=project_id).\ - filter_by(image_id=FLAGS.vpn_image_id).\ + filter_by(image_id=str(FLAGS.vpn_image_id)).\ filter_by(deleted=can_read_deleted(context)).\ first() @@ -979,13 +979,6 @@ def instance_get_floating_address(context, instance_id): return instance_ref.fixed_ip.floating_ips[0]['address'] -@require_admin_context -def instance_is_vpn(context, instance_id): - # TODO(vish): Move this into image code somewhere - instance_ref = instance_get(context, instance_id) - return instance_ref['image_id'] == FLAGS.vpn_image_id - - @require_admin_context def instance_set_state(context, instance_id, state, description=None): # TODO(devcamcar): Move this out of models and into driver diff --git a/nova/flags.py b/nova/flags.py index f011ab383..760bcc37d 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -316,7 +316,7 @@ DEFINE_string('null_kernel', 'nokernel', 'kernel image that indicates not to use a kernel,' ' but to use a raw disk image instead') -DEFINE_string('vpn_image_id', 'ami-cloudpipe', 'AMI for cloudpipe vpn server') +DEFINE_integer('vpn_image_id', 0, 'integer id for cloudpipe vpn server') DEFINE_string('vpn_key_suffix', '-vpn', 'Suffix to add to project name for vpn key and secgroups') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 5da091920..9c8d64446 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1837,7 +1837,7 @@ class NWFilterFirewall(FirewallDriver): """ if not network_info: network_info = _get_network_info(instance) - if instance['image_id'] == FLAGS.vpn_image_id: + if instance['image_id'] == str(FLAGS.vpn_image_id): base_filter = 'nova-vpn' else: base_filter = 'nova-base' -- cgit From 7554ab7da290565ee457b2d42730a2bff2fd7861 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 09:31:18 -0700 Subject: remove typo --- nova/cloudpipe/pipelib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 2c8912422..f4cb53da0 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -37,7 +37,6 @@ from nova import utils from nova.auth import manager # TODO(eday): Eventually changes these to something not ec2-specific from nova.api.ec2 import cloud -from nova.api FLAGS = flags.FLAGS -- cgit From 2d649fa8928e9682064613f2c984f53f492efbec Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 09:32:33 -0700 Subject: actually use the ec2_id --- nova/cloudpipe/pipelib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index f4cb53da0..7844d31e1 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -107,7 +107,7 @@ class CloudPipe(object): max_count=1, min_count=1, instance_type='m1.tiny', - image_id=FLAGS.vpn_image_id, + image_id=ec2_id, key_name=key_name, security_group=[group_name]) -- cgit From 3e3f8e1f09d0615e66cc1be0b656d0d8e1d69671 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 19 Apr 2011 12:36:07 -0400 Subject: Abstracted lookupByName calls to _lookup_by_name for centralized error handling. --- nova/virt/libvirt_conn.py | 53 +++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 2582b9730..c1f62c391 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -309,19 +309,10 @@ class LibvirtConnection(driver.ComputeDriver): def destroy(self, instance, cleanup=True): instance_name = instance['name'] - # TODO(justinsb): Refactor all lookupByName calls for error-handling try: - virt_dom = self._conn.lookupByName(instance_name) - except libvirt.libvirtError as e: - errcode = e.get_error_code() - if errcode == libvirt.VIR_ERR_NO_DOMAIN: - virt_dom = None - else: - LOG.warning(_("Error from libvirt during lookup of " - "%(instance_name)s. Code=%(errcode)s " - "Error=%(e)s") % - locals()) - raise + virt_dom = self._lookup_by_name(instance_name) + except exception.NotFound: + virt_dom = None # If the instance is already terminated, we're still happy # Otherwise, destroy it @@ -392,7 +383,7 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def attach_volume(self, instance_name, device_path, mountpoint): - virt_dom = self._conn.lookupByName(instance_name) + virt_dom = self._lookup_by_name(instance_name) mount_device = mountpoint.rpartition("/")[2] if device_path.startswith('/dev/'): xml = """ @@ -436,7 +427,7 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def detach_volume(self, instance_name, mountpoint): - virt_dom = self._conn.lookupByName(instance_name) + virt_dom = self._lookup_by_name(instance_name) mount_device = mountpoint.rpartition("/")[2] xml = self._get_disk_xml(virt_dom.XMLDesc(0), mount_device) if not xml: @@ -453,7 +444,7 @@ class LibvirtConnection(driver.ComputeDriver): """ image_service = utils.import_object(FLAGS.image_service) - virt_dom = self._conn.lookupByName(instance['name']) + virt_dom = self._lookup_by_name(instance['name']) elevated = context.get_admin_context() base = image_service.show(elevated, instance['image_id']) @@ -712,7 +703,7 @@ class LibvirtConnection(driver.ComputeDriver): raise Exception(_('Unable to find an open port')) def get_pty_for_instance(instance_name): - virt_dom = self._conn.lookupByName(instance_name) + virt_dom = self._lookup_by_name(instance_name) xml = virt_dom.XMLDesc(0) dom = minidom.parseString(xml) @@ -737,7 +728,7 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def get_vnc_console(self, instance): def get_vnc_port_for_instance(instance_name): - virt_dom = self._conn.lookupByName(instance_name) + virt_dom = self._lookup_by_name(instance_name) xml = virt_dom.XMLDesc(0) # TODO: use etree instead of minidom dom = minidom.parseString(xml) @@ -1046,16 +1037,15 @@ class LibvirtConnection(driver.ComputeDriver): instance['name']) return xml - def get_info(self, instance_name): - """Retrieve information from libvirt for a specific instance name. + def _lookup_by_name(self, instance_name): + """Retrieve libvirt domain object given an instance name. - If a libvirt error is encountered during lookup, we might raise a - NotFound exception or Error exception depending on how severe the - libvirt error is. + All libvirt error handling should be handled in this method and + relevant nova exceptions should be raised in response. """ try: - virt_dom = self._conn.lookupByName(instance_name) + return self._conn.lookupByName(instance_name) except libvirt.libvirtError as ex: error_code = ex.get_error_code() if error_code == libvirt.VIR_ERR_NO_DOMAIN: @@ -1066,6 +1056,15 @@ class LibvirtConnection(driver.ComputeDriver): "[Error Code %(error_code)s] %(ex)s") % locals() raise exception.Error(msg) + def get_info(self, instance_name): + """Retrieve information from libvirt for a specific instance name. + + If a libvirt error is encountered during lookup, we might raise a + NotFound exception or Error exception depending on how severe the + libvirt error is. + + """ + virt_dom = self._lookup_by_name(instance_name) (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info() return {'state': state, 'max_mem': max_mem, @@ -1102,7 +1101,7 @@ class LibvirtConnection(driver.ComputeDriver): Returns a list of all block devices for this domain. """ - domain = self._conn.lookupByName(instance_name) + domain = self._lookup_by_name(instance_name) # TODO(devcamcar): Replace libxml2 with etree. xml = domain.XMLDesc(0) doc = None @@ -1144,7 +1143,7 @@ class LibvirtConnection(driver.ComputeDriver): Returns a list of all network interfaces for this instance. """ - domain = self._conn.lookupByName(instance_name) + domain = self._lookup_by_name(instance_name) # TODO(devcamcar): Replace libxml2 with etree. xml = domain.XMLDesc(0) doc = None @@ -1359,7 +1358,7 @@ class LibvirtConnection(driver.ComputeDriver): Note that this function takes an instance name, not an Instance, so that it can be called by monitor. """ - domain = self._conn.lookupByName(instance_name) + domain = self._lookup_by_name(instance_name) return domain.blockStats(disk) def interface_stats(self, instance_name, interface): @@ -1367,7 +1366,7 @@ class LibvirtConnection(driver.ComputeDriver): Note that this function takes an instance name, not an Instance, so that it can be called by monitor. """ - domain = self._conn.lookupByName(instance_name) + domain = self._lookup_by_name(instance_name) return domain.interfaceStats(interface) def get_console_pool_info(self, console_type): -- cgit From c3a45962a322086e4d7339f980bcf61ee8bd3167 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 09:38:01 -0700 Subject: rename all versions of image_ec2_id --- nova/api/ec2/cloud.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index d9c1af072..c48ec9004 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -159,7 +159,7 @@ class CloudController(object): floating_ip = db.instance_get_floating_address(ctxt, instance_ref['id']) ec2_id = ec2utils.id_to_ec2_id(instance_ref['id']) - image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'ami') + image_ec2_id = self.image_ec2_id(instance_ref['image_id']) data = { 'user-data': base64.b64decode(instance_ref['user_data']), 'meta-data': { @@ -188,8 +188,8 @@ class CloudController(object): for image_type in ['kernel', 'ramdisk']: if '%s_id' % image_type in instance_ref: - ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type], - self._image_type(image_type)) + ec2_id = self.image_ec2_id(instance_ref['%s_id' % image_type], + self._image_type(image_type)) data['meta-data']['%s-id' % image_type] = ec2_id if False: # TODO(vish): store ancestor ids @@ -709,7 +709,7 @@ class CloudController(object): instance_id = instance['id'] ec2_id = ec2utils.id_to_ec2_id(instance_id) i['instanceId'] = ec2_id - i['imageId'] = self._image_ec2_id(instance['image_id']) + i['imageId'] = self.image_ec2_id(instance['image_id']) i['instanceState'] = { 'code': instance['state'], 'name': instance['state_description']} @@ -917,15 +917,15 @@ class CloudController(object): """Convert from format defined by BaseImageService to S3 format.""" i = {} image_type = self._image_type(image.get('container_format')) - ec2_id = self._image_ec2_id(image.get('id'), image_type) + ec2_id = self.image_ec2_id(image.get('id'), image_type) name = image.get('name') i['imageId'] = ec2_id kernel_id = image['properties'].get('kernel_id') if kernel_id: - i['kernelId'] = self._image_ec2_id(kernel_id, 'aki') + i['kernelId'] = self.image_ec2_id(kernel_id, 'aki') ramdisk_id = image['properties'].get('ramdisk_id') if ramdisk_id: - i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari') + i['ramdiskId'] = self.image_ec2_id(ramdisk_id, 'ari') i['imageOwnerId'] = image['properties'].get('owner_id') if name: i['imageLocation'] = "%s (%s)" % (image['properties']. @@ -976,8 +976,8 @@ class CloudController(object): metadata = {'properties': {'image_location': image_location}} image = self.image_service.create(context, metadata) image_type = self._image_type(image.get('container_format')) - image_id = self._image_ec2_id(image['id'], - image_type) + image_id = self.image_ec2_id(image['id'], + image_type) msg = _("Registered image %(image_location)s with" " id %(image_id)s") % locals() LOG.audit(msg, context=context) -- cgit From 1378b117b7ea2bb05219b5a0e48f4b1ae8cac9ae Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Apr 2011 13:17:21 -0400 Subject: refactoring usage of exception.Duplicate errors --- nova/api/ec2/cloud.py | 3 +-- nova/auth/dbdriver.py | 5 ++--- nova/auth/ldapdriver.py | 11 ++++------- nova/exception.py | 39 +++++++++++++++++++++++++++++++++++---- nova/virt/hyperv.py | 3 +-- nova/virt/vmwareapi/vmops.py | 3 +-- nova/virt/xenapi/vm_utils.py | 3 +-- nova/virt/xenapi/vmops.py | 3 +-- 8 files changed, 46 insertions(+), 24 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index bd4c9dcd4..0bbfa0ea6 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -61,8 +61,7 @@ def _gen_key(context, user_id, key_name): # creation before creating key_pair try: db.key_pair_get(context, user_id, key_name) - raise exception.Duplicate(_("The key_pair %s already exists") - % key_name) + raise exception.KeyPairExists(key_name=key_name) except exception.NotFound: pass private_key, public_key, fingerprint = crypto.generate_key_pair() diff --git a/nova/auth/dbdriver.py b/nova/auth/dbdriver.py index b2c580d83..4f5022b9a 100644 --- a/nova/auth/dbdriver.py +++ b/nova/auth/dbdriver.py @@ -81,7 +81,7 @@ class DbDriver(object): user_ref = db.user_create(context.get_admin_context(), values) return self._db_user_to_auth_user(user_ref) except exception.Duplicate, e: - raise exception.Duplicate(_('User %s already exists') % name) + raise exception.UserExists(user=name) def _db_user_to_auth_user(self, user_ref): return {'id': user_ref['id'], @@ -132,8 +132,7 @@ class DbDriver(object): try: project = db.project_create(context.get_admin_context(), values) except exception.Duplicate: - raise exception.Duplicate(_("Project can't be created because " - "project %s already exists") % name) + raise exception.ProjectExists(project=name) for member in members: db.project_add_member(context.get_admin_context(), diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index fcac55510..1feb77625 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -171,7 +171,7 @@ class LdapDriver(object): def create_user(self, name, access_key, secret_key, is_admin): """Create a user""" if self.__user_exists(name): - raise exception.Duplicate(_("LDAP user %s already exists") % name) + raise exception.LDAPUserExists(user=name) if FLAGS.ldap_user_modify_only: if self.__ldap_user_exists(name): # Retrieve user by name @@ -226,8 +226,7 @@ class LdapDriver(object): description=None, member_uids=None): """Create a project""" if self.__project_exists(name): - raise exception.Duplicate(_("Project can't be created because " - "project %s already exists") % name) + raise exception.ProjectExists(project=name) if not self.__user_exists(manager_uid): raise exception.NotFound(_("Project can't be created because " "manager %s doesn't exist") @@ -471,8 +470,7 @@ class LdapDriver(object): description, member_uids=None): """Create a group""" if self.__group_exists(group_dn): - raise exception.Duplicate(_("Group can't be created because " - "group %s already exists") % name) + raise exception.LDAPGroupExists(group=name) members = [] if member_uids is not None: for member_uid in member_uids: @@ -512,8 +510,7 @@ class LdapDriver(object): raise exception.NotFound(_("The group at dn %s doesn't exist") % group_dn) if self.__is_in_group(uid, group_dn): - raise exception.Duplicate(_("User %(uid)s is already a member of " - "the group %(group_dn)s") % locals()) + raise exception.LDAPMembershipExists(uid=uid, group_dn=group_dn) attr = [(self.ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))] self.conn.modify_s(group_dn, attr) diff --git a/nova/exception.py b/nova/exception.py index 6948a93c1..6d3bcd67e 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -68,10 +68,6 @@ class VolumeNotFound(NotFound): super(VolumeNotFound, self).__init__(message) -class Duplicate(Error): - pass - - class NotAuthorized(Error): pass @@ -225,3 +221,38 @@ class InvalidVLANPortGroup(Invalid): class ImageUnacceptable(Invalid): message = _("Image %(image_id)s is unacceptable") + ": %(reason)s" + + +#TODO(bcwaldon): EOL this exception! +class Duplicate(NovaException): + pass + + +class KeyPairExists(Duplicate): + message = _("Key pair %(key_name)s already exists.") + + +class UserExists(Duplicate): + message = _("User %(user)s already exists.") + + +class LDAPUserExists(UserExists): + message = _("LDAP user %(user)s already exists.") + + +class LDAPGroupExists(Duplicate): + message = _("LDAP group %(group)s already exists.") + + +class LDAPMembershipExists(Duplicate): + message = _("User %(uid)s is already a member of " + "the group %(group_dn)s") + + +class ProjectExists(Duplicate): + message = _("Project %(project)s already exists.") + + +class InstanceExists(Duplicate): + message = _("Instance %(name)s already exists.") + diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 13f403a66..85d5190fb 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -143,8 +143,7 @@ class HyperVConnection(driver.ComputeDriver): """ Create a new VM and start it.""" vm = self._lookup(instance.name) if vm is not None: - raise exception.Duplicate(_('Attempt to create duplicate vm %s') % - instance.name) + raise exception.InstanceExists(name=instance.name) user = manager.AuthManager().get_user(instance['user_id']) project = manager.AuthManager().get_project(instance['project_id']) diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index b700c438f..d77f9f8cb 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -100,8 +100,7 @@ class VMWareVMOps(object): """ vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref: - raise exception.Duplicate(_("Attempted to create a VM with a name" - " %s, but that already exists on the host") % instance.name) + raise exception.InstanceExists(name=instance.name) client_factory = self._session._get_vim().client.factory service_content = self._session._get_vim().get_service_content() diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d2045a557..4b00b45ca 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -647,8 +647,7 @@ class VMHelper(HelperBase): if n == 0: return None elif n > 1: - raise exception.Duplicate(_('duplicate name found: %s') % - name_label) + raise exception.InstanceExists(name=name_label) else: return vm_refs[0] diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 7c7aa8e98..6f2870501 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -127,8 +127,7 @@ class VMOps(object): instance_name = instance.name vm_ref = VMHelper.lookup(self._session, instance_name) if vm_ref is not None: - raise exception.Duplicate(_('Attempted to create' - ' non-unique name %s') % instance_name) + raise exception.InstanceExists(name=instance_name) #ensure enough free memory is available if not VMHelper.ensure_free_mem(self._session, instance): -- cgit From 2ed46e198933de00059e8436b970efa0a0de8318 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Apr 2011 13:18:15 -0400 Subject: pep8 fix --- nova/exception.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/exception.py b/nova/exception.py index 6d3bcd67e..ba16c4796 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -255,4 +255,3 @@ class ProjectExists(Duplicate): class InstanceExists(Duplicate): message = _("Instance %(name)s already exists.") - -- cgit From 4442d8f7017868f64eacc6d8ad94620263b9a9c9 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 10:20:56 -0700 Subject: make geninter.sh use the right tmpl file --- nova/CA/geninter.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/CA/geninter.sh b/nova/CA/geninter.sh index 4b7f5a55c..9b3ea3b76 100755 --- a/nova/CA/geninter.sh +++ b/nova/CA/geninter.sh @@ -21,7 +21,7 @@ NAME=$1 SUBJ=$2 mkdir -p projects/$NAME cd projects/$NAME -cp ../../openssl.cnf.tmpl openssl.cnf +cp "$(dirname $0)/openssl.cnf.tmpl" openssl.cnf sed -i -e s/%USERNAME%/$NAME/g openssl.cnf mkdir -p certs crl newcerts private openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes -- cgit From 66a15373a14e9acc30808d2cf21bd800c64cc012 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 10:31:35 -0700 Subject: fix doc typo --- doc/source/devref/cloudpipe.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/devref/cloudpipe.rst b/doc/source/devref/cloudpipe.rst index a1f6c6450..15d3160b7 100644 --- a/doc/source/devref/cloudpipe.rst +++ b/doc/source/devref/cloudpipe.rst @@ -62,13 +62,13 @@ Making a cloudpipe image is relatively easy. :language: bash :linenos: -# download and run the payload on boot from /etc/rc.local. +# download and run the payload on boot from /etc/rc.local .. literalinclude:: rc.local :language: bash :linenos: -# setup network interfaces. +# setup /etc/network/interfaces .. literalinclude:: interfaces :language: bash -- cgit From 41966e6475db5da505947b816670797c0cede029 Mon Sep 17 00:00:00 2001 From: Jason Kölker Date: Tue, 19 Apr 2011 15:52:32 -0500 Subject: add support for git checking and a default of failing if the history can't be read --- nova/tests/test_misc.py | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/nova/tests/test_misc.py b/nova/tests/test_misc.py index 4e17e1ce0..ad62b48bf 100644 --- a/nova/tests/test_misc.py +++ b/nova/tests/test_misc.py @@ -29,11 +29,12 @@ from nova.utils import parse_mailmap, str_dict_replace class ProjectTestCase(test.TestCase): def test_authors_up_to_date(self): topdir = os.path.normpath(os.path.dirname(__file__) + '/../../') - if os.path.exists(os.path.join(topdir, '.bzr')): - contributors = set() - - mailmap = parse_mailmap(os.path.join(topdir, '.mailmap')) + missing = set() + contributors = set() + mailmap = parse_mailmap(os.path.join(topdir, '.mailmap')) + authors_file = open(os.path.join(topdir, 'Authors'), 'r').read() + if os.path.exists(os.path.join(topdir, '.bzr')): import bzrlib.workingtree tree = bzrlib.workingtree.WorkingTree.open(topdir) tree.lock_read() @@ -47,22 +48,36 @@ class ProjectTestCase(test.TestCase): for r in revs: for author in r.get_apparent_authors(): email = author.split(' ')[-1] - contributors.add(str_dict_replace(email, mailmap)) + contributors.add(str_dict_replace(email, + mailmap)) + finally: + tree.unlock() - authors_file = open(os.path.join(topdir, 'Authors'), - 'r').read() + elif os.path.exists(os.path.join(topdir, '.git')): + import git + repo = git.Repo(topdir) + for commit in repo.head.commit.iter_parents(): + email = commit.author.email + if email is None: + email = commit.author.name + if 'nova-core' in email: + continue + if email.split(' ')[-1] == '<>': + email = email.split(' ')[-2] + email = '<' + email + '>' + contributors.add(str_dict_replace(email, mailmap)) - missing = set() - for contributor in contributors: - if contributor == 'nova-core': - continue - if not contributor in authors_file: - missing.add(contributor) + else: + self.assertTrue(False, 'Cannot read commit history') - self.assertTrue(len(missing) == 0, - '%r not listed in Authors' % missing) - finally: - tree.unlock() + for contributor in contributors: + if contributor == 'nova-core': + continue + if not contributor in authors_file: + missing.add(contributor) + + self.assertTrue(len(missing) == 0, + '%r not listed in Authors' % missing) class LockTestCase(test.TestCase): -- cgit From 63f5aa5484aa9d61f2ed79caae1c665230a56f35 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Tue, 19 Apr 2011 15:25:39 -0700 Subject: revamped spacing per Rick Harris suggestion. Added exact error to nova-manage output. --- bin/nova-manage | 1 + nova/compute/instance_types.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 8ec3a1e64..6b47cc4f5 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -833,6 +833,7 @@ class InstanceTypeCommands(object): sys.exit(1) except exception.ApiError, e: print "\n\n" + print "\n%s" % e print "Please ensure instance_type name and flavorid are unique." print "To complete remove a instance_type, use the --purge flag:" print "\n # nova-manage instance_type delete --purge\n" diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index a92e5d3bc..6accf82bb 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -56,8 +56,8 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, rxtx_cap=rxtx_cap)) except exception.DBError, e: LOG.exception(_('DB error: %s') % e) - raise exception.ApiError(_("Cannot create instance_type with\ - name %(name)s and flavorid %(flavorid)s") % + raise exception.ApiError(_("Cannot create instance_type with " + "name %(name)s and flavorid %(flavorid)s") % locals()) -- cgit From 45178fd6da58ff37617e35b5cddaf416ae5cee65 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Wed, 20 Apr 2011 17:44:25 +0400 Subject: fix: mark floating ip as auto assigned --- nova/compute/manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 313b9e0e1..c1dc06557 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -252,6 +252,7 @@ class ComputeManager(manager.SchedulerDependentManager): if FLAGS.auto_assign_floating_ip: public_ip = self.network_api.allocate_floating_ip(context) + self.db.floating_ip_set_auto_assigned(context, public_ip) fixed_ip = self.db.fixed_ip_get_by_address(context, address) floating_ip = self.db.floating_ip_get_by_address(context, public_ip) -- cgit From bee606e08f5ba96a25d02a9358265db4a59ce5cd Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 20 Apr 2011 11:06:03 -0400 Subject: Removed _ and replaced with real variable name. --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index a0f0ff27e..703533f55 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -106,7 +106,7 @@ class API(base.Base): def _check_injected_file_format(self, injected_files): """Ensure given injected files are in the correct format.""" - for _, content in injected_files: + for file_path, content in injected_files: try: base64.b64decode(content) except TypeError: -- cgit From e628007bec0e313f252d8dd15d19297f99dc93f8 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 20 Apr 2011 11:09:14 -0400 Subject: Removed TODO we don't need. --- nova/compute/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0e5d6c4ff..5a7b4fb11 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -319,7 +319,6 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_("Rebuilding instance %s"), instance_id, context=context) - # TODO(blamar): Detach volumes prior to rebuild. self._update_state(context, instance_id, power_state.BUILDING) self.driver.destroy(instance_ref) -- cgit From 6c538b870005464b2bab0510b4e98a71d0d24770 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 20 Apr 2011 11:11:45 -0400 Subject: Removed no longer relevant comment. --- nova/compute/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 5a7b4fb11..46f910b27 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1103,7 +1103,6 @@ class ComputeManager(manager.SchedulerDependentManager): # NOTE(justinsb): We have to be very careful here, because a # concurrent operation could be in progress (e.g. a spawn) if db_state == power_state.BUILDING: - # Assume that NOSTATE => spawning # TODO(justinsb): This does mean that if we crash during a # spawn, the machine will never leave the spawning state, # but this is just the way nova is; this function isn't -- cgit From bdbfcb49179d32da5fcecd75fb849efe71469b00 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 20 Apr 2011 11:16:35 -0400 Subject: Reverted bad merge. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 6b417124e..715512507 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -609,7 +609,7 @@ class LibvirtConnection(driver.ComputeDriver): # for xenapi(tr3buchet) @exception.wrap_exception def spawn(self, instance, network_info=None): - xml = self.to_xml(instance, network_info) + xml = self.to_xml(instance, False, network_info) self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) -- cgit From 48936f6b8f063cf71fa42b4586d8ba524ed39cc4 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 20 Apr 2011 20:37:51 +0400 Subject: fix Request.get_content_type --- nova/api/openstack/servers.py | 67 +++++++++++------------------ nova/tests/api/openstack/test_extensions.py | 1 + nova/tests/api/openstack/test_servers.py | 4 ++ nova/tests/api/test_wsgi.py | 7 +++ nova/wsgi.py | 20 ++++++--- 5 files changed, 52 insertions(+), 47 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 22a9c632c..e0681e597 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -294,61 +294,47 @@ class Controller(common.OpenstackController): 'revertResize': self._action_revert_resize, 'rebuild': self._action_rebuild, } - input_dict = self._deserialize(req.body, req.get_content_type()) for key in actions.keys(): if key in input_dict: - return actions[key](input_dict, req, id) + try: + context = req.environ['nova.context'] + return actions[key](context, input_dict, id) + except Exception, e: + LOG.exception(_("Error in action %(key)s: %(e)s") % + locals()) + return faults.Fault(exc.HTTPBadRequest()) return faults.Fault(exc.HTTPNotImplemented()) - def _action_change_password(self, input_dict, req, id): + def _action_change_password(self, context, input_dict, id): return exc.HTTPNotImplemented() - def _action_confirm_resize(self, input_dict, req, id): - try: - self.compute_api.confirm_resize(req.environ['nova.context'], id) - except Exception, e: - LOG.exception(_("Error in confirm-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + def _action_confirm_resize(self, context, input_dict, id): + self.compute_api.confirm_resize(context, id) return exc.HTTPNoContent() - def _action_revert_resize(self, input_dict, req, id): - try: - self.compute_api.revert_resize(req.environ['nova.context'], id) - except Exception, e: - LOG.exception(_("Error in revert-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + def _action_revert_resize(self, context, input_dict, id): + self.compute_api.revert_resize(context, id) return exc.HTTPAccepted() - def _action_rebuild(self, input_dict, req, id): + def _action_rebuild(self, context, input_dict, id): return faults.Fault(exc.HTTPNotImplemented()) - def _action_resize(self, input_dict, req, id): + def _action_resize(self, context, input_dict, id): """ Resizes a given instance to the flavor size requested """ - try: - if 'resize' in input_dict and 'flavorId' in input_dict['resize']: - flavor_id = input_dict['resize']['flavorId'] - self.compute_api.resize(req.environ['nova.context'], id, - flavor_id) - else: - LOG.exception(_("Missing arguments for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) - except Exception, e: - LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + if 'resize' in input_dict and 'flavorId' in input_dict['resize']: + flavor_id = input_dict['resize']['flavorId'] + self.compute_api.resize(context, id, flavor_id) + else: + LOG.exception(_("Missing arguments for resize")) + return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPAccepted()) - def _action_reboot(self, input_dict, req, id): - try: - reboot_type = input_dict['reboot']['type'] - except Exception: - raise faults.Fault(exc.HTTPNotImplemented()) - try: - # TODO(gundlach): pass reboot_type, support soft reboot in - # virt driver - self.compute_api.reboot(req.environ['nova.context'], id) - except: - return faults.Fault(exc.HTTPUnprocessableEntity()) + def _action_reboot(self, context, input_dict, id): + reboot_type = input_dict['reboot']['type'] + # TODO(gundlach): pass reboot_type, support soft reboot in + # virt driver + self.compute_api.reboot(context, id) return exc.HTTPAccepted() @scheduler_api.redirect_handler @@ -632,8 +618,7 @@ class ControllerV11(Controller): def _get_addresses_view_builder(self, req): return nova.api.openstack.views.addresses.ViewBuilderV11(req) - def _action_change_password(self, input_dict, req, id): - context = req.environ['nova.context'] + def _action_change_password(self, context, input_dict, id): if (not 'changePassword' in input_dict or not 'adminPass' in input_dict['changePassword']): msg = _("No adminPass was specified") diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 481d34ed1..6453e96c9 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -158,6 +158,7 @@ class ActionExtensionTest(unittest.TestCase): request.method = 'POST' request.content_type = 'application/json' request.body = json.dumps(body) + request.environ = {'nova.context': 'context'} response = request.get_response(ext_midware) return response diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 556046e9d..a92da52b1 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -952,6 +952,7 @@ class ServersTest(test.TestCase): req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) + req.environ = {"nova.context": "context"} res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 501) @@ -973,6 +974,7 @@ class ServersTest(test.TestCase): req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) + req.environ = {"nova.context": "context"} res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) self.assertEqual(mock_method.instance_id, '1') @@ -993,6 +995,7 @@ class ServersTest(test.TestCase): req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) + req.environ = {"nova.context": "context"} res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) @@ -1002,6 +1005,7 @@ class ServersTest(test.TestCase): req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) + req.environ = {"nova.context": "context"} res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) diff --git a/nova/tests/api/test_wsgi.py b/nova/tests/api/test_wsgi.py index 1ecdd1cfb..ed96aac5e 100644 --- a/nova/tests/api/test_wsgi.py +++ b/nova/tests/api/test_wsgi.py @@ -136,6 +136,13 @@ class RequestTest(test.TestCase): request.body = "asdf
" self.assertRaises(webob.exc.HTTPBadRequest, request.get_content_type) + def test_request_content_type_with_charset(self): + request = wsgi.Request.blank('/tests/123') + request.headers["Content-Type"] = "application/json; charset=UTF-8" + request.body = "" + result = request.get_content_type() + self.assertEqual(result, "application/json") + def test_content_type_from_accept_xml(self): request = wsgi.Request.blank('/tests/123') request.headers["Accept"] = "application/xml" diff --git a/nova/wsgi.py b/nova/wsgi.py index de2e0749f..617830c22 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -28,6 +28,7 @@ from xml.dom import minidom import eventlet import eventlet.wsgi eventlet.patcher.monkey_patch(all=False, socket=True, time=True) +import re import routes import routes.middleware import webob @@ -105,12 +106,19 @@ class Request(webob.Request): return bm or "application/json" def get_content_type(self): - try: - ct = self.headers["Content-Type"] - assert ct in ("application/xml", "application/json") - return ct - except Exception: - raise webob.exc.HTTPBadRequest("Invalid content type") + allowed_types = ("application/xml", "application/json") + if not "Content-Type" in self.headers: + msg = _("Missing Content-Type") + LOG.debug(msg) + raise webob.exc.HTTPBadRequest(msg) + content_type = self.headers["Content-Type"] + match = re.search("([\w/]+)", content_type) + if match: + type = match.group(0) + if type in allowed_types: + return type + LOG.debug(_("Wrong Content-Type: %s") % content_type) + raise webob.exc.HTTPBadRequest("Invalid content type") class Application(object): -- cgit From a4b78306d31e1ef84d5dc9550ef2dcb1ed030fa2 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 20 Apr 2011 21:34:55 +0400 Subject: fix after review: style, improving tests, replacing underscore --- nova/tests/test_virt.py | 20 ++++++++++++++------ nova/virt/libvirt_conn.py | 14 +++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 19e4d5428..2e6fae6c7 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -223,7 +223,7 @@ class LibvirtConnTestCase(test.TestCase): _create_network_info(2)) self.assertTrue(len(result['nics']) == 2) - def test_get_nic_for_xml(self): + def test_get_nic_for_xml_v4(self): conn = libvirt_conn.LibvirtConnection(True) network, mapping = _create_network_info()[0] self.flags(use_ipv6=False) @@ -794,8 +794,11 @@ class IptablesFirewallTestCase(test.TestCase): self.assertEquals(len(rulesv6), 3) def multinic_iptables_test(self): + ipv4_rules_per_network = 2 + ipv6_rules_per_network = 3 + networks_count = 5 instance_ref = self._create_instance_ref() - network_info = _create_network_info() + network_info = _create_network_info(networks_count) ipv4_len = len(self.fw.iptables.ipv4['filter'].rules) ipv6_len = len(self.fw.iptables.ipv6['filter'].rules) inst_ipv4, inst_ipv6 = self.fw.instance_rules(instance_ref, @@ -803,8 +806,12 @@ class IptablesFirewallTestCase(test.TestCase): self.fw.add_filters_for_instance(instance_ref, network_info) ipv4 = self.fw.iptables.ipv4['filter'].rules ipv6 = self.fw.iptables.ipv6['filter'].rules - self.assertEquals(len(ipv4) - len(inst_ipv4) - ipv4_len, 2) - self.assertEquals(len(ipv6) - len(inst_ipv6) - ipv6_len, 3) + ipv4_network_rules = len(ipv4) - len(inst_ipv4) - ipv4_len + ipv6_network_rules = len(ipv6) - len(inst_ipv6) - ipv6_len + self.assertEquals(ipv4_network_rules, + ipv4_rules_per_network * networks_count) + self.assertEquals(ipv6_network_rules, + ipv6_rules_per_network * networks_count) class NWFilterTestCase(test.TestCase): @@ -965,6 +972,7 @@ class NWFilterTestCase(test.TestCase): def test_create_network_filters(self): instance_ref = self._create_instance() network_info = _create_network_info(3) - result = \ - self.fw._create_network_filters(instance_ref, network_info, "fake") + result = self.fw._create_network_filters(instance_ref, + network_info, + "fake") self.assertEquals(len(result), 3) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 73a804014..7e8ff409a 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1839,12 +1839,12 @@ class NWFilterFirewall(FirewallDriver): 'nova-allow-dhcp-server'] if FLAGS.use_ipv6: - networks = [network for (network, _) in network_info if + networks = [network for (network, _m) in network_info if network['gateway_v6']] if networks: instance_secgroup_filter_children.\ - append('nova-allow-ra-server') + append('nova-allow-ra-server') for security_group in \ db.security_group_get_by_instance(ctxt, instance['id']): @@ -1859,8 +1859,8 @@ class NWFilterFirewall(FirewallDriver): instance_secgroup_filter_children)) network_filters = self.\ - _create_network_filters(instance, network_info, - instance_secgroup_filter_name) + _create_network_filters(instance, network_info, + instance_secgroup_filter_name) for (name, children) in network_filters: self._define_filters(name, children) @@ -1873,7 +1873,7 @@ class NWFilterFirewall(FirewallDriver): base_filter = 'nova-base' result = [] - for (_, mapping) in network_info: + for (_n, mapping) in network_info: nic_id = mapping['mac'].replace(':', '') instance_filter_name = self._instance_filter_name(instance, nic_id) instance_filter_children = [base_filter, @@ -1996,11 +1996,11 @@ class IptablesFirewallDriver(FirewallDriver): return ['-d %s -j $%s' % (ip, chain_name) for ip in ips] def _filters_for_instance(self, chain_name, network_info): - ips_v4 = [ip['ip'] for (_, mapping) in network_info + ips_v4 = [ip['ip'] for (_n, mapping) in network_info for ip in mapping['ips']] ipv4_rules = self._create_filter(ips_v4, chain_name) - ips_v6 = [ip['ip'] for (_, mapping) in network_info + ips_v6 = [ip['ip'] for (_n, mapping) in network_info for ip in mapping['ip6s']] ipv6_rules = self._create_filter(ips_v6, chain_name) -- cgit From 155635faf20d4a1996639baf5d2c10b05734c3df Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 20 Apr 2011 21:50:03 +0400 Subject: replaced regex to webob.Request.content_type --- nova/wsgi.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/nova/wsgi.py b/nova/wsgi.py index 617830c22..9e0f80565 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -28,7 +28,6 @@ from xml.dom import minidom import eventlet import eventlet.wsgi eventlet.patcher.monkey_patch(all=False, socket=True, time=True) -import re import routes import routes.middleware import webob @@ -111,13 +110,10 @@ class Request(webob.Request): msg = _("Missing Content-Type") LOG.debug(msg) raise webob.exc.HTTPBadRequest(msg) - content_type = self.headers["Content-Type"] - match = re.search("([\w/]+)", content_type) - if match: - type = match.group(0) - if type in allowed_types: - return type - LOG.debug(_("Wrong Content-Type: %s") % content_type) + type = self.content_type + if type in allowed_types: + return type + LOG.debug(_("Wrong Content-Type: %s") % type) raise webob.exc.HTTPBadRequest("Invalid content type") -- cgit From fe23f71687e09248feb7542ea97001a496697742 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 20 Apr 2011 22:01:14 +0400 Subject: remove ambiguity in test --- nova/tests/api/test_wsgi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/api/test_wsgi.py b/nova/tests/api/test_wsgi.py index ed96aac5e..5820ecdc2 100644 --- a/nova/tests/api/test_wsgi.py +++ b/nova/tests/api/test_wsgi.py @@ -139,7 +139,6 @@ class RequestTest(test.TestCase): def test_request_content_type_with_charset(self): request = wsgi.Request.blank('/tests/123') request.headers["Content-Type"] = "application/json; charset=UTF-8" - request.body = "" result = request.get_content_type() self.assertEqual(result, "application/json") -- cgit From 2e9b8301e835a97bf250026f98c7729d76be4407 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 20 Apr 2011 13:37:21 -0700 Subject: fix display of vpn instance id and add output rule so it can be tested from network host --- bin/nova-manage | 2 +- nova/network/linux_net.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index b2308bc03..55e275e7a 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -151,7 +151,7 @@ class VpnCommands(object): state = 'up' print address, print vpn['host'], - print vpn['ec2_id'], + print ec2utils.id_to_ec2_id(vpn['id']), print vpn['state_description'], print state else: diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index ec5579dee..ad9b3f13c 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -407,6 +407,10 @@ def ensure_vlan_forward(public_ip, port, private_ip): "-d %s -p udp " "--dport %s -j DNAT --to %s:1194" % (public_ip, port, private_ip)) + iptables_manager.ipv4['nat'].add_rule("OUTPUT", + "-d %s -p udp " + "--dport %s -j DNAT --to %s:1194" % + (public_ip, port, private_ip)) iptables_manager.apply() -- cgit From 2217872ff5e8e5b53af0b38064a3cdbc2c783ebb Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 21 Apr 2011 05:22:09 +0400 Subject: pep8 cleaning --- nova/compute/manager.py | 47 +++++++++++----------- .../015_add_auto_assign_to_floating_ips.py | 1 + nova/network/api.py | 6 +-- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c1dc06557..13a082b44 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -37,8 +37,6 @@ terminating it. import datetime import os -import random -import string import socket import sys import tempfile @@ -54,7 +52,7 @@ from nova import rpc from nova import utils from nova.compute import power_state from nova.virt import driver -from nova.network import api as network_api +from nova import network FLAGS = flags.FLAGS flags.DEFINE_string('instances_path', '$state_path/instances', @@ -74,8 +72,8 @@ flags.DEFINE_integer('live_migration_retry_count', 30, flags.DEFINE_integer("rescue_timeout", 0, "Automatically unrescue an instance after N seconds." " Set to 0 to disable.") -flags.DEFINE_bool('auto_assign_floating_ip', False, 'Autoassigning floating' - ' ip to VM') +flags.DEFINE_bool('auto_assign_floating_ip', False, + 'Autoassigning floating ip to VM') LOG = logging.getLogger('nova.compute.manager') @@ -135,7 +133,7 @@ class ComputeManager(manager.SchedulerDependentManager): self.network_manager = utils.import_object(FLAGS.network_manager) self.volume_manager = utils.import_object(FLAGS.volume_manager) - self.network_api = network_api.API() + self.network_api = network.API() super(ComputeManager, self).__init__(service_name="compute", *args, **kwargs) @@ -248,18 +246,18 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id, power_state.SHUTDOWN) - if not FLAGS.stub_network: - if FLAGS.auto_assign_floating_ip: - public_ip = self.network_api.allocate_floating_ip(context) + if not FLAGS.stub_network and FLAGS.auto_assign_floating_ip: + public_ip = self.network_api.allocate_floating_ip(context) - self.db.floating_ip_set_auto_assigned(context, public_ip) - fixed_ip = self.db.fixed_ip_get_by_address(context, address) - floating_ip = self.db.floating_ip_get_by_address(context, - public_ip) + self.db.floating_ip_set_auto_assigned(context, public_ip) + fixed_ip = self.db.fixed_ip_get_by_address(context, address) + floating_ip = self.db.floating_ip_get_by_address(context, + public_ip) - self.network_api.associate_floating_ip(context, floating_ip, - fixed_ip, - affect_auto_assigned=True) + self.network_api.associate_floating_ip(context, + floating_ip, + fixed_ip, + affect_auto_assigned=True) self._update_state(context, instance_id) @exception.wrap_exception @@ -280,14 +278,17 @@ class ComputeManager(manager.SchedulerDependentManager): # NOTE(vish): Right now we don't really care if the ip is # disassociated. We may need to worry about # checking this later. - self.network_api.disassociate_floating_ip(context, address, - affect_auto_assigned=True) - if FLAGS.auto_assign_floating_ip \ - and floating_ip.get('auto_assigned'): + self.network_api.disassociate_floating_ip(context, + address, + True) + if (FLAGS.auto_assign_floating_ip + and floating_ip.get('auto_assigned')): LOG.debug(_("Deallocating floating ip %s"), - floating_ip['address'], context=context) - self.network_api.release_floating_ip(context, address, - affect_auto_assigned=True) + floating_ip['address'], + context=context) + self.network_api.release_floating_ip(context, + address, + True) address = fixed_ip['address'] if address: diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py index f3767b29f..64d6df77f 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -22,6 +22,7 @@ from migrate import * meta = MetaData() + c_auto_assigned = Column('auto_assigned', Boolean, default=False) diff --git a/nova/network/api.py b/nova/network/api.py index 61db646ae..1d8193b28 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -52,7 +52,7 @@ class API(base.Base): "args": {"project_id": context.project_id}}) def release_floating_ip(self, context, address, - affect_auto_assigned = False): + affect_auto_assigned=False): floating_ip = self.db.floating_ip_get_by_address(context, address) if not affect_auto_assigned and floating_ip.get('auto_assigned'): return @@ -66,7 +66,7 @@ class API(base.Base): "args": {"floating_address": floating_ip['address']}}) def associate_floating_ip(self, context, floating_ip, fixed_ip, - affect_auto_assigned = False): + affect_auto_assigned=False): if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) @@ -97,7 +97,7 @@ class API(base.Base): "fixed_address": fixed_ip['address']}}) def disassociate_floating_ip(self, context, address, - affect_auto_assigned = False): + affect_auto_assigned=False): floating_ip = self.db.floating_ip_get_by_address(context, address) if not affect_auto_assigned and floating_ip.get('auto_assigned'): return -- cgit From ba9edf8d6d93290d1f1e85bb3a51e3a69e3f0822 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 20 Apr 2011 21:06:56 -0700 Subject: put up and down in the right dir --- doc/source/devref/down.sh | 7 +++++++ doc/source/devref/up.sh | 7 +++++++ doc/source/down.sh | 7 ------- doc/source/up.sh | 7 ------- 4 files changed, 14 insertions(+), 14 deletions(-) create mode 100644 doc/source/devref/down.sh create mode 100644 doc/source/devref/up.sh delete mode 100644 doc/source/down.sh delete mode 100644 doc/source/up.sh diff --git a/doc/source/devref/down.sh b/doc/source/devref/down.sh new file mode 100644 index 000000000..5c1888870 --- /dev/null +++ b/doc/source/devref/down.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +BR=$1 +DEV=$2 + +/usr/sbin/brctl delif $BR $DEV +/sbin/ifconfig $DEV down diff --git a/doc/source/devref/up.sh b/doc/source/devref/up.sh new file mode 100644 index 000000000..073a58e15 --- /dev/null +++ b/doc/source/devref/up.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +BR=$1 +DEV=$2 +MTU=$3 +/sbin/ifconfig $DEV mtu $MTU promisc up +/usr/sbin/brctl addif $BR $DEV diff --git a/doc/source/down.sh b/doc/source/down.sh deleted file mode 100644 index 5c1888870..000000000 --- a/doc/source/down.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -BR=$1 -DEV=$2 - -/usr/sbin/brctl delif $BR $DEV -/sbin/ifconfig $DEV down diff --git a/doc/source/up.sh b/doc/source/up.sh deleted file mode 100644 index 073a58e15..000000000 --- a/doc/source/up.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -BR=$1 -DEV=$2 -MTU=$3 -/sbin/ifconfig $DEV mtu $MTU promisc up -/usr/sbin/brctl addif $BR $DEV -- cgit From db1f6a3f2a8d85c82eb3530194e61276e7f54c6a Mon Sep 17 00:00:00 2001 From: Yoshiaki Tamura Date: Thu, 21 Apr 2011 16:54:37 +0900 Subject: Add a test checking spawn() works when network_info is set, which currently doesn't. The following patch would fix it. --- nova/tests/test_virt.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index aeaea91c7..fdde6ed97 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -549,6 +549,43 @@ class LibvirtConnTestCase(test.TestCase): db.volume_destroy(self.context, volume_ref['id']) db.instance_destroy(self.context, instance_ref['id']) + def test_spawn_with_network_info(self): + # Skip if non-libvirt environment + if not self.lazy_load_library_exists(): + return + + # Preparing mocks + def fake_none(self, instance): + return + + self.create_fake_libvirt_mock() + instance = db.instance_create(self.context, self.test_instance) + + # Start test + self.mox.ReplayAll() + conn = libvirt_conn.LibvirtConnection(False) + conn.firewall_driver.setattr('setup_basic_filtering', fake_none) + conn.firewall_driver.setattr('prepare_instance_filter', fake_none) + + network = db.project_get_network(context.get_admin_context(), + self.project.id) + ip_dict = {'ip': self.test_ip, + 'netmask': network['netmask'], + 'enabled': '1'} + mapping = {'label': network['label'], + 'gateway': network['gateway'], + 'mac': instance['mac_address'], + 'dns': [network['dns']], + 'ips': [ip_dict]} + network_info = [(network, mapping)] + + try: + conn.spawn(instance, network_info) + except Exception, e: + count = (0 <= e.message.find('Unexpected method call')) + + self.assertTrue(count) + def tearDown(self): self.manager.delete_project(self.project) self.manager.delete_user(self.user) -- cgit From ba00a83490d6f442688d42f7f58c5f6cc566e1ee Mon Sep 17 00:00:00 2001 From: Yoshiaki Tamura Date: Thu, 21 Apr 2011 16:54:59 +0900 Subject: Fix parameter mismatch calling _create_image() from spawn() in libvirt_conn.py --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9e815799f..a8de7147b 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -612,7 +612,7 @@ class LibvirtConnection(driver.ComputeDriver): 'launching') self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) - self._create_image(instance, xml, network_info) + self._create_image(instance, xml, network_info=network_info) domain = self._create_new_domain(xml) LOG.debug(_("instance %s: is running"), instance['name']) self.firewall_driver.apply_instance_filter(instance) -- cgit From e1f37b81e805c087947c87a9bc341dd60e7e481c Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 21 Apr 2011 15:49:47 +0400 Subject: style fixing --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 13a082b44..864b3a816 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -48,11 +48,11 @@ from nova import exception from nova import flags from nova import log as logging from nova import manager +from nova import network from nova import rpc from nova import utils from nova.compute import power_state from nova.virt import driver -from nova import network FLAGS = flags.FLAGS flags.DEFINE_string('instances_path', '$state_path/instances', -- cgit From e6b76ce6886a1404739a972d106248a67df4f02a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 21 Apr 2011 07:35:30 -0700 Subject: use simpler interfaces --- doc/source/devref/interfaces | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/doc/source/devref/interfaces b/doc/source/devref/interfaces index 2aae39558..b7116aeb7 100644 --- a/doc/source/devref/interfaces +++ b/doc/source/devref/interfaces @@ -1,18 +1,17 @@ +# This file describes the network interfaces available on your system +# and how to activate them. For more information, see interfaces(5). + # The loopback network interface auto lo iface lo inet loopback # The primary network interface +auto eth0 +iface eth0 inet manual + up ifconfig $IFACE 0.0.0.0 up + down ifconfig $IFACE down + auto br0 iface br0 inet dhcp bridge_ports eth0 - bridge_fd 9 ## from the libvirt docs (forward delay time) - bridge_hello 2 ## from the libvirt docs (hello time) - bridge_maxage 12 ## from the libvirt docs (maximum message age) - bridge_stp off ## from the libvirt docs (spanning tree protocol) -iface eth0 inet manual - up ifconfig $IFACE 0.0.0.0 up - up ip link set $IFACE promisc on - down ip link set $IFACE promisc off - down ifconfig $IFACE down -- cgit From 2d82195d59240ea53d4726879d2a28a5872e58f7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 21 Apr 2011 07:39:49 -0700 Subject: use vpn filter in basic filtering so cloudpipe works with iptables driver --- nova/virt/libvirt_conn.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9c8d64446..3dcb8ae42 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1734,11 +1734,16 @@ class NWFilterFirewall(FirewallDriver): logging.info('ensuring static filters') self._ensure_static_filters() + if instance['image_id'] == str(FLAGS.vpn_image_id): + base_filter = 'nova-vpn' + else: + base_filter = 'nova-base' + for (network, mapping) in network_info: nic_id = mapping['mac'].replace(':', '') instance_filter_name = self._instance_filter_name(instance, nic_id) self._define_filter(self._filter_container(instance_filter_name, - ['nova-base'])) + [base_filter])) def _ensure_static_filters(self): if self.static_filters_configured: @@ -1749,11 +1754,12 @@ class NWFilterFirewall(FirewallDriver): 'no-ip-spoofing', 'no-arp-spoofing', 'allow-dhcp-server'])) + self._define_filter(self._filter_container('nova-vpn', + ['allow-dhcp-server'])) self._define_filter(self.nova_base_ipv4_filter) self._define_filter(self.nova_base_ipv6_filter) self._define_filter(self.nova_dhcp_filter) self._define_filter(self.nova_ra_filter) - self._define_filter(self.nova_vpn_filter) if FLAGS.allow_project_net_traffic: self._define_filter(self.nova_project_filter) if FLAGS.use_ipv6: @@ -1767,14 +1773,6 @@ class NWFilterFirewall(FirewallDriver): ''.join(["" % (f,) for f in filters])) return xml - nova_vpn_filter = ''' - 2086015e-cf03-11df-8c5d-080027c27973 - - - - - ''' - def nova_base_ipv4_filter(self): retval = "" for protocol in ['tcp', 'udp', 'icmp']: -- cgit From 891eb82afacc10795e4ac05a0c8f817645db85c2 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Fri, 22 Apr 2011 01:26:59 +0900 Subject: Utility method reworked, etc. --- nova/auth/authutils.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ nova/auth/manager.py | 4 +++- nova/tests/test_auth.py | 24 ++++++++++++++++++++++++ nova/utils.py | 24 ------------------------ 4 files changed, 75 insertions(+), 25 deletions(-) create mode 100644 nova/auth/authutils.py diff --git a/nova/auth/authutils.py b/nova/auth/authutils.py new file mode 100644 index 000000000..429e86ef9 --- /dev/null +++ b/nova/auth/authutils.py @@ -0,0 +1,48 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 NTT DATA CORPORATION. +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Auth module specific utilities and helper functions. +""" + +import netaddr +import string + + +def get_host_only_server_string(server_str): + """ + Returns host part only of the given server_string if it's a combination + of host part and port. Otherwise, return null string. + """ + + # First of all, exclude pure IPv6 address (w/o port). + if netaddr.valid_ipv6(server_str): + return '' + + # Next, check if this is IPv6 address with port number combination. + if server_str.find("]:") != -1: + [address, sep, port] = server_str.replace('[', '', 1).partition(']:') + return address + + # Third, check if this is a combination of general address and port + if server_str.find(':') == -1: + return '' + + # This must be a combination of host part and port + (address, port) = server_str.split(':') + return address diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 06def220a..775b38af1 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -35,6 +35,7 @@ from nova import flags from nova import log as logging from nova import utils from nova.auth import signer +from nova.auth import authutils FLAGS = flags.FLAGS @@ -315,7 +316,8 @@ class AuthManager(object): LOG.debug(_('expected_signature: %s'), expected_signature) LOG.debug(_('signature: %s'), signature) if signature != expected_signature: - host_only = utils.get_host_only_server_string(server_string) + host_only = authutils.get_host_only_server_string( + server_string) # If the given server_string contains port num, try without it. if host_only != '': host_only_signature = signer.Signer( diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index f8a1b1564..3886e9e6b 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -25,6 +25,7 @@ from nova import log as logging from nova import test from nova.auth import manager from nova.api.ec2 import cloud +from nova.auth import authutils FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.auth_unittest') @@ -339,6 +340,29 @@ class AuthManagerDbTestCase(_AuthManagerBaseTestCase): auth_driver = 'nova.auth.dbdriver.DbDriver' +class AuthManagerUtilTestCase(test.TestCase): + def test_get_host_only_server_string(self): + result = authutils.get_host_only_server_string('::1') + self.assertEqual('', result) + result = authutils.get_host_only_server_string('[::1]:8773') + self.assertEqual('::1', result) + result = authutils.get_host_only_server_string('2001:db8::192.168.1.1') + self.assertEqual('', result) + result = authutils.get_host_only_server_string( + '[2001:db8::192.168.1.1]:8773') + self.assertEqual('2001:db8::192.168.1.1', result) + result = authutils.get_host_only_server_string('192.168.1.1') + self.assertEqual('', result) + result = authutils.get_host_only_server_string('192.168.1.2:8773') + self.assertEqual('192.168.1.2', result) + result = authutils.get_host_only_server_string('192.168.1.3') + self.assertEqual('', result) + result = authutils.get_host_only_server_string('www.example.com:8443') + self.assertEqual('www.example.com', result) + result = authutils.get_host_only_server_string('www.example.com') + self.assertEqual('', result) + + if __name__ == "__main__": # TODO: Implement use_fake as an option unittest.main() diff --git a/nova/utils.py b/nova/utils.py index 5060b1ef6..76cba1a08 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -714,27 +714,3 @@ def check_isinstance(obj, cls): raise Exception(_("Expected object of type: %s") % (str(cls))) # TODO(justinsb): Can we make this better?? return cls() # Ugly PyLint hack - - -def get_host_only_server_string(server_str): - """ - Returns host part only of the given server_string if it's a combination - of host part and port. Otherwise, return null string. - """ - - # First of all, exclude pure IPv6 address (w/o port). - if netaddr.valid_ipv6(server_str): - return '' - - # Next, check if this is IPv6 address with port number combination. - if server_str.find("]:") != -1: - [address, sep, port] = server_str.replace('[', '', 1).partition(']:') - return address - - # Third, check if this is a combination of general address and port - if server_str.find(':') == -1: - return '' - - # This must be a combination of host part and port - (address, port) = server_str.split(':') - return address -- cgit From 9cb5c0113d67a306a6c85ed6f6fd7f353cc95c7c Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Apr 2011 14:12:54 -0400 Subject: Modified instance status for shutdown power state in OS api --- Authors | 1 + nova/api/openstack/views/servers.py | 2 +- nova/tests/api/openstack/test_servers.py | 20 ++++++++++++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Authors b/Authors index ce280749d..d3ba23fb9 100644 --- a/Authors +++ b/Authors @@ -1,3 +1,4 @@ +Alex Meade Andy Smith Andy Southgate Anne Gentle diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index e52bfaea3..f2d8d5720 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -63,7 +63,7 @@ class ViewBuilder(object): power_state.BLOCKED: 'ACTIVE', power_state.SUSPENDED: 'SUSPENDED', power_state.PAUSED: 'PAUSED', - power_state.SHUTDOWN: 'ACTIVE', + power_state.SHUTDOWN: 'INACTIVE', power_state.SHUTOFF: 'ACTIVE', power_state.CRASHED: 'ERROR', power_state.FAILED: 'ERROR'} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 556046e9d..c34392764 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -33,6 +33,7 @@ import nova.api.openstack from nova.api.openstack import servers import nova.compute.api from nova.compute import instance_types +from nova.compute import power_state import nova.db.api from nova.db.sqlalchemy.models import Instance from nova.db.sqlalchemy.models import InstanceMetadata @@ -56,6 +57,12 @@ def return_server_with_addresses(private, public): return _return_server +def return_server_with_power_state(power_state): + def _return_server(context, id): + return stub_instance(id, power_state=power_state) + return _return_server + + def return_servers(context, user_id=1): return [stub_instance(i, user_id) for i in xrange(5)] @@ -73,7 +80,7 @@ def instance_address(context, instance_id): def stub_instance(id, user_id=1, private_address=None, public_addresses=None, - host=None): + host=None, power_state=0): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -96,7 +103,7 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None, "launch_index": 0, "key_name": "", "key_data": "", - "state": 0, + "state": power_state, "state_description": "", "memory_mb": 0, "vcpus": 0, @@ -1155,6 +1162,15 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_shutdown_status(self): + new_return_server = return_server_with_power_state(power_state.SHUTDOWN) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['status'], 'INACTIVE') + class TestServerCreateRequestXMLDeserializer(unittest.TestCase): -- cgit From 5904cba617038600f3d8e7f65c71485abb163927 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 21 Apr 2011 22:23:40 +0400 Subject: style cleaning --- .../migrate_repo/versions/015_add_auto_assign_to_floating_ips.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py index 64d6df77f..29b26b3dd 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -31,7 +31,9 @@ def upgrade(migrate_engine): # bind migrate_engine to your metadata meta.bind = migrate_engine - floating_ips = Table('floating_ips', meta, autoload=True, - autoload_with=migrate_engine) + floating_ips = Table('floating_ips', + meta, + autoload=True, + autoload_with=migrate_engine) floating_ips.create_column(c_auto_assigned) -- cgit From 14e7200272e70ead7fe973e3cf2b20811ccf8377 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 21 Apr 2011 12:09:36 -0700 Subject: updated image builder and tests for OS API 1.1 compatibility (serverRef) --- nova/api/openstack/views/images.py | 4 +++- nova/tests/api/openstack/test_images.py | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 9dec8a355..ab9a9d294 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -87,7 +87,6 @@ class ViewBuilderV10(ViewBuilder): """OpenStack API v1.0 Image Builder""" pass - class ViewBuilderV11(ViewBuilder): """OpenStack API v1.1 Image Builder""" @@ -95,6 +94,9 @@ class ViewBuilderV11(ViewBuilder): """Return a standardized image structure for display by the API.""" image = ViewBuilder.build(self, image_obj, detail) href = self.generate_href(image_obj["id"]) + if "serverId" in image: + image["serverRef"] = image["serverId"] + del image["serverId"] image["links"] = [{ "rel": "self", diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index ae86d0686..f73aff6e2 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -538,7 +538,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, { 'id': 127, - 'name': 'killed backup', 'serverId': 42, + 'name': 'killed backup', + 'serverId': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', @@ -584,7 +585,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 124, 'name': 'queued backup', - 'serverId': 42, + 'serverRef': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'QUEUED', @@ -606,7 +607,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 125, 'name': 'saving backup', - 'serverId': 42, + 'serverRef': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'SAVING', @@ -629,7 +630,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 126, 'name': 'active backup', - 'serverId': 42, + 'serverRef': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', @@ -650,7 +651,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, { 'id': 127, - 'name': 'killed backup', 'serverId': 42, + 'name': 'killed backup', + 'serverRef': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', -- cgit From 8681db3aa9104f97a84a3323b102ed10af269888 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 21 Apr 2011 15:50:04 -0400 Subject: Addressing exception.NotFound across the project --- nova/api/ec2/__init__.py | 4 +- nova/api/ec2/cloud.py | 11 +- nova/api/ec2/ec2utils.py | 5 +- nova/api/openstack/common.py | 2 +- nova/api/openstack/servers.py | 6 +- nova/auth/dbdriver.py | 16 +- nova/auth/ldapdriver.py | 48 ++--- nova/auth/manager.py | 14 +- nova/compute/api.py | 8 +- nova/compute/manager.py | 5 +- nova/console/vmrc.py | 6 +- nova/db/sqlalchemy/api.py | 131 +++++++------- nova/exception.py | 291 +++++++++++++++++++++++++++++-- nova/image/fake.py | 10 +- nova/image/glance.py | 16 +- nova/image/local.py | 14 +- nova/network/vmwareapi_net.py | 7 +- nova/tests/api/openstack/test_flavors.py | 4 +- nova/tests/test_scheduler.py | 20 +-- nova/tests/test_volume.py | 2 +- nova/utils.py | 2 +- nova/virt/fake.py | 3 +- nova/virt/hyperv.py | 10 +- nova/virt/libvirt_conn.py | 5 +- nova/virt/vmwareapi/fake.py | 9 +- nova/virt/vmwareapi/vmops.py | 27 +-- nova/virt/xenapi/vm_utils.py | 6 +- nova/virt/xenapi/vmops.py | 11 +- nova/virt/xenapi/volumeops.py | 6 +- 29 files changed, 441 insertions(+), 258 deletions(-) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index a3c3b25a1..e18e7f05e 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -322,9 +322,7 @@ class Executor(wsgi.Application): except exception.InstanceNotFound as ex: LOG.info(_('InstanceNotFound raised: %s'), unicode(ex), context=context) - ec2_id = ec2utils.id_to_ec2_id(ex.instance_id) - message = _('Instance %s not found') % ec2_id - return self._error(req, context, type(ex).__name__, message) + return self._error(req, context, type(ex).__name__, ex.message) except exception.VolumeNotFound as ex: LOG.info(_('VolumeNotFound raised: %s'), unicode(ex), context=context) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index bd4c9dcd4..abd668ca4 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -907,11 +907,11 @@ class CloudController(object): try: internal_id = ec2utils.ec2_id_to_id(ec2_id) return self.image_service.show(context, internal_id) - except exception.NotFound: + except ValueError: try: return self.image_service.show_by_name(context, ec2_id) except exception.NotFound: - raise exception.NotFound(_('Image %s not found') % ec2_id) + raise exception.ImageNotFound(image_id=ec2_id) def _format_image(self, image): """Convert from format defined by BaseImageService to S3 format.""" @@ -955,8 +955,7 @@ class CloudController(object): try: image = self._get_image(context, ec2_id) except exception.NotFound: - raise exception.NotFound(_('Image %s not found') % - ec2_id) + raise exception.ImageNotFound(image_id=ec2_id) images.append(image) else: images = self.image_service.detail(context) @@ -990,7 +989,7 @@ class CloudController(object): try: image = self._get_image(context, image_id) except exception.NotFound: - raise exception.NotFound(_('Image %s not found') % image_id) + raise exception.ImageNotFound(image_id=image_id) result = {'imageId': image_id, 'launchPermission': []} if image['is_public']: result['launchPermission'].append({'group': 'all'}) @@ -1013,7 +1012,7 @@ class CloudController(object): try: image = self._get_image(context, image_id) except exception.NotFound: - raise exception.NotFound(_('Image %s not found') % image_id) + raise exception.ImageNotFound(image_id=image_id) internal_id = image['id'] del(image['id']) diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index 3b34f6ea5..1ac48163c 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -21,10 +21,7 @@ from nova import exception def ec2_id_to_id(ec2_id): """Convert an ec2 ID (i-[base 16 number]) to an instance id (int)""" - try: - return int(ec2_id.split('-')[-1], 16) - except ValueError: - raise exception.NotFound(_("Id %s Not Found") % ec2_id) + return int(ec2_id.split('-')[-1], 16) def id_to_ec2_id(instance_id, template='i-%08x'): diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 0b6dc944a..65ed1e143 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -124,7 +124,7 @@ def get_image_id_from_image_hash(image_service, context, image_hash): "should have numerical format") % image_id LOG.error(msg) raise Exception(msg) - raise exception.NotFound(image_hash) + raise exception.ImageNotFound(image_id=image_hash) def get_id_from_href(href): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 2ccc6d1db..763b021a8 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -570,14 +570,12 @@ class Controller(common.OpenstackController): try: kernel_id = image_meta['properties']['kernel_id'] except KeyError: - raise exception.NotFound( - _("Kernel not found for image %(image_id)s") % locals()) + raise exception.KernelNotFoundForImage(image_id=image_id) try: ramdisk_id = image_meta['properties']['ramdisk_id'] except KeyError: - raise exception.NotFound( - _("Ramdisk not found for image %(image_id)s") % locals()) + raise exception.RamdiskNotFoundForImage(image_id=image_id) return kernel_id, ramdisk_id diff --git a/nova/auth/dbdriver.py b/nova/auth/dbdriver.py index b2c580d83..813cba36d 100644 --- a/nova/auth/dbdriver.py +++ b/nova/auth/dbdriver.py @@ -103,9 +103,7 @@ class DbDriver(object): """Create a project""" manager = db.user_get(context.get_admin_context(), manager_uid) if not manager: - raise exception.NotFound(_("Project can't be created because " - "manager %s doesn't exist") - % manager_uid) + raise exception.UserNotFound(user_id=manager_uid) # description is a required attribute if description is None: @@ -119,9 +117,7 @@ class DbDriver(object): for member_uid in member_uids: member = db.user_get(context.get_admin_context(), member_uid) if not member: - raise exception.NotFound(_("Project can't be created " - "because user %s doesn't exist") - % member_uid) + raise exception.UserNotFound(user_id=member_uid) members.add(member) values = {'id': name, @@ -154,9 +150,7 @@ class DbDriver(object): if manager_uid: manager = db.user_get(context.get_admin_context(), manager_uid) if not manager: - raise exception.NotFound(_("Project can't be modified because " - "manager %s doesn't exist") % - manager_uid) + raise exception.UserNotFound(user_id=manager_uid) values['project_manager'] = manager['id'] if description: values['description'] = description @@ -244,8 +238,8 @@ class DbDriver(object): def _validate_user_and_project(self, user_id, project_id): user = db.user_get(context.get_admin_context(), user_id) if not user: - raise exception.NotFound(_('User "%s" not found') % user_id) + raise exception.UserNotFound(user_id=user_id) project = db.project_get(context.get_admin_context(), project_id) if not project: - raise exception.NotFound(_('Project "%s" not found') % project_id) + raise exception.ProjectNotFound(project_id=project_id) return user, project diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index fcac55510..093462b93 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -202,8 +202,7 @@ class LdapDriver(object): self.conn.modify_s(self.__uid_to_dn(name), attr) return self.get_user(name) else: - raise exception.NotFound(_("LDAP object for %s doesn't exist") - % name) + raise exception.LDAPUserNotFound(user_id=name) else: attr = [ ('objectclass', ['person', @@ -229,9 +228,7 @@ class LdapDriver(object): raise exception.Duplicate(_("Project can't be created because " "project %s already exists") % name) if not self.__user_exists(manager_uid): - raise exception.NotFound(_("Project can't be created because " - "manager %s doesn't exist") - % manager_uid) + raise exception.LDAPUserNotFound(user_id=manager_uid) manager_dn = self.__uid_to_dn(manager_uid) # description is a required attribute if description is None: @@ -240,9 +237,7 @@ class LdapDriver(object): if member_uids is not None: for member_uid in member_uids: if not self.__user_exists(member_uid): - raise exception.NotFound(_("Project can't be created " - "because user %s doesn't exist") - % member_uid) + raise exception.LDAPUserNotFound(user_id=member_uid) members.append(self.__uid_to_dn(member_uid)) # always add the manager as a member because members is required if not manager_dn in members: @@ -265,9 +260,7 @@ class LdapDriver(object): attr = [] if manager_uid: if not self.__user_exists(manager_uid): - raise exception.NotFound(_("Project can't be modified because " - "manager %s doesn't exist") - % manager_uid) + raise exception.LDAPUserNotFound(user_id=manager_uid) manager_dn = self.__uid_to_dn(manager_uid) attr.append((self.ldap.MOD_REPLACE, LdapDriver.project_attribute, manager_dn)) @@ -347,7 +340,7 @@ class LdapDriver(object): def delete_user(self, uid): """Delete a user""" if not self.__user_exists(uid): - raise exception.NotFound(_("User %s doesn't exist") % uid) + raise exception.LDAPUserNotFound(user_id=uid) self.__remove_from_all(uid) if FLAGS.ldap_user_modify_only: # Delete attributes @@ -477,9 +470,7 @@ class LdapDriver(object): if member_uids is not None: for member_uid in member_uids: if not self.__user_exists(member_uid): - raise exception.NotFound(_("Group can't be created " - "because user %s doesn't exist") - % member_uid) + raise exception.LDAPUserNotFound(user_id=member_uid) members.append(self.__uid_to_dn(member_uid)) dn = self.__uid_to_dn(uid) if not dn in members: @@ -494,8 +485,7 @@ class LdapDriver(object): def __is_in_group(self, uid, group_dn): """Check if user is in group""" if not self.__user_exists(uid): - raise exception.NotFound(_("User %s can't be searched in group " - "because the user doesn't exist") % uid) + raise exception.LDAPUserNotFound(user_id=uid) if not self.__group_exists(group_dn): return False res = self.__find_object(group_dn, @@ -506,11 +496,9 @@ class LdapDriver(object): def __add_to_group(self, uid, group_dn): """Add user to group""" if not self.__user_exists(uid): - raise exception.NotFound(_("User %s can't be added to the group " - "because the user doesn't exist") % uid) + raise exception.LDAPUserNotFound(user_id=uid) if not self.__group_exists(group_dn): - raise exception.NotFound(_("The group at dn %s doesn't exist") % - group_dn) + raise exception.LDAPGroupNotFound(group_id=group_dn) if self.__is_in_group(uid, group_dn): raise exception.Duplicate(_("User %(uid)s is already a member of " "the group %(group_dn)s") % locals()) @@ -520,15 +508,12 @@ class LdapDriver(object): def __remove_from_group(self, uid, group_dn): """Remove user from group""" if not self.__group_exists(group_dn): - raise exception.NotFound(_("The group at dn %s doesn't exist") - % group_dn) + raise exception.LDAPGroupNotFound(group_id=group_dn) if not self.__user_exists(uid): - raise exception.NotFound(_("User %s can't be removed from the " - "group because the user doesn't exist") - % uid) + raise exception.LDAPUserNotFound(user_id=uid) if not self.__is_in_group(uid, group_dn): - raise exception.NotFound(_("User %s is not a member of the group") - % uid) + raise exception.LDAPGroupMembershipNotFound(user_id=uid, + group_id=group_dn) # NOTE(vish): remove user from group and any sub_groups sub_dns = self.__find_group_dns_with_member(group_dn, uid) for sub_dn in sub_dns: @@ -548,9 +533,7 @@ class LdapDriver(object): def __remove_from_all(self, uid): """Remove user from all roles and projects""" if not self.__user_exists(uid): - raise exception.NotFound(_("User %s can't be removed from all " - "because the user doesn't exist") - % uid) + raise exception.LDAPUserNotFound(user_id=uid) role_dns = self.__find_group_dns_with_member( FLAGS.role_project_subtree, uid) for role_dn in role_dns: @@ -563,8 +546,7 @@ class LdapDriver(object): def __delete_group(self, group_dn): """Delete Group""" if not self.__group_exists(group_dn): - raise exception.NotFound(_("Group at dn %s doesn't exist") - % group_dn) + raise exception.LDAPGroupNotFound(group_id=group_dn) self.conn.delete_s(group_dn) def __delete_roles(self, project_dn): diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 8479c95a4..b719a0dbd 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -270,8 +270,7 @@ class AuthManager(object): LOG.debug('user: %r', user) if user is None: LOG.audit(_("Failed authorization for access key %s"), access_key) - raise exception.NotFound(_('No user found for access key %s') - % access_key) + raise exception.AccessKeyNotFound(access_key=access_key) # NOTE(vish): if we stop using project name as id we need better # logic to find a default project for user @@ -285,8 +284,7 @@ class AuthManager(object): uname = user.name LOG.audit(_("failed authorization: no project named %(pjid)s" " (user=%(uname)s)") % locals()) - raise exception.NotFound(_('No project called %s could be found') - % project_id) + raise exception.ProjectNotFound(project_id=project_id) if not self.is_admin(user) and not self.is_project_member(user, project): uname = user.name @@ -295,8 +293,8 @@ class AuthManager(object): pjid = project.id LOG.audit(_("Failed authorization: user %(uname)s not admin" " and not member of project %(pjname)s") % locals()) - raise exception.NotFound(_('User %(uid)s is not a member of' - ' project %(pjid)s') % locals()) + raise exception.ProjectMembershipNotFound(project_id=pjid, + user_id=uid) if check_type == 's3': sign = signer.Signer(user.secret.encode()) expected_signature = sign.s3_authorization(headers, verb, path) @@ -420,9 +418,9 @@ class AuthManager(object): @param project: Project in which to add local role. """ if role not in FLAGS.allowed_roles: - raise exception.NotFound(_("The %s role can not be found") % role) + raise exception.UserRoleNotFound(role_id=role) if project is not None and role in FLAGS.global_roles: - raise exception.NotFound(_("The %s role is global only") % role) + raise exception.GlobalRoleNotAllowed(role_id=role) uid = User.safe_id(user) pid = Project.safe_id(project) if project: diff --git a/nova/compute/api.py b/nova/compute/api.py index 264961fe3..c85f0f53a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -507,8 +507,8 @@ class API(base.Base): migration_ref = self.db.migration_get_by_instance_and_status(context, instance_id, 'finished') if not migration_ref: - raise exception.NotFound(_("No finished migrations found for " - "instance")) + raise exception.MigrationNotFoundByStatus(instance_id=instance_id, + status='finished') params = {'migration_id': migration_ref['id']} self._cast_compute_message('revert_resize', context, instance_id, @@ -522,8 +522,8 @@ class API(base.Base): migration_ref = self.db.migration_get_by_instance_and_status(context, instance_id, 'finished') if not migration_ref: - raise exception.NotFound(_("No finished migrations found for " - "instance")) + raise exception.MigrationNotFoundByStatus(instance_id=instance_id, + status='finished') instance_ref = self.db.instance_get(context, instance_id) params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, instance_id, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c795d72ad..9272df3da 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -822,7 +822,7 @@ class ComputeManager(manager.SchedulerDependentManager): tmp_file = os.path.join(FLAGS.instances_path, filename) if not os.path.exists(tmp_file): - raise exception.NotFound(_('%s not found') % tmp_file) + raise exception.FileNotFound(file_path=tmp_file) @exception.wrap_exception def cleanup_shared_storage_test_file(self, context, filename): @@ -865,8 +865,7 @@ class ComputeManager(manager.SchedulerDependentManager): # Getting fixed ips fixed_ip = self.db.instance_get_fixed_address(context, instance_id) if not fixed_ip: - msg = _("%(instance_id)s(%(ec2_id)s) does not have fixed_ip.") - raise exception.NotFound(msg % locals()) + raise exception.NoFixedIpsFoundForInstance(instance_id=instance_id) # If any volume is mounted, prepare here. if not instance_ref['volumes']: diff --git a/nova/console/vmrc.py b/nova/console/vmrc.py index 521da289f..b62de347d 100644 --- a/nova/console/vmrc.py +++ b/nova/console/vmrc.py @@ -92,8 +92,7 @@ class VMRCConsole(object): vm_ds_path_name = ds_path_name break if vm_ref is None: - raise exception.NotFound(_("instance - %s not present") % - instance_name) + raise exception.InstanceNotFound(instance_id=instance_name) json_data = json.dumps({"vm_id": vm_ds_path_name, "username": username, "password": password}) @@ -127,8 +126,7 @@ class VMRCSessionConsole(VMRCConsole): if vm.propSet[0].val == instance_name: vm_ref = vm.obj if vm_ref is None: - raise exception.NotFound(_("instance - %s not present") % - instance_name) + raise exception.InstanceNotFound(instance_id=instance_name) virtual_machine_ticket = \ vim_session._call_method( vim_session._get_vim(), diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index cd6052506..0e5433490 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -137,7 +137,7 @@ def service_get(context, service_id, session=None): first() if not result: - raise exception.NotFound(_('No service for id %s') % service_id) + raise exception.ServiceNotFound(service_id=service_id) return result @@ -196,8 +196,7 @@ def service_get_all_compute_by_host(context, host): all() if not result: - raise exception.NotFound(_("%s does not exist or is not " - "a compute node.") % host) + raise exception.ComputeHostNotFound(host=host) return result @@ -284,8 +283,7 @@ def service_get_by_args(context, host, binary): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.NotFound(_('No service for %(host)s, %(binary)s') - % locals()) + raise exception.HostBinaryNotFound(host=host, binary=binary) return result @@ -323,7 +321,7 @@ def compute_node_get(context, compute_id, session=None): first() if not result: - raise exception.NotFound(_('No computeNode for id %s') % compute_id) + raise exception.ComputeHostNotFound(host=compute_id) return result @@ -359,7 +357,7 @@ def certificate_get(context, certificate_id, session=None): first() if not result: - raise exception.NotFound('No certificate for id %s' % certificate_id) + raise exception.CertificateNotFound(certificate_id=certificate_id) return result @@ -564,7 +562,7 @@ def floating_ip_get_by_address(context, address, session=None): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.NotFound('No floating ip for address %s' % address) + raise exception.FloatingIpNotFound(fixed_ip=address) return result @@ -672,7 +670,7 @@ def fixed_ip_get_all(context, session=None): session = get_session() result = session.query(models.FixedIp).all() if not result: - raise exception.NotFound(_('No fixed ips defined')) + raise exception.NoFloatingIpsDefined() return result @@ -688,7 +686,7 @@ def fixed_ip_get_all_by_host(context, host=None): all() if not result: - raise exception.NotFound(_('No fixed ips for this host defined')) + raise exception.NoFloatingIpsDefinedForHost(host=host) return result @@ -704,7 +702,7 @@ def fixed_ip_get_by_address(context, address, session=None): options(joinedload('instance')).\ first() if not result: - raise exception.NotFound(_('No floating ip for address %s') % address) + raise exception.FloatingIpNotFound(fixed_ip=address) if is_user_context(context): authorize_project_context(context, result.instance.project_id) @@ -725,7 +723,7 @@ def fixed_ip_get_all_by_instance(context, instance_id): filter_by(instance_id=instance_id).\ filter_by(deleted=False) if not rv: - raise exception.NotFound(_('No address for instance %s') % instance_id) + raise exception.NoFloatingIpsFoundForInstance(instance_id=instance_id) return rv @@ -848,9 +846,7 @@ def instance_get(context, instance_id, session=None): filter_by(deleted=False).\ first() if not result: - raise exception.InstanceNotFound(_('Instance %s not found') - % instance_id, - instance_id) + raise exception.InstanceNotFound(instance_id=instance_id) return result @@ -1126,8 +1122,7 @@ def key_pair_get(context, user_id, name, session=None): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.NotFound(_('no keypair for user %(user_id)s,' - ' name %(name)s') % locals()) + raise exception.KeypairNotFound(user_id=user_id, name=name) return result @@ -1253,7 +1248,7 @@ def network_get(context, network_id, session=None): filter_by(deleted=False).\ first() if not result: - raise exception.NotFound(_('No network for id %s') % network_id) + raise exception.NetworkNotFound(network_id=network_id) return result @@ -1263,7 +1258,7 @@ def network_get_all(context): session = get_session() result = session.query(models.Network) if not result: - raise exception.NotFound(_('No networks defined')) + raise exception.NoNetworksFound() return result @@ -1292,7 +1287,7 @@ def network_get_by_bridge(context, bridge): first() if not result: - raise exception.NotFound(_('No network for bridge %s') % bridge) + raise exception.NetworkNotFoundForBridge(bridge=bridge) return result @@ -1303,8 +1298,7 @@ def network_get_by_cidr(context, cidr): filter_by(cidr=cidr).first() if not result: - raise exception.NotFound(_('Network with cidr %s does not exist') % - cidr) + raise exception.NetworkNotFoundForCidr(cidr=cidr) return result @@ -1318,7 +1312,7 @@ def network_get_by_instance(_context, instance_id): filter_by(deleted=False).\ first() if not rv: - raise exception.NotFound(_('No network for instance %s') % instance_id) + raise exception.NetworkNotFoundForInstance(instance_id=instance_id) return rv @@ -1331,7 +1325,7 @@ def network_get_all_by_instance(_context, instance_id): filter_by(instance_id=instance_id).\ filter_by(deleted=False) if not rv: - raise exception.NotFound(_('No network for instance %s') % instance_id) + raise exception.NetworkNotFoundForInstance(instance_id=instance_id) return rv @@ -1345,7 +1339,7 @@ def network_set_host(context, network_id, host_id): with_lockmode('update').\ first() if not network_ref: - raise exception.NotFound(_('No network for id %s') % network_id) + raise exception.NetworkNotFound(network_id=network_id) # NOTE(vish): if with_lockmode isn't supported, as in sqlite, # then this has concurrency issues @@ -1470,7 +1464,7 @@ def auth_token_get(context, token_hash, session=None): filter_by(deleted=can_read_deleted(context)).\ first() if not tk: - raise exception.NotFound(_('Token %s does not exist') % token_hash) + raise exception.AuthTokenNotFound(token=token_hash) return tk @@ -1504,7 +1498,7 @@ def quota_get(context, project_id, session=None): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.NotFound(_('No quota for project_id %s') % project_id) + raise exception.ProjectQuotaNotFound(project_id=project_id) return result @@ -1659,8 +1653,7 @@ def volume_get(context, volume_id, session=None): filter_by(deleted=False).\ first() if not result: - raise exception.VolumeNotFound(_('Volume %s not found') % volume_id, - volume_id) + raise exception.VolumeNotFound(volume_id=volume_id) return result @@ -1692,7 +1685,7 @@ def volume_get_all_by_instance(context, instance_id): filter_by(deleted=False).\ all() if not result: - raise exception.NotFound(_('No volume for instance %s') % instance_id) + raise exception.VolumeNotFoundForInstance(instance_id=instance_id) return result @@ -1717,8 +1710,7 @@ def volume_get_instance(context, volume_id): options(joinedload('instance')).\ first() if not result: - raise exception.VolumeNotFound(_('Volume %s not found') % volume_id, - volume_id) + raise exception.VolumeNotFound(volume_id=volume_id) return result.instance @@ -1730,8 +1722,7 @@ def volume_get_shelf_and_blade(context, volume_id): filter_by(volume_id=volume_id).\ first() if not result: - raise exception.NotFound(_('No export device found for volume %s') % - volume_id) + raise exception.ExportDeviceNotFoundForVolume(volume_id=volume_id) return (result.shelf_id, result.blade_id) @@ -1743,8 +1734,7 @@ def volume_get_iscsi_target_num(context, volume_id): filter_by(volume_id=volume_id).\ first() if not result: - raise exception.NotFound(_('No target id found for volume %s') % - volume_id) + raise exception.ISCSITargetNotFoundForVolume(volume_id=volume_id) return result.target_num @@ -1788,8 +1778,8 @@ def security_group_get(context, security_group_id, session=None): options(joinedload_all('rules')).\ first() if not result: - raise exception.NotFound(_("No security group with id %s") % - security_group_id) + raise exception.SecurityGroupNotFound( + security_group_id=security_group_id) return result @@ -1804,9 +1794,8 @@ def security_group_get_by_name(context, project_id, group_name): options(joinedload_all('instances')).\ first() if not result: - raise exception.NotFound( - _('No security group named %(group_name)s' - ' for project: %(project_id)s') % locals()) + raise exception.SecurityGroupNotFoundForProject(project_id=project_id, + security_group_id=group_name) return result @@ -1907,8 +1896,8 @@ def security_group_rule_get(context, security_group_rule_id, session=None): filter_by(id=security_group_rule_id).\ first() if not result: - raise exception.NotFound(_("No secuity group rule with id %s") % - security_group_rule_id) + raise exception.SecurityGroupNotFoundForRule( + rule_id=security_group_rule_id) return result @@ -1981,7 +1970,7 @@ def user_get(context, id, session=None): first() if not result: - raise exception.NotFound(_('No user for id %s') % id) + raise exception.UserNotFound(user_id=id) return result @@ -1997,7 +1986,7 @@ def user_get_by_access_key(context, access_key, session=None): first() if not result: - raise exception.NotFound(_('No user for access key %s') % access_key) + raise exception.AccessKeyNotFound(access_key=access_key) return result @@ -2062,7 +2051,7 @@ def project_get(context, id, session=None): first() if not result: - raise exception.NotFound(_("No project with id %s") % id) + raise exception.ProjectNotFound(project_id=id) return result @@ -2083,7 +2072,7 @@ def project_get_by_user(context, user_id): options(joinedload_all('projects')).\ first() if not user: - raise exception.NotFound(_('Invalid user_id %s') % user_id) + raise exception.UserNotFound(user_id=user_id) return user.projects @@ -2223,8 +2212,7 @@ def migration_get(context, id, session=None): result = session.query(models.Migration).\ filter_by(id=id).first() if not result: - raise exception.NotFound(_("No migration found with id %s") - % id) + raise exception.MigrationNotFound(migration_id=id) return result @@ -2235,8 +2223,8 @@ def migration_get_by_instance_and_status(context, instance_id, status): filter_by(instance_id=instance_id).\ filter_by(status=status).first() if not result: - raise exception.NotFound(_("No migration found for instance " - "%(instance_id)s with status %(status)s") % locals()) + raise exception.MigrationNotFoundByStatus(instance_id=instance_id, + status=status) return result @@ -2257,8 +2245,7 @@ def console_pool_get(context, pool_id): filter_by(id=pool_id).\ first() if not result: - raise exception.NotFound(_("No console pool with id %(pool_id)s") - % locals()) + raise exception.ConsolePoolNotFound(pool_id=pool_id) return result @@ -2274,9 +2261,9 @@ def console_pool_get_by_host_type(context, compute_host, host, options(joinedload('consoles')).\ first() if not result: - raise exception.NotFound(_('No console pool of type %(console_type)s ' - 'for compute host %(compute_host)s ' - 'on proxy host %(host)s') % locals()) + raise exception.ConsolePoolNotFoundForHostType(host=host, + console_type=console_type, + compute_host=compute_host ) return result @@ -2314,8 +2301,8 @@ def console_get_by_pool_instance(context, pool_id, instance_id): options(joinedload('pool')).\ first() if not result: - raise exception.NotFound(_('No console for instance %(instance_id)s ' - 'in pool %(pool_id)s') % locals()) + raise exception.ConsoleNotFoundInPoolForInstance(pool_id=pool_id, + instance_id=instance_id) return result @@ -2336,9 +2323,11 @@ def console_get(context, console_id, instance_id=None): query = query.filter_by(instance_id=instance_id) result = query.options(joinedload('pool')).first() if not result: - idesc = (_("on instance %s") % instance_id) if instance_id else "" - raise exception.NotFound(_("No console with id %(console_id)s" - " %(idesc)s") % locals()) + if instance_id: + raise exception.ConsoleNotFoundForInstance(console_id=console_id, + instance_id=instance_id) + else: + raise exception.ConsoleNotFound(console_id=console_id) return result @@ -2377,7 +2366,7 @@ def instance_type_get_all(context, inactive=False): inst_dict[i['name']] = dict(i) return inst_dict else: - raise exception.NotFound + raise exception.NoInstanceTypesFound() @require_context @@ -2388,7 +2377,7 @@ def instance_type_get_by_id(context, id): filter_by(id=id).\ first() if not inst_type: - raise exception.NotFound(_("No instance type with id %s") % id) + raise exception.InstanceTypeNotFound(instance_type=id) else: return dict(inst_type) @@ -2401,7 +2390,7 @@ def instance_type_get_by_name(context, name): filter_by(name=name).\ first() if not inst_type: - raise exception.NotFound(_("No instance type with name %s") % name) + raise exception.InstanceTypeNotFoundByName(instance_type_name=name) else: return dict(inst_type) @@ -2414,7 +2403,7 @@ def instance_type_get_by_flavor_id(context, id): filter_by(flavorid=int(id)).\ first() if not inst_type: - raise exception.NotFound(_("No flavor with flavorid %s") % id) + raise exception.FlavorNotFound(flavor_id=id) else: return dict(inst_type) @@ -2427,7 +2416,7 @@ def instance_type_destroy(context, name): filter_by(name=name) records = instance_type_ref.update(dict(deleted=True)) if records == 0: - raise exception.NotFound + raise exception.InstanceTypeNotFoundByName(instance_type_name=name) else: return instance_type_ref @@ -2442,7 +2431,7 @@ def instance_type_purge(context, name): filter_by(name=name) records = instance_type_ref.delete() if records == 0: - raise exception.NotFound + raise exception.InstanceTypeNotFoundByName(instance_type_name=name) else: return instance_type_ref @@ -2463,7 +2452,7 @@ def zone_update(context, zone_id, values): session = get_session() zone = session.query(models.Zone).filter_by(id=zone_id).first() if not zone: - raise exception.NotFound(_("No zone with id %(zone_id)s") % locals()) + raise exception.ZoneNotFound(zone_id=zone_id) zone.update(values) zone.save() return zone @@ -2483,7 +2472,7 @@ def zone_get(context, zone_id): session = get_session() result = session.query(models.Zone).filter_by(id=zone_id).first() if not result: - raise exception.NotFound(_("No zone with id %(zone_id)s") % locals()) + raise exception.ZoneNotFound(zone_id=zone_id) return result @@ -2533,8 +2522,8 @@ def instance_metadata_get_item(context, instance_id, key): first() if not meta_result: - raise exception.NotFound(_('Invalid metadata key for instance %s') % - instance_id) + raise exception.InstanceMetadataNotFound(metadata_key=key, + instance_id=instance_id) return meta_result diff --git a/nova/exception.py b/nova/exception.py index 6948a93c1..88f6bfcce 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -52,22 +52,6 @@ class ApiError(Error): super(ApiError, self).__init__('%s: %s' % (code, message)) -class NotFound(Error): - pass - - -class InstanceNotFound(NotFound): - def __init__(self, message, instance_id): - self.instance_id = instance_id - super(InstanceNotFound, self).__init__(message) - - -class VolumeNotFound(NotFound): - def __init__(self, message, volume_id): - self.volume_id = volume_id - super(VolumeNotFound, self).__init__(message) - - class Duplicate(Error): pass @@ -160,6 +144,10 @@ class InstanceNotSuspended(Invalid): message = _("Instance %(instance_id)s is not suspended.") +class InstanceNotInRescueMode(Invalid): + message = _("Instance %(instance_id)s is not in rescue mode") + + class InstanceSuspendFailure(Invalid): message = _("Failed to suspend instance") + ": %(reason)s" @@ -223,5 +211,276 @@ class InvalidVLANPortGroup(Invalid): "is %(actual)s.") +class InvalidDiskFormat(Invalid): + message = _("Disk format %(disk_format)s is not acceptable") + + class ImageUnacceptable(Invalid): message = _("Image %(image_id)s is unacceptable") + ": %(reason)s" + + +class InstanceUnacceptable(Invalid): + message = _("Instance %(instance_id)s is unacceptable") + ": %(reason)s" + + +class NotFound(NovaException): + message = _("Resource could not be found.") + + def __init__(self, *args, **kwargs): + super(NotFound, self).__init__(**kwargs) + + +class InstanceNotFound(NotFound): + message = _("Instance %(instance_id)s could not be found.") + + +class VolumeNotFound(NotFound): + message = _("Volume %(volume_id)s could not be found.") + + +class VolumeNotFoundForInstance(VolumeNotFound): + message = _("Volume not found for instance %(instance_id)s.") + + +class ExportDeviceNotFoundForVolume(NotFound): + message = _("No export device found for volume %(volume_id)s.") + + +class ISCSITargetNotFoundForVolume(NotFound): + message = _("No target id found for volume %(volume_id)s.") + + +class DiskNotFound(NotFound): + message = _("No disk at %(location)s") + + +class ImageNotFound(NotFound): + message = _("Image %(image_id)s could not be found.") + + +class KernelNotFoundForImage(ImageNotFound): + message = _("Kernel not found for image %(image_id)s.") + + +class RamdiskNotFoundForImage(ImageNotFound): + message = _("Ramdisk not found for image %(image_id)s.") + + +class UserNotFound(NotFound): + message = _("User %(user_id)s could not be found.") + + +class ProjectNotFound(NotFound): + message = _("Project %(project_id)s could not be found.") + + +class ProjectMembershipNotFound(NotFound): + message = _("User %(user_id)s is not a member of project %(project_id)s.") + + +class UserRoleNotFound(NotFound): + message = _("Role %(role_id)s could not be found.") + + +class StorageRepositoryNotFound(NotFound): + message = _("Cannot find SR to read/write VDI.") + + +class NetworkNotFound(NotFound): + message = _("Network %(network_id)s could not be found.") + + +class NetworkNotFoundForBridge(NetworkNotFound): + message = _("Network could not be found for bridge %(bridge)s") + + +class NetworkNotFoundForCidr(NetworkNotFound): + message = _("Network could not be found with cidr %(cidr)s.") + + +class NetworkNotFoundForInstance(NetworkNotFound): + message = _("Network could not be found for instance %(instance_id)s.") + + +class NoNetworksFound(NotFound): + message = _("No networks defined.") + + +class DatastoreNotFound(NotFound): + message = _("Could not find the datastore reference(s) which the VM uses.") + + +class NoFixedIpsFoundForInstance(NotFound): + message = _("Instance %(instance_id)s has zero fixed ips.") + + +class FloatingIpNotFound(NotFound): + message = _("Floating ip not found for fixed address %(fixed_ip)s.") + + +class NoFloatingIpsDefined(NotFound): + message = _("Zero floating ips could be found.") + + +class NoFloatingIpsDefinedForHost(NoFloatingIpsDefined): + message = _("Zero floating ips defined for host %(host)s.") + + +class NoFloatingIpsDefinedForInstance(NoFloatingIpsDefined): + message = _("Zero floating ips defined for instance %(instance_id)s.") + + +class KeypairNotFound(NotFound): + message = _("Keypair %(keypair_name)s not found for user %(user_id)s") + + +class CertificateNotFound(NotFound): + message = _("Certificate %(certificate_id)s not found.") + + +class ServiceNotFound(NotFound): + message = _("Service %(service_id)s could not be found.") + + +class HostNotFound(NotFound): + message = _("Host %(host)s could not be found.") + + +class ComputeHostNotFound(HostNotFound): + message = _("Compute host %(host)s could not be found.") + + +class HostBinaryNotFound(NotFound): + message = _("Could not find binary %(binary)s on host %(host)s.") + + +class AuthTokenNotFound(NotFound): + message = _("Auth token %(token)s could not be found.") + + +class AccessKeyNotFound(NotFound): + message = _("Access Key %(access_key)s could not be found.") + + +class QuotaNotFound(NotFound): + message = _("Quota could not be found") + + +class ProjectQuotaNotFound(QuotaNotFound): + message = _("Quota for project %(project_id)s could not be found.") + + +class SecurityGroupNotFound(NotFound): + message = _("Security group %(security_group_id)s not found.") + + +class SecurityGroupNotFoundForProject(SecurityGroupNotFound): + message = _("Security group %(security_group_id)s not found " + "for project %(project_id)s.") + + +class SecurityGroupNotFoundForRule(SecurityGroupNotFound): + message = _("Security group with rule %(rule_id)s not found.") + + +class MigrationNotFound(NotFound): + message = _("Migration %(migration_id)s could not be found.") + + +class MigrationNotFoundByStatus(MigrationNotFound): + message = _("Migration not found for instance %(instance_id)s " + "with status %(status)s.") + + +class ConsolePoolNotFound(NotFound): + message = _("Console pool %(pool_id)s could not be found.") + + +class ConsolePoolNotFoundForHostType(NotFound): + message = _("Console pool of type %(console_type)s " + "for compute host %(compute_host)s " + "on proxy host %(host)s not found.") + + +class ConsoleNotFound(NotFound): + message = _("Console %(console_id)s could not be found.") + + +class ConsoleNotFoundForInstance(ConsoleNotFound): + message = _("Console for instance %(instance_id)s could not be found.") + + +class ConsoleNotFoundInPoolForInstance(ConsoleNotFound): + message = _("Console for instance %(instance_id)s " + "in pool %(pool_id)s could not be found.") + + +class NoInstanceTypesFound(NotFound): + message = _("Zero instance types found.") + + +class InstanceTypeNotFound(NotFound): + message = _("Instance type %(instance_type_id)s could not be found.") + + +class InstanceTypeNotFoundByName(InstanceTypeNotFound): + message = _("Instance type with name %(instance_type_name)s " + "could not be found.") + + +class FlavorNotFound(NotFound): + message = _("Flavor %(flavor_id)s could not be found.") + + +class ZoneNotFound(NotFound): + message = _("Zone %(zone_id)s could not be found.") + + +class InstanceMetadataNotFound(NotFound): + message = _("Instance %(instance_id)s has no metadata with " + "key %(metadata_key)s.") + + +class LDAPObjectNotFound(NotFound): + message = _("LDAP object could not be found") + + +class LDAPUserNotFound(LDAPObjectNotFound): + message = _("LDAP user %(user_id)s could not be found.") + + +class LDAPGroupNotFound(LDAPObjectNotFound): + message = _("LDAP group %(group_id)s could not be found.") + + +class LDAPGroupMembershipNotFound(NotFound): + message = _("LDAP user %(user_id)s is not a member of group %(group_id)s.") + + +class FileNotFound(NotFound): + message = _("File %(file_path)s could not be found.") + + +class NoFilesFound(NotFound): + message = _("Zero files could be found.") + + +class SwitchNotFoundForNetworkAdapter(NotFound): + message = _("Virtual switch associated with the " + "network adapter %(adapter)s not found.") + + +class NetworkAdapterNotFound(NotFound): + message = _("Network adapter %(adapter)s could not be found.") + + +class ClassNotFound(NotFound): + message = _("Class %(class_name)s could not be found") + + +class NotAllowed(NovaException): + message = _("Action not allowed.") + + +class GlobalRoleNotAllowed(NotAllowed): + message = _("Unable to use global role %(role_id)s") diff --git a/nova/image/fake.py b/nova/image/fake.py index e02b4127e..c0b98d68f 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -71,7 +71,7 @@ class FakeImageService(service.BaseImageService): return copy.deepcopy(image) LOG.warn("Unable to find image id %s. Have images: %s", image_id, self.images) - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) def create(self, context, data): """Store the image data and return the new image id. @@ -88,24 +88,24 @@ class FakeImageService(service.BaseImageService): def update(self, context, image_id, data): """Replace the contents of the given image with the new data. - :raises NotFound if the image does not exist. + :raises ImageNotFound if the image does not exist. """ image_id = int(image_id) if not self.images.get(image_id): - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) self.images[image_id] = copy.deepcopy(data) def delete(self, context, image_id): """Delete the given image. - :raises NotFound if the image does not exist. + :raises ImageNotFound if the image does not exist. """ image_id = int(image_id) removed = self.images.pop(image_id, None) if not removed: - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) def delete_all(self): """Clears out all images.""" diff --git a/nova/image/glance.py b/nova/image/glance.py index 1a80bb2af..2f1716239 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -89,10 +89,10 @@ class GlanceImageService(service.BaseImageService): try: image_meta = self.client.get_image_meta(image_id) except glance_exception.NotFound: - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) if not self._is_image_available(context, image_meta): - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) base_image_meta = self._translate_to_base(image_meta) return base_image_meta @@ -107,7 +107,7 @@ class GlanceImageService(service.BaseImageService): for image_meta in image_metas: if name == image_meta.get('name'): return image_meta - raise exception.NotFound + raise exception.ImageNotFound(image_id=name) def get(self, context, image_id, data): """ @@ -116,7 +116,7 @@ class GlanceImageService(service.BaseImageService): try: image_meta, image_chunks = self.client.get_image(image_id) except glance_exception.NotFound: - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) for chunk in image_chunks: data.write(chunk) @@ -149,14 +149,14 @@ class GlanceImageService(service.BaseImageService): def update(self, context, image_id, image_meta, data=None): """Replace the contents of the given image with the new data. - :raises NotFound if the image does not exist. + :raises ImageNotFound if the image does not exist. """ # NOTE(vish): show is to check if image is available self.show(context, image_id) try: image_meta = self.client.update_image(image_id, image_meta, data) except glance_exception.NotFound: - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) base_image_meta = self._translate_to_base(image_meta) return base_image_meta @@ -165,14 +165,14 @@ class GlanceImageService(service.BaseImageService): """ Delete the given image. - :raises NotFound if the image does not exist. + :raises ImageNotFound if the image does not exist. """ # NOTE(vish): show is to check if image is available self.show(context, image_id) try: result = self.client.delete_image(image_id) except glance_exception.NotFound: - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) return result def delete_all(self): diff --git a/nova/image/local.py b/nova/image/local.py index fa5e93346..5d1ff6b3e 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -86,10 +86,10 @@ class LocalImageService(service.BaseImageService): with open(self._path_to(image_id)) as metadata_file: image_meta = json.load(metadata_file) if not self._is_image_available(context, image_meta): - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) return image_meta except (IOError, ValueError): - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) def show_by_name(self, context, name): """Returns a dict containing image data for the given name.""" @@ -102,7 +102,7 @@ class LocalImageService(service.BaseImageService): image = cantidate break if image is None: - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) return image def get(self, context, image_id, data): @@ -113,7 +113,7 @@ class LocalImageService(service.BaseImageService): with open(self._path_to(image_id, 'image')) as image_file: shutil.copyfileobj(image_file, data) except (IOError, ValueError): - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) return metadata def create(self, context, metadata, data=None): @@ -143,12 +143,12 @@ class LocalImageService(service.BaseImageService): with open(self._path_to(image_id), 'w') as metadata_file: json.dump(metadata, metadata_file) except (IOError, ValueError): - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) return metadata def delete(self, context, image_id): """Delete the given image. - Raises NotFound if the image does not exist. + Raises ImageNotFound if the image does not exist. """ # NOTE(vish): show is to check if image is available @@ -156,7 +156,7 @@ class LocalImageService(service.BaseImageService): try: shutil.rmtree(self._path_to(image_id, None)) except (IOError, ValueError): - raise exception.NotFound + raise exception.ImageNotFound(image_id=image_id) def delete_all(self): """Clears out all images in local directory.""" diff --git a/nova/network/vmwareapi_net.py b/nova/network/vmwareapi_net.py index 6e1ed480b..9b2db7b8f 100644 --- a/nova/network/vmwareapi_net.py +++ b/nova/network/vmwareapi_net.py @@ -52,16 +52,13 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): # Check if the vlan_interface physical network adapter exists on the host if not network_utils.check_if_vlan_interface_exists(session, vlan_interface): - raise exception.NotFound(_("There is no physical network adapter with " - "the name %s on the ESX host") % vlan_interface) + raise exception.NetworkAdapterNotFound(adapter=vlan_interface) # Get the vSwitch associated with the Physical Adapter vswitch_associated = network_utils.get_vswitch_for_vlan_interface( session, vlan_interface) if vswitch_associated is None: - raise exception.NotFound(_("There is no virtual switch associated " - "with the physical network adapter with name %s") % - vlan_interface) + raise exception.SwicthNotFoundForNetworkAdapter(adapter=vlan_interface) # Check whether bridge already exists and retrieve the the ref of the # network whose name_label is "bridge" network_ref = network_utils.get_network_with_the_name(session, bridge) diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index 954d72adf..d1c62e454 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -47,8 +47,8 @@ def return_instance_types(context, num=2): return instance_types -def return_instance_type_not_found(context, flavorid): - raise exception.NotFound() +def return_instance_type_not_found(context, flavor_id): + raise exception.InstanceTypeNotFound(flavor_id=flavor_id) class FlavorsTest(test.TestCase): diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 42ea19d6e..efd12f930 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -120,12 +120,11 @@ class SchedulerTestCase(test.TestCase): dest = 'dummydest' ctxt = context.get_admin_context() - try: - scheduler.show_host_resources(ctxt, dest) - except exception.NotFound, e: - c1 = (e.message.find(_("does not exist or is not a " - "compute node.")) >= 0) - self.assertTrue(c1) + self.assertRaises(exception.NotFound, scheduler.show_host_resources, + ctxt, dest) + #TODO(bcwaldon): reimplement this functionality + #c1 = (e.message.find(_("does not exist or is not a " + # "compute node.")) >= 0) def _dic_is_equal(self, dic1, dic2, keys=None): """Compares 2 dictionary contents(Helper method)""" @@ -941,7 +940,7 @@ class FakeRerouteCompute(api.reroute_compute): def go_boom(self, context, instance): - raise exception.InstanceNotFound("boom message", instance) + raise exception.InstanceNotFound(instance_id=instance) def found_instance(self, context, instance): @@ -990,11 +989,8 @@ class ZoneRedirectTest(test.TestCase): def test_routing_flags(self): FLAGS.enable_zone_routing = False decorator = FakeRerouteCompute("foo") - try: - result = decorator(go_boom)(None, None, 1) - self.assertFail(_("Should have thrown exception.")) - except exception.InstanceNotFound, e: - self.assertEquals(e.message, 'boom message') + self.assertRaises(exception.InstanceNotFound, decorator(go_boom), + None, None, 1) def test_get_collection_context_and_id(self): decorator = api.reroute_compute("foo") diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py index e9d8289aa..236d12434 100644 --- a/nova/tests/test_volume.py +++ b/nova/tests/test_volume.py @@ -142,7 +142,7 @@ class VolumeTestCase(test.TestCase): self.assertEqual(vol['status'], "available") self.volume.delete_volume(self.context, volume_id) - self.assertRaises(exception.Error, + self.assertRaises(exception.VolumeNotFound, db.volume_get, self.context, volume_id) diff --git a/nova/utils.py b/nova/utils.py index 76cba1a08..372b1df38 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -63,7 +63,7 @@ def import_class(import_str): return getattr(sys.modules[mod_str], class_str) except (ImportError, ValueError, AttributeError), exc: LOG.debug(_('Inner Exception: %s'), exc) - raise exception.NotFound(_('Class %s cannot be found') % class_str) + raise exception.ClassNotFound(class_name=class_str) def import_object(import_str): diff --git a/nova/virt/fake.py b/nova/virt/fake.py index c3d5230df..33f37b512 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -288,8 +288,7 @@ class FakeConnection(driver.ComputeDriver): knowledge of the instance """ if instance_name not in self.instances: - raise exception.NotFound(_("Instance %s Not Found") - % instance_name) + raise exception.InstanceNotFound(instance_id=instance_name) i = self.instances[instance_name] return {'state': i.state, 'max_mem': 0, diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 13f403a66..507ea5457 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -368,7 +368,7 @@ class HyperVConnection(driver.ComputeDriver): """Reboot the specified instance.""" vm = self._lookup(instance.name) if vm is None: - raise exception.NotFound('instance not present %s' % instance.name) + raise exception.InstanceNotFound(instance_id=instance.id) self._set_vm_state(instance.name, 'Reboot') def destroy(self, instance): @@ -412,7 +412,7 @@ class HyperVConnection(driver.ComputeDriver): """Get information about the VM""" vm = self._lookup(instance_id) if vm is None: - raise exception.NotFound('instance not present %s' % instance_id) + raise exception.InstanceNotFound(instance_id=instance_id) vm = self._conn.Msvm_ComputerSystem(ElementName=instance_id)[0] vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] vmsettings = vm.associators( @@ -474,14 +474,12 @@ class HyperVConnection(driver.ComputeDriver): def attach_volume(self, instance_name, device_path, mountpoint): vm = self._lookup(instance_name) if vm is None: - raise exception.NotFound('Cannot attach volume to missing %s vm' - % instance_name) + raise exception.InstanceNotFound(instance_id=instance_name) def detach_volume(self, instance_name, mountpoint): vm = self._lookup(instance_name) if vm is None: - raise exception.NotFound('Cannot detach volume from missing %s ' - % instance_name) + raise exception.InstanceNotFound(instance_id=instance_name) def poll_rescued_instances(self, timeout): pass diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 55e1d4295..705b6380c 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -449,7 +449,7 @@ class LibvirtConnection(driver.ComputeDriver): mount_device = mountpoint.rpartition("/")[2] xml = self._get_disk_xml(virt_dom.XMLDesc(0), mount_device) if not xml: - raise exception.NotFound(_("No disk at %s") % mount_device) + raise exception.DiskNotFound(location=mount_device) virt_dom.detachDevice(xml) @exception.wrap_exception @@ -1054,8 +1054,7 @@ class LibvirtConnection(driver.ComputeDriver): except libvirt.libvirtError as e: errcode = e.get_error_code() if errcode == libvirt.VIR_ERR_NO_DOMAIN: - raise exception.NotFound(_("Instance %s not found") - % instance_name) + raise exception.InstanceNotFound(instance_id=instance_name) LOG.warning(_("Error from libvirt during lookup. " "Code=%(errcode)s Error=%(e)s") % locals()) diff --git a/nova/virt/vmwareapi/fake.py b/nova/virt/vmwareapi/fake.py index 4bb467fa9..7370684bd 100644 --- a/nova/virt/vmwareapi/fake.py +++ b/nova/virt/vmwareapi/fake.py @@ -387,12 +387,11 @@ def _add_file(file_path): def _remove_file(file_path): """Removes a file reference from the db.""" if _db_content.get("files") is None: - raise exception.NotFound(_("No files have been added yet")) + raise exception.NoFilesFound() # Check if the remove is for a single file object or for a folder if file_path.find(".vmdk") != -1: if file_path not in _db_content.get("files"): - raise exception.NotFound(_("File- '%s' is not there in the " - "datastore") % file_path) + raise exception.FileNotFound(file_path=file_path) _db_content.get("files").remove(file_path) else: # Removes the files in the folder and the folder too from the db @@ -579,7 +578,7 @@ class FakeVim(object): """Searches the datastore for a file.""" ds_path = kwargs.get("datastorePath") if _db_content.get("files", None) is None: - raise exception.NotFound(_("No files have been added yet")) + raise exception.NoFilesFound() for file in _db_content.get("files"): if file.find(ds_path) != -1: task_mdo = create_task(method, "success") @@ -591,7 +590,7 @@ class FakeVim(object): """Creates a directory in the datastore.""" ds_path = kwargs.get("name") if _db_content.get("files", None) is None: - raise exception.NotFound(_("No files have been added yet")) + raise exception.NoFilesFound() _db_content["files"].append(ds_path) def _set_power_state(self, method, vm_ref, pwr_state="poweredOn"): diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index b700c438f..033a511f8 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -116,8 +116,7 @@ class VMWareVMOps(object): network_utils.get_network_with_the_name(self._session, net_name) if network_ref is None: - raise exception.NotFound(_("Network with the name '%s' doesn't" - " exist on the ESX host") % net_name) + raise exception.NetworkNotFoundForBridge(bridge=net_name) _check_if_network_bridge_exists() @@ -337,8 +336,7 @@ class VMWareVMOps(object): """ vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: - raise exception.NotFound(_("instance - %s not present") % - instance.name) + raise exception.InstanceNotFound(instance_id=instance.id) client_factory = self._session._get_vim().client.factory service_content = self._session._get_vim().get_service_content() @@ -388,8 +386,7 @@ class VMWareVMOps(object): "VirtualMachine", "datastore") if not ds_ref_ret: - raise exception.NotFound(_("Failed to get the datastore " - "reference(s) which the VM uses")) + raise exception.DatastoreNotFound() ds_ref = ds_ref_ret.ManagedObjectReference[0] ds_browser = vim_util.get_dynamic_property( self._session._get_vim(), @@ -480,8 +477,7 @@ class VMWareVMOps(object): """Reboot a VM instance.""" vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: - raise exception.NotFound(_("instance - %s not present") % - instance.name) + raise exception.InstanceNotFound(instance_id=instance.id) lst_properties = ["summary.guest.toolsStatus", "runtime.powerState", "summary.guest.toolsRunningStatus"] props = self._session._call_method(vim_util, "get_object_properties", @@ -605,8 +601,7 @@ class VMWareVMOps(object): """Suspend the specified instance.""" vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: - raise exception.NotFound(_("instance - %s not present") % - instance.name) + raise exception.InstanceNotFound(instance_id=instance.id) pwr_state = self._session._call_method(vim_util, "get_dynamic_property", vm_ref, @@ -630,8 +625,7 @@ class VMWareVMOps(object): """Resume the specified instance.""" vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: - raise exception.NotFound(_("instance - %s not present") % - instance.name) + raise exception.InstanceNotFound(instance_id=instance.id) pwr_state = self._session._call_method(vim_util, "get_dynamic_property", vm_ref, @@ -651,8 +645,7 @@ class VMWareVMOps(object): """Return data about the VM instance.""" vm_ref = self._get_vm_ref_from_the_name(instance_name) if vm_ref is None: - raise exception.NotFound(_("instance - %s not present") % - instance_name) + raise exception.InstanceNotFound(instance_id=instance_name) lst_properties = ["summary.config.numCpu", "summary.config.memorySizeMB", @@ -688,8 +681,7 @@ class VMWareVMOps(object): """Return snapshot of console.""" vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: - raise exception.NotFound(_("instance - %s not present") % - instance.name) + raise exception.InstanceNotFound(instance_id=instance.id) param_list = {"id": str(vm_ref)} base_url = "%s://%s/screen?%s" % (self._session._scheme, self._session._host_ip, @@ -717,8 +709,7 @@ class VMWareVMOps(object): """ vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: - raise exception.NotFound(_("instance - %s not present") % - instance.name) + raise exception.InstanceNotFound(instance_id=instance.id) network = db.network_get_by_instance(context.get_admin_context(), instance['id']) mac_addr = instance.mac_address diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 1927500ad..8f30a6d7c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -506,9 +506,7 @@ class VMHelper(HelperBase): try: return glance_disk_format2nova_type[disk_format] except KeyError: - raise exception.NotFound( - _("Unrecognized disk_format '%(disk_format)s'") - % locals()) + raise exception.InvalidDiskFormat(disk_format=disk_format) def determine_from_instance(): if instance.kernel_id: @@ -853,7 +851,7 @@ def safe_find_sr(session): """ sr_ref = find_sr(session) if sr_ref is None: - raise exception.NotFound(_('Cannot find SR to read/write VDI')) + raise exception.StorageRepositoryNotFound() return sr_ref diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 8b6a35f74..69b1a163d 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -260,8 +260,7 @@ class VMOps(object): instance_name = instance_or_vm.name vm_ref = VMHelper.lookup(self._session, instance_name) if vm_ref is None: - raise exception.NotFound( - _('Instance not present %s') % instance_name) + raise exception.InstanceNotFound(instance_id=instance_obj.id) return vm_ref def _acquire_bootlock(self, vm): @@ -578,9 +577,8 @@ class VMOps(object): if not (instance.kernel_id and instance.ramdisk_id): # 2. We only have kernel xor ramdisk - raise exception.NotFound( - _("Instance %(instance_id)s has a kernel or ramdisk but not " - "both" % locals())) + raise exception.InstanceUnacceptable(instance_id=instance_id, + reason=_("instance has a kernel or ramdisk but not both")) # 3. We have both kernel and ramdisk (kernel, ramdisk) = VMHelper.lookup_kernel_ramdisk(self._session, @@ -721,8 +719,7 @@ class VMOps(object): "%s-rescue" % instance.name) if not rescue_vm_ref: - raise exception.NotFound(_( - "Instance is not in Rescue Mode: %s" % instance.name)) + raise exception.InstanceNotInRescueMode(instance_id=instance.id) original_vm_ref = VMHelper.lookup(self._session, instance.name) instance._rescue = False diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index 757ecf5ad..afcb8cf47 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -45,8 +45,7 @@ class VolumeOps(object): # Before we start, check that the VM exists vm_ref = VMHelper.lookup(self._session, instance_name) if vm_ref is None: - raise exception.NotFound(_('Instance %s not found') - % instance_name) + raise exception.InstanceNotFound(instance_id=instance_name) # NOTE: No Resource Pool concept so far LOG.debug(_("Attach_volume: %(instance_name)s, %(device_path)s," " %(mountpoint)s") % locals()) @@ -98,8 +97,7 @@ class VolumeOps(object): # Before we start, check that the VM exists vm_ref = VMHelper.lookup(self._session, instance_name) if vm_ref is None: - raise exception.NotFound(_('Instance %s not found') - % instance_name) + raise exception.InstanceNotFound(instance_id=instance_name) # Detach VBD from VM LOG.debug(_("Detach_volume: %(instance_name)s, %(mountpoint)s") % locals()) -- cgit From c8bf0d217ba3601dfccff32c56fef1565d90d262 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 21 Apr 2011 13:02:33 -0700 Subject: pep8 --- nova/api/openstack/views/images.py | 1 + nova/tests/api/openstack/test_images.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index ab9a9d294..3c98599b4 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -87,6 +87,7 @@ class ViewBuilderV10(ViewBuilder): """OpenStack API v1.0 Image Builder""" pass + class ViewBuilderV11(ViewBuilder): """OpenStack API v1.1 Image Builder""" diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index f73aff6e2..4389539a0 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -538,7 +538,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, { 'id': 127, - 'name': 'killed backup', + 'name': 'killed backup', 'serverId': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, -- cgit From 6c037c5c639249556fcadd871d8af91760b50e90 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 21 Apr 2011 16:17:16 -0400 Subject: Fixes and reworkings based on review. --- nova/api/openstack/servers.py | 28 +++++++++------------------- nova/compute/api.py | 11 ----------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 357a279ef..a7e240917 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -623,11 +623,6 @@ class ControllerV10(Controller): msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - except exception.Error as ex: - msg = _("Error encountered attempting to rebuild instance " - "%(instance_id): %(ex)") % locals() - LOG.error(msg) - raise response = exc.HTTPAccepted() response.empty_body = True @@ -672,8 +667,8 @@ class ControllerV11(Controller): def _limit_items(self, items, req): return common.limited_by_marker(items, req) - def _check_metadata(self, metadata): - """Ensure that the metadata given is of the correct type.""" + def _validate_metadata(self, metadata): + """Ensure that we can work with the metadata given.""" try: metadata.iteritems() except AttributeError as ex: @@ -681,8 +676,8 @@ class ControllerV11(Controller): LOG.debug(msg) raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) - def _check_personalities(self, personalities): - """Ensure the given personalities have valid paths and contents.""" + def _decode_personalities(self, personalities): + """Decode the Base64-encoded personalities.""" for personality in personalities: try: path = personality["path"] @@ -693,7 +688,7 @@ class ControllerV11(Controller): raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) try: - base64.b64decode(contents) + personality["contents"] = base64.b64decode(contents) except TypeError: msg = _("Personality content could not be Base64 decoded.") LOG.info(msg) @@ -713,21 +708,16 @@ class ControllerV11(Controller): personalities = info["rebuild"].get("personality", []) metadata = info["rebuild"].get("metadata", {}) - self._check_metadata(metadata) - self._check_personalities(personalities) + self._validate_metadata(metadata) + self._decode_personalities(personalities) try: - args = [context, instance_id, image_id, metadata, personalities] - self.compute_api.rebuild(*args) + self.compute_api.rebuild(context, instance_id, image_id, metadata, + personalities) except exception.BuildInProgress: msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - except exception.Error as ex: - msg = _("Error encountered attempting to rebuild instance " - "%(instance_id): %(ex)") % locals() - LOG.error(msg) - raise response = exc.HTTPAccepted() response.empty_body = True diff --git a/nova/compute/api.py b/nova/compute/api.py index 703533f55..4ec840b07 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -18,7 +18,6 @@ """Handles all requests relating to instances (guest vms).""" -import base64 import datetime import re import time @@ -104,15 +103,6 @@ class API(base.Base): if len(content) > content_limit: raise quota.QuotaError(code="OnsetFileContentLimitExceeded") - def _check_injected_file_format(self, injected_files): - """Ensure given injected files are in the correct format.""" - for file_path, content in injected_files: - try: - base64.b64decode(content) - except TypeError: - msg = _("File contents must be base64 encoded.") - raise exception.Error(msg) - def _check_metadata_properties_quota(self, context, metadata={}): """Enforce quota limits on metadata properties.""" num_metadata = len(metadata) @@ -526,7 +516,6 @@ class API(base.Base): files_to_inject = files_to_inject or [] self._check_injected_file_quota(context, files_to_inject) - self._check_injected_file_format(files_to_inject) self.db.instance_update(context, instance_id, {"metadata": metadata}) -- cgit From 21ea16a857fe95cec3a3748a519c547f433e982a Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 21 Apr 2011 13:17:32 -0700 Subject: pep8 --- nova/tests/api/openstack/test_images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 4389539a0..3d341a179 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -651,7 +651,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, { 'id': 127, - 'name': 'killed backup', + 'name': 'killed backup', 'serverRef': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, -- cgit From dc4beede6bda3b7db5ca5963cc6c48052d2b7c62 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 21 Apr 2011 23:45:02 -0700 Subject: Fixes per review --- nova/auth/manager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 3de2ceffe..4d4bdbce9 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -368,10 +368,10 @@ class AuthManager(object): return True def _build_mc_key(self, user, role, project=None): - role_key = str("rolecache-%s-%s" % (User.safe_id(user), role)) + key_parts = ['rolecache', User.safe_id(user), str(role)] if project: - return "%s-%s" % (role_key, Project.safe_id(project)) - return role_key + key_parts.append(Project.safe_id(project)) + return '-'.join(key_parts) def _clear_mc_key(self, user, role, project=None): # NOTE(anthony): it would be better to delete the key @@ -380,7 +380,7 @@ class AuthManager(object): def _has_role(self, user, role, project=None): mc_key = self._build_mc_key(user, role, project) rslt = self.mc.get(mc_key) - if rslt == None: + if rslt is None: with self.driver() as drv: rslt = drv.has_role(user, role, project) self.mc.set(mc_key, rslt) -- cgit From f710ad1e3fff16de696f608986f24bdc8ffc3f6b Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Fri, 22 Apr 2011 00:22:23 -0700 Subject: pep8 --- nova/compute/instance_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 4e250bb83..7e7198b96 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -57,8 +57,8 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, except exception.DBError, e: LOG.exception(_('DB error: %s') % e) raise exception.ApiError(_("Cannot create instance_type with " - "name %(name)s and flavorid %(flavorid)s") % - locals()) + "name %(name)s and flavorid %(flavorid)s") + % locals()) def destroy(name): -- cgit From edc63f9734a4b053a3b57fd6febe94824c83815f Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Fri, 22 Apr 2011 21:35:54 +0900 Subject: Rework completed. Added test cases, changed helper method name, etc. --- nova/auth/authutils.py | 48 ------------------------------------ nova/auth/manager.py | 8 +++--- nova/tests/test_auth.py | 64 ++++++++++++++++++++++++++++-------------------- nova/tests/test_utils.py | 25 +++++++++++++++++++ nova/utils.py | 30 +++++++++++++++++++++++ 5 files changed, 95 insertions(+), 80 deletions(-) delete mode 100644 nova/auth/authutils.py diff --git a/nova/auth/authutils.py b/nova/auth/authutils.py deleted file mode 100644 index 429e86ef9..000000000 --- a/nova/auth/authutils.py +++ /dev/null @@ -1,48 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 NTT DATA CORPORATION. -# Copyright 2011 OpenStack LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Auth module specific utilities and helper functions. -""" - -import netaddr -import string - - -def get_host_only_server_string(server_str): - """ - Returns host part only of the given server_string if it's a combination - of host part and port. Otherwise, return null string. - """ - - # First of all, exclude pure IPv6 address (w/o port). - if netaddr.valid_ipv6(server_str): - return '' - - # Next, check if this is IPv6 address with port number combination. - if server_str.find("]:") != -1: - [address, sep, port] = server_str.replace('[', '', 1).partition(']:') - return address - - # Third, check if this is a combination of general address and port - if server_str.find(':') == -1: - return '' - - # This must be a combination of host part and port - (address, port) = server_str.split(':') - return address diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 775b38af1..d42594c84 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -35,7 +35,6 @@ from nova import flags from nova import log as logging from nova import utils from nova.auth import signer -from nova.auth import authutils FLAGS = flags.FLAGS @@ -316,13 +315,12 @@ class AuthManager(object): LOG.debug(_('expected_signature: %s'), expected_signature) LOG.debug(_('signature: %s'), signature) if signature != expected_signature: - host_only = authutils.get_host_only_server_string( - server_string) + (addr_str, port_str) = utils.parse_server_string(server_string) # If the given server_string contains port num, try without it. - if host_only != '': + if port_str != '': host_only_signature = signer.Signer( user.secret.encode()).generate(params, verb, - host_only, path) + addr_str, path) LOG.debug(_('host_only_signature: %s'), host_only_signature) if signature == host_only_signature: diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index 3886e9e6b..f02dd94b7 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -25,7 +25,6 @@ from nova import log as logging from nova import test from nova.auth import manager from nova.api.ec2 import cloud -from nova.auth import authutils FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.auth_unittest') @@ -102,9 +101,43 @@ class _AuthManagerBaseTestCase(test.TestCase): self.assertEqual('private-party', u.access) def test_004_signature_is_valid(self): - #self.assertTrue(self.manager.authenticate(**boto.generate_url ...? )) - pass - #raise NotImplementedError + with user_generator(self.manager, name='admin', secret='admin', + access='admin'): + with project_generator(self.manager, name="admin", + manager_user='admin'): + accesskey = 'admin:admin' + expected_result = (self.manager.get_user('admin'), + self.manager.get_project('admin')) + # captured sig and query string using boto 1.9b/euca2ools 1.2 + sig = 'd67Wzd9Bwz8xid9QU+lzWXcF2Y3tRicYABPJgrqfrwM=' + auth_params = {'AWSAccessKeyId': 'admin:admin', + 'Action': 'DescribeAvailabilityZones', + 'SignatureMethod': 'HmacSHA256', + 'SignatureVersion': '2', + 'Timestamp': '2011-04-22T11:29:29', + 'Version': '2009-11-30'} + self.assertTrue(expected_result, self.manager.authenticate( + accesskey, + sig, + auth_params, + 'GET', + '127.0.0.1:8773', + '/services/Cloud/')) + # captured sig and query string using RightAWS 1.10.0 + sig = 'ECYLU6xdFG0ZqRVhQybPJQNJ5W4B9n8fGs6+/fuGD2c=' + auth_params = {'AWSAccessKeyId': 'admin:admin', + 'Action': 'DescribeAvailabilityZones', + 'SignatureMethod': 'HmacSHA256', + 'SignatureVersion': '2', + 'Timestamp': '2011-04-22T11:29:49.000Z', + 'Version': '2008-12-01'} + self.assertTrue(expected_result, self.manager.authenticate( + accesskey, + sig, + auth_params, + 'GET', + '127.0.0.1', + '/services/Cloud')) def test_005_can_get_credentials(self): return @@ -340,29 +373,6 @@ class AuthManagerDbTestCase(_AuthManagerBaseTestCase): auth_driver = 'nova.auth.dbdriver.DbDriver' -class AuthManagerUtilTestCase(test.TestCase): - def test_get_host_only_server_string(self): - result = authutils.get_host_only_server_string('::1') - self.assertEqual('', result) - result = authutils.get_host_only_server_string('[::1]:8773') - self.assertEqual('::1', result) - result = authutils.get_host_only_server_string('2001:db8::192.168.1.1') - self.assertEqual('', result) - result = authutils.get_host_only_server_string( - '[2001:db8::192.168.1.1]:8773') - self.assertEqual('2001:db8::192.168.1.1', result) - result = authutils.get_host_only_server_string('192.168.1.1') - self.assertEqual('', result) - result = authutils.get_host_only_server_string('192.168.1.2:8773') - self.assertEqual('192.168.1.2', result) - result = authutils.get_host_only_server_string('192.168.1.3') - self.assertEqual('', result) - result = authutils.get_host_only_server_string('www.example.com:8443') - self.assertEqual('www.example.com', result) - result = authutils.get_host_only_server_string('www.example.com') - self.assertEqual('', result) - - if __name__ == "__main__": # TODO: Implement use_fake as an option unittest.main() diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index e08d229b0..e7b5c826e 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -250,3 +250,28 @@ class GetFromPathTestCase(test.TestCase): input = {'a': [1, 2, {'b': 'b_1'}]} self.assertEquals([1, 2, {'b': 'b_1'}], f(input, "a")) self.assertEquals(['b_1'], f(input, "a/b")) + + +class GenericUtilsTestCase(test.TestCase): + def test_parse_server_string(self): + result = utils.parse_server_string('::1') + self.assertEqual(('::1', ''), result) + result = utils.parse_server_string('[::1]:8773') + self.assertEqual(('::1', '8773'), result) + result = utils.parse_server_string('2001:db8::192.168.1.1') + self.assertEqual(('2001:db8::192.168.1.1', ''), result) + result = utils.parse_server_string('[2001:db8::192.168.1.1]:8773') + self.assertEqual(('2001:db8::192.168.1.1', '8773'), result) + result = utils.parse_server_string('192.168.1.1') + self.assertEqual(('192.168.1.1', ''), result) + result = utils.parse_server_string('192.168.1.2:8773') + self.assertEqual(('192.168.1.2', '8773'), result) + result = utils.parse_server_string('192.168.1.3') + self.assertEqual(('192.168.1.3', ''), result) + result = utils.parse_server_string('www.example.com:8443') + self.assertEqual(('www.example.com', '8443'), result) + result = utils.parse_server_string('www.example.com') + self.assertEqual(('www.example.com', ''), result) + # error case + result = utils.parse_server_string('www.exa:mple.com:8443') + self.assertEqual(('', ''), result) diff --git a/nova/utils.py b/nova/utils.py index b783f6c14..82ab3fc9e 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -709,3 +709,33 @@ def check_isinstance(obj, cls): raise Exception(_('Expected object of type: %s') % (str(cls))) # TODO(justinsb): Can we make this better?? return cls() # Ugly PyLint hack + + +def parse_server_string(server_str): + """ + Parses the given server_string and returns a list of host and port. + If it's not a combination of host part and port, the port element + is a null string. If the input is invalid expression, return a null + list. + """ + try: + # First of all, exclude pure IPv6 address (w/o port). + if netaddr.valid_ipv6(server_str): + return (server_str, '') + + # Next, check if this is IPv6 address with a port number combination. + if server_str.find("]:") != -1: + (address, port) = server_str.replace('[', '', 1).split(']:') + return (address, port) + + # Third, check if this is a combination of an address and a port + if server_str.find(':') == -1: + return (server_str, '') + + # This must be a combination of an address and a port + (address, port) = server_str.split(':') + return (address, port) + + except: + LOG.debug(_('Invalid server_string: %s' % server_str)) + return ('', '') -- cgit From 7168812fdf56280f24dc977c5dd9c7a73959c2a2 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 22 Apr 2011 13:30:13 -0400 Subject: adding gettext to setup.py --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 6c45109bc..194b55183 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. +import gettext import glob import os import subprocess @@ -33,6 +34,7 @@ except ImportError: assert DistUtilsExtra.auto.__version__ >= '2.18',\ 'needs DistUtilsExtra.auto >= 2.18' +gettext.install('nova', unicode=1) from nova.utils import parse_mailmap, str_dict_replace from nova import version -- cgit From c2ec2054a6b42b086580c6647ae0d5d808b4d2c7 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 22 Apr 2011 15:14:36 -0400 Subject: fixing bad merge --- nova/virt/libvirt_conn.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ee0b7ab98..7a78ce9e2 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -619,10 +619,6 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def spawn(self, instance, network_info=None): xml = self.to_xml(instance, False, network_info) - db.instance_set_state(context.get_admin_context(), - instance['id'], - power_state.NOSTATE, - 'launching') self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) self._create_image(instance, xml, network_info) -- cgit From a3f16d7efbdc51c75c1a729d9194ee3a66841bab Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 22 Apr 2011 15:49:37 -0400 Subject: pep8 --- nova/db/sqlalchemy/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index edfd3ce77..0d4fe61bf 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2270,7 +2270,7 @@ def console_pool_get_by_host_type(context, compute_host, host, if not result: raise exception.ConsolePoolNotFoundForHostType(host=host, console_type=console_type, - compute_host=compute_host ) + compute_host=compute_host) return result -- cgit From 2014dcd674cc18d440b92202558adef1a81e36c3 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Mon, 25 Apr 2011 00:01:19 -0700 Subject: updated tests to reflect serverRef as href (per Ilya Alekseyev) and refactored _build_server from ViewBuilder (per Eldar Nugaev) --- nova/api/openstack/views/images.py | 20 +++++++++++++++----- nova/tests/api/openstack/test_images.py | 8 ++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 3c98599b4..2773c9c13 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -46,6 +46,14 @@ class ViewBuilder(object): except KeyError: image['status'] = image['status'].upper() + def _build_server(self, image, instance_id): + """Indicates that you must use a ViewBuilder subclass.""" + raise NotImplementedError + + def generate_server_ref(self, server_id): + """Return an href string pointing to this server.""" + return os.path.join(self._url, "servers", str(server_id)) + def generate_href(self, image_id): """Return an href string pointing to this object.""" return os.path.join(self._url, "images", str(image_id)) @@ -66,7 +74,7 @@ class ViewBuilder(object): if "instance_id" in properties: try: - image["serverId"] = int(properties["instance_id"]) + self._build_server(image, int(properties["instance_id"])) except ValueError: pass @@ -85,19 +93,21 @@ class ViewBuilder(object): class ViewBuilderV10(ViewBuilder): """OpenStack API v1.0 Image Builder""" - pass + + def _build_server(self, image, instance_id): + image["serverId"] = instance_id class ViewBuilderV11(ViewBuilder): """OpenStack API v1.1 Image Builder""" + def _build_server(self, image, instance_id): + image["serverRef"] = self.generate_server_ref(instance_id) + def build(self, image_obj, detail=False): """Return a standardized image structure for display by the API.""" image = ViewBuilder.build(self, image_obj, detail) href = self.generate_href(image_obj["id"]) - if "serverId" in image: - image["serverRef"] = image["serverId"] - del image["serverId"] image["links"] = [{ "rel": "self", diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 3d341a179..e5dd93c3f 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -585,7 +585,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 124, 'name': 'queued backup', - 'serverRef': 42, + 'serverRef': "http://localhost/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'QUEUED', @@ -607,7 +607,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 125, 'name': 'saving backup', - 'serverRef': 42, + 'serverRef': "http://localhost/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'SAVING', @@ -630,7 +630,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 126, 'name': 'active backup', - 'serverRef': 42, + 'serverRef': "http://localhost/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', @@ -652,7 +652,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 127, 'name': 'killed backup', - 'serverRef': 42, + 'serverRef': "http://localhost/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', -- cgit From c3ab4f023e2636e254f940e08da0aded42c0e96b Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 25 Apr 2011 12:55:59 -0400 Subject: removed extra newline --- nova/tests/test_exception.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/test_exception.py b/nova/tests/test_exception.py index 65df20a61..1b0e41d9a 100644 --- a/nova/tests/test_exception.py +++ b/nova/tests/test_exception.py @@ -21,7 +21,6 @@ from nova import exception class ApiErrorTestCase(test.TestCase): - def test_return_valid_error(self): # without 'code' arg err = exception.ApiError('fake error') -- cgit From 6721d8918820f53288cbdf09ee352e93120439f9 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 26 Apr 2011 09:45:53 -0400 Subject: Modified instance status for shutoff power state in OS api --- nova/api/openstack/views/servers.py | 4 ++-- nova/tests/api/openstack/test_servers.py | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index f2d8d5720..bdc85f4a1 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -63,8 +63,8 @@ class ViewBuilder(object): power_state.BLOCKED: 'ACTIVE', power_state.SUSPENDED: 'SUSPENDED', power_state.PAUSED: 'PAUSED', - power_state.SHUTDOWN: 'INACTIVE', - power_state.SHUTOFF: 'ACTIVE', + power_state.SHUTDOWN: 'SHUTDOWN', + power_state.SHUTOFF: 'SHUTOFF', power_state.CRASHED: 'ERROR', power_state.FAILED: 'ERROR'} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index c34392764..f651bb258 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1169,7 +1169,16 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) - self.assertEqual(res_dict['server']['status'], 'INACTIVE') + self.assertEqual(res_dict['server']['status'], 'SHUTDOWN') + + def test_shutoff_status(self): + new_return_server = return_server_with_power_state(power_state.SHUTOFF) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['status'], 'SHUTOFF') class TestServerCreateRequestXMLDeserializer(unittest.TestCase): -- cgit From 7630ae42c0e5ba0b7cb2c1cb10b9019215c36570 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 26 Apr 2011 11:25:02 -0400 Subject: Fixed formatting to align with PEP 8 --- nova/tests/api/openstack/test_servers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index f651bb258..9d8ab8217 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1163,8 +1163,8 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 400) def test_shutdown_status(self): - new_return_server = return_server_with_power_state(power_state.SHUTDOWN) - self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + new_server = return_server_with_power_state(power_state.SHUTDOWN) + self.stubs.Set(nova.db.api, 'instance_get', new_server) req = webob.Request.blank('/v1.0/servers/1') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -1172,8 +1172,8 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict['server']['status'], 'SHUTDOWN') def test_shutoff_status(self): - new_return_server = return_server_with_power_state(power_state.SHUTOFF) - self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + new_server = return_server_with_power_state(power_state.SHUTOFF) + self.stubs.Set(nova.db.api, 'instance_get', new_server) req = webob.Request.blank('/v1.0/servers/1') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) -- cgit From 10552f691b73f8fe1a91e2ab8dc24b2d69f254c0 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 26 Apr 2011 13:22:24 -0400 Subject: Updated run_tests.sh usage info to reflect the --stop flag --- run_tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/run_tests.sh b/run_tests.sh index 8f4d37cd4..2c80f5ff7 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -6,6 +6,7 @@ function usage { echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" + echo " -x, --stop Stop running tests after the first error or failure." echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -h, --help Print this usage message" echo "" -- cgit From c95aaaaefe11048990021d376dbca6460f19248c Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Tue, 26 Apr 2011 14:17:09 -0700 Subject: Make the import of distutils.extra non-mandatory in setup.py. Just print a warning that i18n commands are not available... --- setup.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 194b55183..c165f40d7 100644 --- a/setup.py +++ b/setup.py @@ -25,14 +25,18 @@ import sys from setuptools import find_packages from setuptools.command.sdist import sdist +# In order to run the i18n commands for compiling and +# installing message catalogs, we use DistUtilsExtra. +# Don't make this a hard requirement, but warn that +# i18n commands won't be available if DistUtilsExtra is +# not installed... try: - import DistUtilsExtra.auto + from DistUtilsExtra.auto import setup except ImportError: - print >> sys.stderr, 'To build nova you need '\ - 'https://launchpad.net/python-distutils-extra' - sys.exit(1) -assert DistUtilsExtra.auto.__version__ >= '2.18',\ - 'needs DistUtilsExtra.auto >= 2.18' + from setuptools import setup + print "Warning: DistUtilsExtra required to use i18n builders. " + print "To build nova with support for message catalogs, you need " + print " https://launchpad.net/python-distutils-extra >= 2.18" gettext.install('nova', unicode=1) @@ -102,7 +106,7 @@ def find_data_files(destdir, srcdir): package_data += [(destdir, files)] return package_data -DistUtilsExtra.auto.setup(name='nova', +setup(name='nova', version=version.canonical_version_string(), description='cloud computing fabric controller', author='OpenStack', -- cgit From 475453e9981d4d71a0639afc176629163abfc818 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 26 Apr 2011 18:55:13 -0400 Subject: Let nova-mange limit project list by user. --- bin/nova-manage | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index c8230670a..820b10e26 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -385,10 +385,10 @@ class ProjectCommands(object): with open(filename, 'w') as f: f.write(rc) - def list(self): + def list(self, username=None): """Lists all projects - arguments: """ - for project in self.manager.get_projects(): + arguments: [username]""" + for project in self.manager.get_projects(username): print project.name def quota(self, project_id, key=None, value=None): -- cgit From e0e95c36f1d2c08c5ab419abdd8867c05d101475 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 27 Apr 2011 00:30:33 -0400 Subject: pep8 fixes --- nova/tests/api/openstack/test_servers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 9d8ab8217..f294b3b56 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1162,7 +1162,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_shutdown_status(self): + def test_shutdown_status(self): new_server = return_server_with_power_state(power_state.SHUTDOWN) self.stubs.Set(nova.db.api, 'instance_get', new_server) req = webob.Request.blank('/v1.0/servers/1') @@ -1170,8 +1170,8 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) self.assertEqual(res_dict['server']['status'], 'SHUTDOWN') - - def test_shutoff_status(self): + + def test_shutoff_status(self): new_server = return_server_with_power_state(power_state.SHUTOFF) self.stubs.Set(nova.db.api, 'instance_get', new_server) req = webob.Request.blank('/v1.0/servers/1') @@ -1179,7 +1179,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) self.assertEqual(res_dict['server']['status'], 'SHUTOFF') - + class TestServerCreateRequestXMLDeserializer(unittest.TestCase): -- cgit From 461b02122d2bc05ace3664e4a0a81251dd4e9d59 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 27 Apr 2011 00:53:07 -0400 Subject: Added myself to authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index eccf38a43..cef2c761c 100644 --- a/Authors +++ b/Authors @@ -1,3 +1,4 @@ +Alex Meade Andy Smith Andy Southgate Anne Gentle -- cgit From 8e6875e8c9b45a03396d5e4312c4f9136b1dc552 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Apr 2011 14:03:05 -0700 Subject: further cleanup of nova/exceptions.py --- nova/api/ec2/cloud.py | 8 ++-- nova/api/openstack/accounts.py | 2 +- nova/api/openstack/users.py | 2 +- nova/auth/manager.py | 6 ++- nova/compute/instance_types.py | 12 +++--- nova/db/sqlalchemy/api.py | 4 +- nova/exception.py | 78 ++++++++++++++++++++------------------- nova/scheduler/driver.py | 8 ++-- nova/tests/test_instance_types.py | 6 +-- nova/tests/test_scheduler.py | 10 ++--- nova/virt/libvirt_conn.py | 3 +- nova/wsgi.py | 5 +-- 12 files changed, 69 insertions(+), 75 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 94fbf19ff..092b80fa2 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -49,8 +49,6 @@ flags.DECLARE('service_down_time', 'nova.scheduler.driver') LOG = logging.getLogger("nova.api.cloud") -InvalidInputException = exception.InvalidInputException - def _gen_key(context, user_id, key_name): """Generate a key @@ -398,11 +396,11 @@ class CloudController(object): ip_protocol = str(ip_protocol) if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']: - raise InvalidInputException(_('%s is not a valid ipProtocol') % - (ip_protocol,)) + raise exception.InvalidIpProtocol(protocol=ip_protocol) if ((min(from_port, to_port) < -1) or (max(from_port, to_port) > 65535)): - raise InvalidInputException(_('Invalid port range')) + raise exception.InvalidPortRange(from_port=from_port, + to_port=to_port) values['protocol'] = ip_protocol values['from_port'] = from_port diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index 6e3763e47..00fdd4540 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -48,7 +48,7 @@ class Controller(common.OpenstackController): """We cannot depend on the db layer to check for admin access for the auth manager, so we do it here""" if not context.is_admin: - raise exception.NotAuthorized(_("Not admin user.")) + raise exception.AdminRequired() def index(self, req): raise faults.Fault(webob.exc.HTTPNotImplemented()) diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index 077ccfc79..7ae4c3232 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -48,7 +48,7 @@ class Controller(common.OpenstackController): """We cannot depend on the db layer to check for admin access for the auth manager, so we do it here""" if not context.is_admin: - raise exception.NotAuthorized(_("Not admin user")) + raise exception.AdminRequired() def index(self, req): """Return all users in brief""" diff --git a/nova/auth/manager.py b/nova/auth/manager.py index b719a0dbd..72566717e 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -303,7 +303,8 @@ class AuthManager(object): LOG.debug('signature: %s', signature) if signature != expected_signature: LOG.audit(_("Invalid signature for user %s"), user.name) - raise exception.NotAuthorized(_('Signature does not match')) + raise exception.InvalidSignature(signature=signature, + user=user) elif check_type == 'ec2': # NOTE(vish): hmac can't handle unicode, so encode ensures that # secret isn't unicode @@ -314,7 +315,8 @@ class AuthManager(object): LOG.debug('signature: %s', signature) if signature != expected_signature: LOG.audit(_("Invalid signature for user %s"), user.name) - raise exception.NotAuthorized(_('Signature does not match')) + raise exception.InvalidSignature(signature=signature, + user=user) return (user, project) def get_access_key(self, user, project): diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 7e7198b96..8cfa2f8da 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -37,11 +37,11 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, try: int(option) except ValueError: - raise exception.InvalidInputException( - _("create arguments must be positive integers")) + raise exception.InvalidInput(reason=_("create arguments must " + "be positive integers")) if (int(memory) <= 0) or (int(vcpus) <= 0) or (int(local_gb) < 0): - raise exception.InvalidInputException( - _("create arguments must be positive integers")) + raise exception.InvalidInput(reason=_("create arguments must " + "be positive integers")) try: db.instance_type_create( @@ -64,7 +64,7 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, def destroy(name): """Marks instance types as deleted.""" if name is None: - raise exception.InvalidInputException(_("No instance type specified")) + raise exception.InstanceNotFound(instance_id=instance_name) else: try: db.instance_type_destroy(context.get_admin_context(), name) @@ -76,7 +76,7 @@ def destroy(name): def purge(name): """Removes instance types from database.""" if name is None: - raise exception.InvalidInputException(_("No instance type specified")) + raise exception.InnstanceNotFound(instance_id=name) else: try: db.instance_type_purge(context.get_admin_context(), name) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 0d4fe61bf..31daa1f43 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -94,7 +94,7 @@ def require_admin_context(f): """ def wrapper(*args, **kwargs): if not is_admin_context(args[0]): - raise exception.NotAuthorized() + raise exception.AdminRequired() return f(*args, **kwargs) return wrapper @@ -105,7 +105,7 @@ def require_context(f): """ def wrapper(*args, **kwargs): if not is_admin_context(args[0]) and not is_user_context(args[0]): - raise exception.NotAuthorized() + raise exception.AdminRequired() return f(*args, **kwargs) return wrapper diff --git a/nova/exception.py b/nova/exception.py index 67b9d95ef..e8444cb14 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -55,42 +55,6 @@ class ApiError(Error): super(ApiError, self).__init__('%s: %s' % (code, message)) -class NotFound(Error): - pass - - -class InstanceNotFound(NotFound): - def __init__(self, message, instance_id): - self.instance_id = instance_id - super(InstanceNotFound, self).__init__(message) - - -class VolumeNotFound(NotFound): - def __init__(self, message, volume_id): - self.volume_id = volume_id - super(VolumeNotFound, self).__init__(message) - - -class NotAuthorized(Error): - pass - - -class NotEmpty(Error): - pass - - -class InvalidInputException(Error): - pass - - -class InvalidContentType(Error): - pass - - -class TimeoutException(Error): - pass - - class DBError(Error): """Wraps an implementation specific exception.""" def __init__(self, inner_exception): @@ -146,9 +110,43 @@ class NovaException(Exception): return self._error_string -#TODO(bcwaldon): EOL this exception! +class NotAuthorized(NovaException): + message = _("Not authorized.") + + def __init__(self, *args, **kwargs): + super(NotFound, self).__init__(**kwargs) + + +class AdminRequired(NotAuthorized): + message = _("User does not have admin privileges") + + class Invalid(NovaException): - pass + message = _("Unacceptable parameters.") + + +class InvalidSignature(Invalid): + message = _("Invalid signature %(signature)s for user %(user)s.") + + +class InvalidInput(Invalid): + message = _("Invalid input received") + ": %(reason)s" + + +class InvalidInstanceType(Invalid): + message = _("Invalid instance type %(instance_type)s.") + + +class InvalidPortRange(Invalid): + message = _("Invalid port range %(from_port)s:%(to_port)s.") + + +class InvalidIpProtocol(Invalid): + message = _("Invalid IP protocol %(protocol)s.") + + +class InvalidContentType(Invalid): + message = _("Invalid content type %(content_type)s.") class InstanceNotRunning(Invalid): @@ -533,3 +531,7 @@ class ProjectExists(Duplicate): class InstanceExists(Duplicate): message = _("Instance %(name)s already exists.") + + +class MigrationError(NovaException): + message = _("Migration error") + ": %(reason)s" diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index 87b10e940..2094e3565 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -255,11 +255,9 @@ class Scheduler(object): mem_avail = mem_total - mem_used mem_inst = instance_ref['memory_mb'] if mem_avail <= mem_inst: - raise exception.NotEmpty(_("Unable to migrate %(ec2_id)s " - "to destination: %(dest)s " - "(host:%(mem_avail)s " - "<= instance:%(mem_inst)s)") - % locals()) + reason = _("Unable to migrate %(ec2_id)s to destination: %(dest)s " + "(host:%(mem_avail)s <= instance:%(mem_inst)s)") + raise exception.MigrationError(reason=reason % locals()) def mounted_on_same_shared_storage(self, context, instance_ref, dest): """Check if the src and dest host mount same shared storage. diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py index dd7d0737e..ef271518c 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -75,13 +75,13 @@ class InstanceTypeTestCase(test.TestCase): def test_invalid_create_args_should_fail(self): """Ensures that instance type creation fails with invalid args""" self.assertRaises( - exception.InvalidInputException, + exception.InvalidInput, instance_types.create, self.name, 0, 1, 120, self.flavorid) self.assertRaises( - exception.InvalidInputException, + exception.InvalidInput, instance_types.create, self.name, 256, -1, 120, self.flavorid) self.assertRaises( - exception.InvalidInputException, + exception.InvalidInput, instance_types.create, self.name, 256, 1, "aa", self.flavorid) def test_non_existant_inst_type_shouldnt_delete(self): diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index efd12f930..968ef9d6c 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -768,14 +768,10 @@ class SimpleDriverTestCase(test.TestCase): s_ref = self._create_compute_service(host='somewhere', memory_mb_used=12) - try: - self.scheduler.driver._live_migration_dest_check(self.context, - i_ref, - 'somewhere') - except exception.NotEmpty, e: - c = (e.message.find('Unable to migrate') >= 0) + self.assertRaises(exception.MigrationError, + self.scheduler.driver._live_migration_dest_check, + self.context, i_ref, 'somewhere') - self.assertTrue(c) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 15adcccee..8cb971d95 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1059,8 +1059,7 @@ class LibvirtConnection(driver.ComputeDriver): except libvirt.libvirtError as ex: error_code = ex.get_error_code() if error_code == libvirt.VIR_ERR_NO_DOMAIN: - msg = _("Instance %s not found") % instance_name - raise exception.NotFound(msg) + raise exception.InstanceNotFound(instance_id=instance_name) msg = _("Error from libvirt while looking up %(instance_name)s: " "[Error Code %(error_code)s] %(ex)s") % locals() diff --git a/nova/wsgi.py b/nova/wsgi.py index f3f82b36a..e60a8820d 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -428,7 +428,7 @@ class Serializer(object): try: return handlers[content_type] except Exception: - raise exception.InvalidContentType() + raise exception.InvalidContentType(content_type=content_type) def serialize(self, data, content_type): """Serialize a dictionary into the specified content type.""" @@ -451,8 +451,7 @@ class Serializer(object): try: return handlers[content_type] except Exception: - raise exception.InvalidContentType(_('Invalid content type %s' - % content_type)) + raise exception.InvalidContentType(content_type=content_type) def _from_json(self, datastring): return utils.loads(datastring) -- cgit From 0fe36c8ba3e524e490c66011c0787ea8a26dcfee Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Apr 2011 14:23:21 -0700 Subject: Correcting exception case --- nova/compute/instance_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 8cfa2f8da..1275a6fdd 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -64,7 +64,7 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, def destroy(name): """Marks instance types as deleted.""" if name is None: - raise exception.InstanceNotFound(instance_id=instance_name) + raise exception.InvalidInstanceType(instance_type=name) else: try: db.instance_type_destroy(context.get_admin_context(), name) @@ -76,7 +76,7 @@ def destroy(name): def purge(name): """Removes instance types from database.""" if name is None: - raise exception.InnstanceNotFound(instance_id=name) + raise exception.InvalidInstanceType(instance_type=name) else: try: db.instance_type_purge(context.get_admin_context(), name) -- cgit From 95ee288d3498c478248afdea649eef1aa58fe2f2 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Wed, 27 Apr 2011 20:33:55 -0700 Subject: added nova version output to usage printout for nova-manage --- bin/nova-manage | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/nova-manage b/bin/nova-manage index c8230670a..898255fb9 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -82,6 +82,7 @@ from nova import log as logging from nova import quota from nova import rpc from nova import utils +from nova import version from nova.api.ec2 import ec2utils from nova.auth import manager from nova.cloudpipe import pipelib @@ -1091,6 +1092,8 @@ def main(): script_name = argv.pop(0) if len(argv) < 1: + print _("\nOpenStack Nova version: %s (%s)\n") %\ + (version.version_string(), version.version_string_with_vcs()) print script_name + " category action []" print _("Available categories:") for k, _v in CATEGORIES: -- cgit From 542909e52a6f3f2a9891b710f3755ea7c033a8d0 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Thu, 28 Apr 2011 14:41:56 +0400 Subject: Sanitize get_console_output in libvirt_conn --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 15adcccee..54186ced0 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -695,7 +695,7 @@ class LibvirtConnection(driver.ComputeDriver): else: fpath = console_log - return self._dump_file(fpath) + return self._dump_file(fpath).decode('utf8','replace').encode('ascii','replace') @exception.wrap_exception def get_ajax_console(self, instance): -- cgit From 0ab13f16af693fc7eee200aadc951c99241f86fa Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 28 Apr 2011 10:26:43 -0700 Subject: added version list command to nova-manage --- bin/nova-manage | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 898255fb9..ad2960d14 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -759,6 +759,16 @@ class DbCommands(object): print migration.db_version() +class VersionCommands(object): + """Class for managing the database.""" + + def __init__(self): + pass + + def list(self): + print _("%s (%s)") %\ + (version.version_string(), version.version_string_with_vcs()) + class VolumeCommands(object): """Methods for dealing with a cloud in an odd state""" @@ -1050,7 +1060,8 @@ CATEGORIES = [ ('volume', VolumeCommands), ('instance_type', InstanceTypeCommands), ('image', ImageCommands), - ('flavor', InstanceTypeCommands)] + ('flavor', InstanceTypeCommands), + ('version', VersionCommands)] def lazy_match(name, key_value_tuples): -- cgit From ae50200f9a5a72cab7d976e5dd7fda287c54341f Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 28 Apr 2011 12:49:07 -0700 Subject: fixed docstring per jsb --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index ad2960d14..d2d81e5af 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -760,7 +760,7 @@ class DbCommands(object): class VersionCommands(object): - """Class for managing the database.""" + """Class for exposing the codebase version.""" def __init__(self): pass -- cgit From ad077fc137cc6a1dfdcd60349560abb94f4cc8eb Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 28 Apr 2011 12:49:48 -0700 Subject: pep8 --- bin/nova-manage | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/nova-manage b/bin/nova-manage index d2d81e5af..8122e6745 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -769,6 +769,7 @@ class VersionCommands(object): print _("%s (%s)") %\ (version.version_string(), version.version_string_with_vcs()) + class VolumeCommands(object): """Methods for dealing with a cloud in an odd state""" -- cgit From ba43fe874a959e677c2d02583d261d8136c9ff8e Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 28 Apr 2011 13:37:30 -0700 Subject: converted 1/0 comparison to True/False for Postgres compatibility --- nova/db/sqlalchemy/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 0d4fe61bf..aa341949d 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -816,17 +816,17 @@ def instance_destroy(context, instance_id): with session.begin(): session.query(models.Instance).\ filter_by(id=instance_id).\ - update({'deleted': 1, + update({'deleted': True, 'deleted_at': datetime.datetime.utcnow(), 'updated_at': literal_column('updated_at')}) session.query(models.SecurityGroupInstanceAssociation).\ filter_by(instance_id=instance_id).\ - update({'deleted': 1, + update({'deleted': True, 'deleted_at': datetime.datetime.utcnow(), 'updated_at': literal_column('updated_at')}) session.query(models.InstanceMetadata).\ filter_by(instance_id=instance_id).\ - update({'deleted': 1, + update({'deleted': True, 'deleted_at': datetime.datetime.utcnow(), 'updated_at': literal_column('updated_at')}) @@ -2513,7 +2513,7 @@ def instance_metadata_delete(context, instance_id, key): filter_by(instance_id=instance_id).\ filter_by(key=key).\ filter_by(deleted=False).\ - update({'deleted': 1, + update({'deleted': True, 'deleted_at': datetime.datetime.utcnow(), 'updated_at': literal_column('updated_at')}) -- cgit From e49ef5187491d4143de8d0707595c9fb566d4211 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 29 Apr 2011 13:20:31 +0400 Subject: Changed test_cloud and fake virt driver to show out the fix. --- nova/tests/test_cloud.py | 2 +- nova/virt/fake.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index c45bdd12c..f271c03f2 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -290,7 +290,7 @@ class CloudTestCase(test.TestCase): instance_id = rv['instancesSet'][0]['instanceId'] output = self.cloud.get_console_output(context=self.context, instance_id=[instance_id]) - self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE OUTPUT') + self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE?OUTPUT') # TODO(soren): We need this until we can stop polling in the rpc code # for unit tests. greenthread.sleep(0.3) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 33f37b512..59189277d 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -367,7 +367,8 @@ class FakeConnection(driver.ComputeDriver): return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L] def get_console_output(self, instance): - return 'FAKE CONSOLE OUTPUT' + return 'FAKE CONSOLE\xffOUTPUT'.decode('utf8','replace').encode('ascii','replace') + def get_ajax_console(self, instance): return {'token': 'FAKETOKEN', -- cgit From b5fb5c865c62834790a595b9ece98406b5cf1394 Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Fri, 29 Apr 2011 14:39:13 -0500 Subject: Changing links in sidebar to previous release --- doc/source/_theme/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/_theme/layout.html b/doc/source/_theme/layout.html index 0a37a7943..b28edb364 100644 --- a/doc/source/_theme/layout.html +++ b/doc/source/_theme/layout.html @@ -73,7 +73,7 @@

- Psst... hey. You're reading the latest content, but it might be out of sync with code. You can read Nova 2011.1 docs or all OpenStack docs too. + Psst... hey. You're reading the latest content, but it might be out of sync with code. You can read Nova 2011.2 docs or all OpenStack docs too.

{%- endif %} -- cgit From 721fafcfe0679e21fc4f60ec9fa0cfb5dcc468b1 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Sat, 30 Apr 2011 07:14:20 -0400 Subject: adding view file --- nova/api/openstack/views/limits.py | 100 ++++++++++++++++++++++++++++++++ nova/tests/api/openstack/test_limits.py | 26 ++++----- 2 files changed, 113 insertions(+), 13 deletions(-) create mode 100644 nova/api/openstack/views/limits.py diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py new file mode 100644 index 000000000..552db39ee --- /dev/null +++ b/nova/api/openstack/views/limits.py @@ -0,0 +1,100 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010-2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import time + +from nova.api.openstack import common + + +class ViewBuilder(object): + """Openstack API base limits view builder.""" + + def build(self, rate_limits, absolute_limits): + rate_limits = self._build_rate_limits(rate_limits) + absolute_limits = self._build_absolute_limits(absolute_limits) + + output = { + "limits": { + "rate": rate_limits, + "absolute": absolute_limits, + }, + } + + return output + + +class ViewBuilderV10(ViewBuilder): + """Openstack API v1.0 limits view builder.""" + + def _build_rate_limits(self, rate_limits): + return [self._build_rate_limit(r) for r in rate_limits] + + def _build_rate_limit(self, rate_limit): + return { + "verb": rate_limit["verb"], + "URI": rate_limit["URI"], + "regex": rate_limit["regex"], + "value": rate_limit["value"], + "remaining": int(rate_limit["remaining"]), + "unit": rate_limit["unit"], + "resetTime": rate_limit["resetTime"], + } + + def _build_absolute_limits(self, absolute_limit): + return {} + + +class ViewBuilderV11(ViewBuilder): + """Openstack API v1.1 limits view builder.""" + + def _build_rate_limits(self, rate_limits): + limits = [] + for rate_limit in rate_limits: + _rate_limit_key = None + _rate_limit = self._build_rate_limit(rate_limit) + + # check for existing key + for limit in limits: + if limit["uri"] == rate_limit["URI"] and \ + limit["regex"] == limit["regex"]: + _rate_limit_key = limit + break + + # ensure we have a key if we didn't find one + if not _rate_limit_key: + _rate_limit_key = { + "uri": rate_limit["URI"], + "regex": rate_limit["regex"], + "limit": [], + } + limits.append(_rate_limit_key) + + _rate_limit_key["limit"].append(_rate_limit) + + return limits + + def _build_rate_limit(self, rate_limit): + return { + "verb": rate_limit["verb"], + "value": rate_limit["value"], + "remaining": int(rate_limit["remaining"]), + "unit": rate_limit["unit"], + "next-available": rate_limit["resetTime"], + } + + def _build_absolute_limits(self, absolute_limit): + return {} diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index e298418a2..45bd4d501 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -223,33 +223,33 @@ class LimitsControllerV11Test(BaseLimitTestSuite): "limits": { "rate": [ { - "regex": "changes-since", - "uri": "changes-since*", - "limits": [ + "regex": ".*", + "uri": "*", + "limit": [ { "verb": "GET", "next-available": 0, "unit": "MINUTE", + "value": 10, + "remaining": 10, + }, + { + "verb": "POST", + "next-available": 0, + "unit": "HOUR", "value": 5, "remaining": 5, }, ], }, { - "regex": ".*", - "uri": "*", - "limits": [ + "regex": "changes-since", + "uri": "changes-since*", + "limit": [ { "verb": "GET", "next-available": 0, "unit": "MINUTE", - "value": 10, - "remaining": 10, - }, - { - "verb": "POST", - "next-available": 0, - "unit": "HOUR", "value": 5, "remaining": 5, }, -- cgit From 6db188a3311ed62a24ba7202de2a6101c0d35c93 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sun, 1 May 2011 01:01:01 -0700 Subject: Added checking ip_v6 flag and test for it --- nova/tests/test_virt.py | 21 ++++++++++++++++----- nova/virt/libvirt_conn.py | 6 ++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 462cf5aa0..b8a2b5c7a 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -44,7 +44,7 @@ def _concurrency(wait, done, target): done.send() -def _create_network_info(count=1): +def _create_network_info(count=1, ipv6=True): fake = 'fake' fake_ip = '0.0.0.0/0' fake_ip_2 = '0.0.0.1/0' @@ -55,8 +55,11 @@ def _create_network_info(count=1): 'cidr': fake_ip, 'cidr_v6': fake_ip} mapping = {'mac': fake, - 'ips': [{'ip': fake_ip}, {'ip': fake_ip}], - 'ip6s': [{'ip': fake_ip}, {'ip': fake_ip_2}, {'ip': fake_ip_3}]} + 'ips': [{'ip': fake_ip}, {'ip': fake_ip}]} + if ipv6: + mapping['ip6s'] = [{'ip': fake_ip}, + {'ip': fake_ip_2}, + {'ip': fake_ip_3}] return [(network, mapping) for x in xrange(0, count)] @@ -825,12 +828,20 @@ class IptablesFirewallTestCase(test.TestCase): "TCP port 80/81 acceptance rule wasn't added") db.instance_destroy(admin_ctxt, instance_ref['id']) - def test_filters_for_instance(self): - network_info = _create_network_info() + def test_filters_for_instance_with_ip_v6(self): + self.flags(use_ipv6=True) + network_info = _create_network_info(ipv6=FLAGS.use_ipv6) rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) self.assertEquals(len(rulesv4), 2) self.assertEquals(len(rulesv6), 3) + def test_filters_for_instance_without_ip_v6(self): + self.flags(use_ipv6=False) + network_info = _create_network_info(ipv6=FLAGS.use_ipv6) + rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) + self.assertEquals(len(rulesv4), 2) + self.assertEquals(len(rulesv6), 0) + def multinic_iptables_test(self): ipv4_rules_per_network = 2 ipv6_rules_per_network = 3 diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 879534f59..4e1ec1a64 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -2008,8 +2008,10 @@ class IptablesFirewallDriver(FirewallDriver): for ip in mapping['ips']] ipv4_rules = self._create_filter(ips_v4, chain_name) - ips_v6 = [ip['ip'] for (_n, mapping) in network_info - for ip in mapping['ip6s']] + ips_v6 = [] + if FLAGS.use_ipv6: + ips_v6 = [ip['ip'] for (_n, mapping) in network_info + for ip in mapping['ip6s']] ipv6_rules = self._create_filter(ips_v6, chain_name) return ipv4_rules, ipv6_rules -- cgit From 103ed1e5ca489de0064decc91bccf25dfbadc761 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sun, 1 May 2011 12:10:54 -0700 Subject: place ipv6_rules creation under if ip_v6 section --- nova/virt/libvirt_conn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 4e1ec1a64..46643ce73 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -2008,12 +2008,12 @@ class IptablesFirewallDriver(FirewallDriver): for ip in mapping['ips']] ipv4_rules = self._create_filter(ips_v4, chain_name) - ips_v6 = [] + ipv6_rules = [] if FLAGS.use_ipv6: ips_v6 = [ip['ip'] for (_n, mapping) in network_info for ip in mapping['ip6s']] + ipv6_rules = self._create_filter(ips_v6, chain_name) - ipv6_rules = self._create_filter(ips_v6, chain_name) return ipv4_rules, ipv6_rules def _add_filters(self, chain_name, ipv4_rules, ipv6_rules): -- cgit From 30d7b7d17f2f08fa35417b03e47cfb7d4c8b24b2 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sun, 1 May 2011 20:43:06 -0700 Subject: small changes in libvirt tests --- nova/tests/test_virt.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index b8a2b5c7a..47432baef 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -44,7 +44,9 @@ def _concurrency(wait, done, target): done.send() -def _create_network_info(count=1, ipv6=True): +def _create_network_info(count=1, ipv6=None): + if ipv6 is None: + ipv6 = FLAGS.use_ipv6 fake = 'fake' fake_ip = '0.0.0.0/0' fake_ip_2 = '0.0.0.1/0' @@ -830,14 +832,14 @@ class IptablesFirewallTestCase(test.TestCase): def test_filters_for_instance_with_ip_v6(self): self.flags(use_ipv6=True) - network_info = _create_network_info(ipv6=FLAGS.use_ipv6) + network_info = _create_network_info() rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) self.assertEquals(len(rulesv4), 2) self.assertEquals(len(rulesv6), 3) def test_filters_for_instance_without_ip_v6(self): self.flags(use_ipv6=False) - network_info = _create_network_info(ipv6=FLAGS.use_ipv6) + network_info = _create_network_info() rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) self.assertEquals(len(rulesv4), 2) self.assertEquals(len(rulesv6), 0) -- cgit From 5ee53b6df23988263ffdc43549756ef59770981c Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 2 May 2011 00:45:09 -0700 Subject: removed unused method and fixed imports --- nova/api/openstack/servers.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 9b883f06b..03f700844 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -14,28 +14,25 @@ # under the License. import base64 -import hashlib import traceback from webob import exc from xml.dom import minidom from nova import compute -from nova import context from nova import exception from nova import flags from nova import log as logging from nova import quota from nova import utils -from nova import wsgi from nova.api.openstack import common from nova.api.openstack import faults import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors +import nova.api.openstack.views.flavors import nova.api.openstack.views.servers from nova.auth import manager as auth_manager from nova.compute import instance_types -from nova.compute import power_state import nova.api.openstack from nova.scheduler import api as scheduler_api @@ -595,9 +592,6 @@ class ControllerV10(Controller): return nova.api.openstack.views.servers.ViewBuilderV10( addresses_builder) - def _get_addresses_view_builder(self, req): - return nova.api.openstack.views.addresses.ViewBuilderV10(req) - def _limit_items(self, items, req): return common.limited(items, req) @@ -629,9 +623,6 @@ class ControllerV11(Controller): return nova.api.openstack.views.servers.ViewBuilderV11( addresses_builder, flavor_builder, image_builder, base_url) - def _get_addresses_view_builder(self, req): - return nova.api.openstack.views.addresses.ViewBuilderV11(req) - def _action_change_password(self, input_dict, req, id): context = req.environ['nova.context'] if (not 'changePassword' in input_dict -- cgit From e28ec55f91b944346065df737adf063436c53779 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 2 May 2011 00:55:54 -0700 Subject: fix typo in import --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 03f700844..8505c3f71 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -29,7 +29,7 @@ from nova.api.openstack import common from nova.api.openstack import faults import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors -import nova.api.openstack.views.flavors +import nova.api.openstack.views.images import nova.api.openstack.views.servers from nova.auth import manager as auth_manager from nova.compute import instance_types -- cgit From 3be272f432b4385cf77787416762a360687a36bd Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 2 May 2011 10:17:51 -0400 Subject: Use my_ip for libvirt version of get_host_ip_addr. --- nova/tests/test_virt.py | 5 +++++ nova/virt/libvirt_conn.py | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 462cf5aa0..9305de4ca 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -641,6 +641,11 @@ class LibvirtConnTestCase(test.TestCase): self.assertTrue(count) + def test_get_host_ip_addr(self): + conn = libvirt_conn.LibvirtConnection(False) + ip = conn.get_host_ip_addr() + self.assertEquals(ip, FLAGS.my_ip) + def tearDown(self): self.manager.delete_project(self.project) self.manager.delete_user(self.user) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 6b56622ff..a62deb4a6 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -40,7 +40,6 @@ import multiprocessing import os import random import shutil -import socket import subprocess import sys import tempfile @@ -737,9 +736,7 @@ class LibvirtConnection(driver.ComputeDriver): return {'token': token, 'host': host, 'port': port} def get_host_ip_addr(self): - hostname = self._conn.getHostname() - ip = socket.gethostbyname(hostname) - return ip + return FLAGS.my_ip @exception.wrap_exception def get_vnc_console(self, instance): -- cgit From c0d046ccb17c44ca66498d4b50e573c835b3d508 Mon Sep 17 00:00:00 2001 From: Kevin Bringard Date: Mon, 2 May 2011 13:19:58 -0600 Subject: Fixed 2 lines to allow pep8 check to pass --- nova/network/linux_net.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 9e2c59e70..82b2c7282 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -63,8 +63,8 @@ flags.DEFINE_string('dns_server', None, 'if set, uses specific dns server for dnsmasq') flags.DEFINE_string('dmz_cidr', '10.128.0.0/24', 'dmz range that should be accepted') -flags.DEFINE_string('dnsmasq_config_file',"", - 'Override the default dnsmasq settings with those in this file') +flags.DEFINE_string('dnsmasq_config_file', "", + 'Override the default dnsmasq settings with this file') binary_name = os.path.basename(inspect.stack()[-1][1]) -- cgit From f6ee353e388a52e338a7f1a27f924a9a0c60f9a1 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 2 May 2011 12:49:10 -0700 Subject: initial pass --- nova/scheduler/query.py | 164 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 nova/scheduler/query.py diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py new file mode 100644 index 000000000..5379b3d44 --- /dev/null +++ b/nova/scheduler/query.py @@ -0,0 +1,164 @@ +# Copyright (c) 2011 Openstack, LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Query is a plug-in mechanism for requesting instance resources. +Three plug-ins are included: PassThru, Flavor & JSON. PassThru just +returns the full, unfiltered list of hosts. Flavor is a hard coded +matching mechanism based on flavor criteria and JSON is an ad-hoc +query grammar. +""" + +from nova import exception +from nova import utils + +class Query: + """Base class for query plug-ins.""" + + def instance_type_to_query(self, instance_type): + """Convert instance_type into a query for most common use-case.""" + raise exception.Error(_("Query driver not specified.")) + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts that fulfill the query.""" + raise exception.Error(_("Query driver not specified.")) + + +def load_driver(driver_name): + resource = utils.import_class(driver_name) + if type(resource) != types.ClassType: + continue + cls = resource + if not issubclass(cls, Query): + raise exception.Error(_("Query driver does not derive " + "from nova.scheduler.query.Query.")) + return cls + + +class PassThruQuery: + """NOP query plug-in. Returns all hosts in ZoneManager. + This essentially does what the old Scheduler+Chance used + to give us.""" + + def instance_type_to_query(self, instance_type): + """Return anything to prevent base-class from raising + exception.""" + return (str(self.__class__), instance_type) + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts from ZoneManager list.""" + hosts = zone_manager.service_state.get('compute', {}) + return [(host, capabilities) + for host, capabilities in hosts.iteritems()] + + +class FlavorQuery: + """Query plug-in hard-coded to work with flavors.""" + + def instance_type_to_query(self, instance_type): + """Use instance_type to filter hosts.""" + return (str(self.__class__), instance_type) + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts that can create instance_type.""" + hosts = zone_manager.service_state.get('compute', {}) + selected_hosts = [] + instance_type = query + for host, capabilities in hosts.iteritems(): + host_ram_mb = capabilities.get['host_memory']['free'] + disk_bytes = capabilities.get['disk']['available'] + if host_ram_mb >= instance_type['memory_mb'] and \ + disk_bytes >= instance_type['local_gb']: + selected_hosts.append((host, capabilities)) + return selected_hosts + +#host entries (currently) are like: +# {'host_name-description': 'Default install of XenServer', +# 'host_hostname': 'xs-mini', +# 'host_memory': {'total': 8244539392, +# 'overhead': 184225792, +# 'free': 3868327936, +# 'free-computed': 3840843776}, +# 'host_other-config': {}, +# 'host_ip_address': '192.168.1.109', +# 'host_cpu_info': {}, +# 'disk': {'available': 32954957824, +# 'total': 50394562560, +# 'used': 17439604736}, +# 'host_uuid': 'cedb9b39-9388-41df-8891-c5c9a0c0fe5f', +# 'host_name-label': 'xs-mini'} + +# instance_type table has: +#name = Column(String(255), unique=True) +#memory_mb = Column(Integer) +#vcpus = Column(Integer) +#local_gb = Column(Integer) +#flavorid = Column(Integer, unique=True) +#swap = Column(Integer, nullable=False, default=0) +#rxtx_quota = Column(Integer, nullable=False, default=0) +#rxtx_cap = Column(Integer, nullable=False, default=0) + +class JsonQuery: + """Query plug-in to allow simple JSON-based grammar for selecting hosts.""" + + def _equals(self, args): + pass + + def _less_than(self, args): + pass + + def _greater_than(self, args): + pass + + def _in(self, args): + pass + + def _less_than_equal(self, args): + pass + + def _greater_than_equal(self, args): + pass + + def _not(self, args): + pass + + def _must(self, args): + pass + + def _or(self, args): + pass + + commands = { + '=': _equals, + '<': _less_than, + '>': _greater_than, + 'in': _in, + '<=': _less_than_equal, + '>=': _greater_than_equal, + 'not': _not, + 'must', _must, + 'or', _or, + } + + def instance_type_to_query(self, instance_type): + """Convert instance_type into JSON query object.""" + return (str(self.__class__), instance_type) + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts that can fulfill query.""" + return [] + + + -- cgit From b4b427ce5d7f66245135b9cf57208884b8b556fe Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Mon, 2 May 2011 16:14:41 -0400 Subject: Added support in osapi for requests with local hrefs, e.g., "imageRef":"2" --- nova/api/openstack/common.py | 9 ++++++++- nova/tests/api/openstack/test_servers.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 65ed1e143..32cd689ca 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import re from urlparse import urlparse import webob @@ -130,10 +131,16 @@ def get_image_id_from_image_hash(image_service, context, image_hash): def get_id_from_href(href): """Return the id portion of a url as an int. - Given: http://www.foo.com/bar/123?q=4 + Given: 'http://www.foo.com/bar/123?q=4' + Returns: 123 + + In order to support local hrefs, the href argument can be just an id: + Given: '123' Returns: 123 """ + if re.match(r'\d+$', str(href)): + return int(href) try: return int(urlparse(href).path.split('/')[-1]) except: diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 556046e9d..ccdf43504 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -613,6 +613,34 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_create_instance_v11_local_href(self): + self._setup_for_create_instance() + + imageRef = 'http://localhost/v1.1/images/2' + imageRefLocal = '2' + flavorRef = 'http://localhost/v1.1/flavors/3' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': imageRefLocal, + 'flavorRef': flavorRef, + }, + } + + req = webob.Request.blank('/v1.1/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + + server = json.loads(res.body)['server'] + self.assertEqual(16, len(server['adminPass'])) + self.assertEqual(1, server['id']) + self.assertEqual(flavorRef, server['flavorRef']) + self.assertEqual(imageRef, server['imageRef']) + self.assertEqual(res.status_int, 200) + def test_create_instance_with_admin_pass_v10(self): self._setup_for_create_instance() -- cgit From 2463fc66e363e13bf955e1ebb9b370f8e901d328 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Mon, 2 May 2011 16:49:20 -0400 Subject: No need to test length of admin password in local href test. --- nova/tests/api/openstack/test_servers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index ccdf43504..0118c5e4e 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -635,7 +635,6 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) server = json.loads(res.body)['server'] - self.assertEqual(16, len(server['adminPass'])) self.assertEqual(1, server['id']) self.assertEqual(flavorRef, server['flavorRef']) self.assertEqual(imageRef, server['imageRef']) -- cgit From 221a756550a61ee73a05b5b7d88d9c12ef97675b Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 2 May 2011 16:55:54 -0400 Subject: Update tools/pip-requires and tools/install_venv.py for python2.7 support (works in ubuntu 11.04) --- tools/install_venv.py | 12 ++++++++++-- tools/pip-requires | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tools/install_venv.py b/tools/install_venv.py index 30ec85374..db9b45cfd 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -31,11 +31,18 @@ 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]) + def die(message, *args): print >>sys.stderr, message % args sys.exit(1) +def check_python_version(): + if sys.version_info < (2,6): + die("Need Python Version >= 2.6") + + def run_command(cmd, redirect_output=True, check_exit_code=True): """ @@ -100,12 +107,12 @@ def install_dependencies(venv=VENV): # Tell the virtual env how to "import nova" - pthfile = os.path.join(venv, "lib", "python2.6", "site-packages", "nova.pth") + 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", "python2.6", "site-packages", "eventlet", + 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) @@ -134,6 +141,7 @@ def print_help(): def main(argv): + check_python_version() check_dependencies() create_virtualenv() install_dependencies() diff --git a/tools/pip-requires b/tools/pip-requires index 2f4136732..013c3ac49 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -2,7 +2,7 @@ SQLAlchemy==0.6.3 pep8==0.5.0 pylint==0.19 IPy==0.70 -Cheetah==2.4.2.1 +Cheetah==2.4.4 M2Crypto==0.20.2 amqplib==0.6.1 anyjson==0.2.4 -- cgit From f99c5f60d202e84f5defb63e771f60d20cf43df9 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 2 May 2011 16:58:06 -0400 Subject: got rid of extra whitespace --- tools/install_venv.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/install_venv.py b/tools/install_venv.py index db9b45cfd..03d93ac7d 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -33,7 +33,6 @@ 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]) - def die(message, *args): print >>sys.stderr, message % args sys.exit(1) @@ -42,8 +41,6 @@ def check_python_version(): 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 -- cgit From c38871690702ad3b6b39845ae33ee71465a8e95c Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Tue, 3 May 2011 11:05:45 +0400 Subject: Moved reencoding logic to compute manager and cloud EC2 API. --- nova/api/ec2/cloud.py | 2 +- nova/compute/manager.py | 3 ++- nova/tests/test_cloud.py | 3 ++- nova/virt/fake.py | 2 +- nova/virt/libvirt_conn.py | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 94fbf19ff..187f1399f 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -540,7 +540,7 @@ class CloudController(object): now = datetime.datetime.utcnow() return {"InstanceId": ec2_id, "Timestamp": now, - "output": base64.b64encode(output)} + "output": base64.b64encode(output.encode('utf-8','replace'))} def get_ajax_console(self, context, instance_id, **kwargs): ec2_id = instance_id[0] diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 61564cb7d..709b72bbb 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -713,7 +713,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_("Get console output for instance %s"), instance_id, context=context) - return self.driver.get_console_output(instance_ref) + output = self.driver.get_console_output(instance_ref) + return output.decode('utf8','replace') @exception.wrap_exception def get_ajax_console(self, context, instance_id): diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index f271c03f2..311adfd5b 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -290,7 +290,8 @@ class CloudTestCase(test.TestCase): instance_id = rv['instancesSet'][0]['instanceId'] output = self.cloud.get_console_output(context=self.context, instance_id=[instance_id]) - self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE?OUTPUT') + self.assertEquals(b64decode(output['output']).decode('utf-8'), + u'FAKE CONSOLE\ufffdOUTPUT') # TODO(soren): We need this until we can stop polling in the rpc code # for unit tests. greenthread.sleep(0.3) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 59189277d..832487deb 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -367,7 +367,7 @@ class FakeConnection(driver.ComputeDriver): return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L] def get_console_output(self, instance): - return 'FAKE CONSOLE\xffOUTPUT'.decode('utf8','replace').encode('ascii','replace') + return 'FAKE CONSOLE\xffOUTPUT' def get_ajax_console(self, instance): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 54186ced0..15adcccee 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -695,7 +695,7 @@ class LibvirtConnection(driver.ComputeDriver): else: fpath = console_log - return self._dump_file(fpath).decode('utf8','replace').encode('ascii','replace') + return self._dump_file(fpath) @exception.wrap_exception def get_ajax_console(self, instance): -- cgit From bf889f68e3efbf0ca388912b6c93ef61c5a8e7ad Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 3 May 2011 12:25:19 -0400 Subject: removing class imports --- nova/api/openstack/limits.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index f582fd1b6..47bc238f1 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -34,8 +34,6 @@ from nova import wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack.views import limits as limits_views -from nova.wsgi import Controller -from nova.wsgi import Middleware # Convenience constants for the limits dictionary passed to Limiter(). @@ -197,7 +195,7 @@ DEFAULT_LIMITS = [ ] -class RateLimitingMiddleware(Middleware): +class RateLimitingMiddleware(wsgi.Middleware): """ Rate-limits requests passing through this middleware. All limit information is stored in memory for this implementation. @@ -211,7 +209,7 @@ class RateLimitingMiddleware(Middleware): @param application: WSGI application to wrap @param limits: List of dictionaries describing limits """ - Middleware.__init__(self, application) + wsgi.Middleware.__init__(self, application) self._limiter = Limiter(limits or DEFAULT_LIMITS) @wsgify(RequestClass=wsgi.Request) -- cgit From 29e9aa173ea20a7d5cb816ce7478d6c0c2c38b80 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 3 May 2011 12:32:40 -0400 Subject: adding debug log message --- nova/api/openstack/servers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a238e6c82..3cf78e32c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -692,6 +692,7 @@ class ControllerV11(Controller): image_ref = info["rebuild"]["imageRef"] except (KeyError, TypeError): msg = _("Could not parse imageRef from request.") + LOG.debug(msg) return faults.Fault(exc.HTTPBadRequest(explanation=msg)) image_id = common.get_id_from_href(image_ref) -- cgit From 2c966851089c5c5267195a96612b4f764b52c09a Mon Sep 17 00:00:00 2001 From: Lvov Maxim Date: Tue, 3 May 2011 21:16:03 +0400 Subject: looking for default flagfile --- nova/utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nova/utils.py b/nova/utils.py index bfcf79216..42aa0f3ae 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -232,9 +232,12 @@ def default_flagfile(filename='nova.conf'): # turn relative filename into an absolute path script_dir = os.path.dirname(inspect.stack()[-1][1]) filename = os.path.abspath(os.path.join(script_dir, filename)) - if os.path.exists(filename): - flagfile = ['--flagfile=%s' % filename] - sys.argv = sys.argv[:1] + flagfile + sys.argv[1:] + if not os.path.exists(filename): + filename = "./nova.conf" + if not os.path.exists(filename): + filename = '/etc/nova/nova.conf' + flagfile = ['--flagfile=%s' % filename] + sys.argv = sys.argv[:1] + flagfile + sys.argv[1:] def debug(arg): -- cgit From 3dc9cbfd4fc04b86742507419cc09e749c6af663 Mon Sep 17 00:00:00 2001 From: Lvov Maxim Date: Tue, 3 May 2011 21:16:58 +0400 Subject: reduce policy for countyname --- nova/CA/openssl.cnf.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/CA/openssl.cnf.tmpl b/nova/CA/openssl.cnf.tmpl index b80fadf40..f87d9f3b2 100644 --- a/nova/CA/openssl.cnf.tmpl +++ b/nova/CA/openssl.cnf.tmpl @@ -46,7 +46,7 @@ policy = policy_match # RHEL 6 and Fedora 14 (using openssl-1.0.0-4.el6.x86_64 or # openssl-1.0.0d-1.fc14.x86_64) [ policy_match ] -countryName = match +countryName = supplied stateOrProvinceName = supplied organizationName = optional organizationalUnitName = optional -- cgit From 36aa631dfdea4d2041df3a60d1a294f6a80807b7 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Tue, 3 May 2011 22:34:00 +0400 Subject: Add missed hyphen. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 709b72bbb..93eb547ba 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -714,7 +714,7 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.audit(_("Get console output for instance %s"), instance_id, context=context) output = self.driver.get_console_output(instance_ref) - return output.decode('utf8','replace') + return output.decode('utf-8','replace') @exception.wrap_exception def get_ajax_console(self, context, instance_id): -- cgit From f50c7260fa0b1dbcfb725bedeb9bb0ed4398f767 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 3 May 2011 15:02:55 -0700 Subject: tests and better driver loading --- nova/exception.py | 8 +++++ nova/scheduler/query.py | 50 ++++++++++++++++----------- nova/tests/test_query.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 20 deletions(-) create mode 100644 nova/tests/test_query.py diff --git a/nova/exception.py b/nova/exception.py index e8444cb14..586ec009b 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -449,6 +449,14 @@ class ZoneNotFound(NotFound): message = _("Zone %(zone_id)s could not be found.") +class SchedulerQueryDriverNotFound(NotFound): + message = _("Scheduler Query Driver %(driver_name)s could not be found.") + + +class BadSchedulerQueryDriver(NotFound): + message = _("Invalid Scheduler Query Driver selected.") + + class InstanceMetadataNotFound(NotFound): message = _("Instance %(instance_id)s has no metadata with " "key %(metadata_key)s.") diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 5379b3d44..4971bc7e4 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -15,39 +15,38 @@ """ Query is a plug-in mechanism for requesting instance resources. -Three plug-ins are included: PassThru, Flavor & JSON. PassThru just +Three plug-ins are included: AllHosts, Flavor & JSON. AllHosts just returns the full, unfiltered list of hosts. Flavor is a hard coded matching mechanism based on flavor criteria and JSON is an ad-hoc query grammar. """ from nova import exception +from nova import flags +from nova import log as logging from nova import utils +LOG = logging.getLogger('nova.scheduler.query') + +FLAGS = flags.FLAGS +flags.DEFINE_string('default_query_engine', + 'nova.scheduler.query.AllHostsQuery', + 'Which query engine to use for filtering hosts.') + + class Query: """Base class for query plug-ins.""" def instance_type_to_query(self, instance_type): """Convert instance_type into a query for most common use-case.""" - raise exception.Error(_("Query driver not specified.")) + raise exception.BadSchedulerQueryDriver() def filter_hosts(self, zone_manager, query): """Return a list of hosts that fulfill the query.""" - raise exception.Error(_("Query driver not specified.")) - + raise exception.BadSchedulerQueryDriver() -def load_driver(driver_name): - resource = utils.import_class(driver_name) - if type(resource) != types.ClassType: - continue - cls = resource - if not issubclass(cls, Query): - raise exception.Error(_("Query driver does not derive " - "from nova.scheduler.query.Query.")) - return cls - -class PassThruQuery: +class AllHostsQuery: """NOP query plug-in. Returns all hosts in ZoneManager. This essentially does what the old Scheduler+Chance used to give us.""" @@ -59,7 +58,7 @@ class PassThruQuery: def filter_hosts(self, zone_manager, query): """Return a list of hosts from ZoneManager list.""" - hosts = zone_manager.service_state.get('compute', {}) + hosts = zone_manager.service_states.get('compute', {}) return [(host, capabilities) for host, capabilities in hosts.iteritems()] @@ -73,7 +72,7 @@ class FlavorQuery: def filter_hosts(self, zone_manager, query): """Return a list of hosts that can create instance_type.""" - hosts = zone_manager.service_state.get('compute', {}) + hosts = zone_manager.service_states.get('compute', {}) selected_hosts = [] instance_type = query for host, capabilities in hosts.iteritems(): @@ -148,8 +147,8 @@ class JsonQuery: '<=': _less_than_equal, '>=': _greater_than_equal, 'not': _not, - 'must', _must, - 'or', _or, + 'must': _must, + 'or': _or, } def instance_type_to_query(self, instance_type): @@ -161,4 +160,15 @@ class JsonQuery: return [] - +# Since the caller may specify which driver to use we need +# to have an authoritative list of what is permissible. +DRIVERS = [AllHostsQuery, FlavorQuery, JsonQuery] + + +def choose_driver(driver_name=None): + if not driver_name: + driver_name = FLAGS.default_query_engine + for driver in DRIVERS: + if str(driver) == driver_name: + return driver + raise exception.SchedulerQueryDriverNotFound(driver_name=driver_name) diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py new file mode 100644 index 000000000..0fab91933 --- /dev/null +++ b/nova/tests/test_query.py @@ -0,0 +1,89 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Tests For Scheduler Query Drivers +""" + +from nova import exception +from nova import flags +from nova import test +from nova.scheduler import query + +FLAGS = flags.FLAGS + +class FakeZoneManager: + pass + +class QueryTestCase(test.TestCase): + """Test case for query drivers.""" + + def _host_caps(self, multiplier): + return {'host_name-description':'XenServer %s' % multiplier, + 'host_hostname':'xs-%s' % multiplier, + 'host_memory':{'total': 100, + 'overhead': 5, + 'free': 5 + multiplier * 5, + 'free-computed': 5 + multiplier * 5}, + 'host_other-config':{}, + 'host_ip_address':'192.168.1.%d' % (100 + multiplier), + 'host_cpu_info':{}, + 'disk':{'available': 100 + multiplier * 100, + 'total': 1000, + 'used': 50 + multiplier * 50}, + 'host_uuid':'xxx-%d' % multiplier, + 'host_name-label':'xs-%s' % multiplier} + + def setUp(self): + self.old_flag = FLAGS.default_query_engine + FLAGS.default_query_engine = 'nova.scheduler.query.AllHostsQuery' + self.instance_type = dict(name='tiny', + memory_mb=500, + vcpus=10, + local_gb=50, + flavorid=1, + swap=500, + rxtx_quota=30000, + rxtx_cap=200) + + hosts = {} + for x in xrange(10): + hosts['host%s' % x] = self._host_caps(x) + + self.zone_manager = FakeZoneManager() + self.zone_manager.service_states = {} + self.zone_manager.service_states['compute'] = hosts + + def tearDown(self): + FLAGS.default_query_engine = self.old_flag + + def test_choose_driver(self): + driver = query.choose_driver() + self.assertEquals(str(driver), 'nova.scheduler.query.AllHostsQuery') + driver = query.choose_driver('nova.scheduler.query.FlavorQuery') + self.assertEquals(str(driver), 'nova.scheduler.query.FlavorQuery') + try: + query.choose_driver('does not exist') + self.fail("Should not find driver") + except exception.SchedulerQueryDriverNotFound: + pass + + def test_all_host_driver(self): + driver = query.AllHostsQuery() + cooked = driver.instance_type_to_query(self.instance_type) + hosts = driver.filter_hosts(self.zone_manager, cooked) + self.assertEquals(10, len(hosts)) + for host, capabilities in hosts: + self.assertTrue(host.startswith('host')) + -- cgit From ad07b86110b0bcb90f3b71bd423c06a6ff5f922d Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 4 May 2011 06:16:33 -0700 Subject: flavor test --- nova/scheduler/query.py | 6 +++--- nova/tests/test_query.py | 44 ++++++++++++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 4971bc7e4..1c593f1ba 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -74,10 +74,10 @@ class FlavorQuery: """Return a list of hosts that can create instance_type.""" hosts = zone_manager.service_states.get('compute', {}) selected_hosts = [] - instance_type = query + query_type, instance_type = query for host, capabilities in hosts.iteritems(): - host_ram_mb = capabilities.get['host_memory']['free'] - disk_bytes = capabilities.get['disk']['available'] + host_ram_mb = capabilities['host_memory']['free'] + disk_bytes = capabilities['disk']['available'] if host_ram_mb >= instance_type['memory_mb'] and \ disk_bytes >= instance_type['local_gb']: selected_hosts.append((host, capabilities)) diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 0fab91933..d89183e9f 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -30,32 +30,39 @@ class QueryTestCase(test.TestCase): """Test case for query drivers.""" def _host_caps(self, multiplier): + # Returns host capabilities in the following way: + # host0 = memory:free 10 (100max) + # disk:available 100 (1000max) + # hostN = memory:free 10 + 10N + # disk:available 100 + 100N + # in other words: hostN has more resources than host0 + # which means ... don't go above 10 hosts. return {'host_name-description':'XenServer %s' % multiplier, 'host_hostname':'xs-%s' % multiplier, 'host_memory':{'total': 100, - 'overhead': 5, - 'free': 5 + multiplier * 5, - 'free-computed': 5 + multiplier * 5}, + 'overhead': 10, + 'free': 10 + multiplier * 10, + 'free-computed': 10 + multiplier * 10}, 'host_other-config':{}, 'host_ip_address':'192.168.1.%d' % (100 + multiplier), 'host_cpu_info':{}, 'disk':{'available': 100 + multiplier * 100, 'total': 1000, - 'used': 50 + multiplier * 50}, + 'used': 0}, 'host_uuid':'xxx-%d' % multiplier, 'host_name-label':'xs-%s' % multiplier} def setUp(self): self.old_flag = FLAGS.default_query_engine FLAGS.default_query_engine = 'nova.scheduler.query.AllHostsQuery' - self.instance_type = dict(name='tiny', - memory_mb=500, - vcpus=10, - local_gb=50, - flavorid=1, - swap=500, - rxtx_quota=30000, - rxtx_cap=200) + self.instance_type = dict(name= 'tiny', + memory_mb= 50, + vcpus= 10, + local_gb= 500, + flavorid= 1, + swap= 500, + rxtx_quota= 30000, + rxtx_cap= 200) hosts = {} for x in xrange(10): @@ -69,10 +76,13 @@ class QueryTestCase(test.TestCase): FLAGS.default_query_engine = self.old_flag def test_choose_driver(self): + # Test default driver ... driver = query.choose_driver() self.assertEquals(str(driver), 'nova.scheduler.query.AllHostsQuery') + # Test valid driver ... driver = query.choose_driver('nova.scheduler.query.FlavorQuery') self.assertEquals(str(driver), 'nova.scheduler.query.FlavorQuery') + # Test invalid driver ... try: query.choose_driver('does not exist') self.fail("Should not find driver") @@ -87,3 +97,13 @@ class QueryTestCase(test.TestCase): for host, capabilities in hosts: self.assertTrue(host.startswith('host')) + def test_flavor_driver(self): + driver = query.FlavorQuery() + # filter all hosts that can support 50 ram and 500 disk + cooked = driver.instance_type_to_query(self.instance_type) + hosts = driver.filter_hosts(self.zone_manager, cooked) + self.assertEquals(6, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + self.assertEquals('host4', just_hosts[0]) + self.assertEquals('host9', just_hosts[5]) -- cgit From 1c16765a8378819596c1d5fb6178e2167da9ca52 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 4 May 2011 23:09:06 +0200 Subject: It's ok if there's no commit history. Otherwise the test suite in the tarball will fail. --- nova/tests/test_misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_misc.py b/nova/tests/test_misc.py index ad62b48bf..cf8f4c05e 100644 --- a/nova/tests/test_misc.py +++ b/nova/tests/test_misc.py @@ -68,7 +68,7 @@ class ProjectTestCase(test.TestCase): contributors.add(str_dict_replace(email, mailmap)) else: - self.assertTrue(False, 'Cannot read commit history') + return for contributor in contributors: if contributor == 'nova-core': -- cgit From 772fc58644871f7ee0bb880074c89d79871e197a Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 4 May 2011 14:48:23 -0700 Subject: json parser --- nova/scheduler/query.py | 123 ++++++++++++++++++++++++++++++++++++++++------- nova/tests/test_query.py | 25 +++++++--- 2 files changed, 124 insertions(+), 24 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 1c593f1ba..1b84e6b1d 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -21,6 +21,8 @@ matching mechanism based on flavor criteria and JSON is an ad-hoc query grammar. """ +import json + from nova import exception from nova import flags from nova import log as logging @@ -58,9 +60,8 @@ class AllHostsQuery: def filter_hosts(self, zone_manager, query): """Return a list of hosts from ZoneManager list.""" - hosts = zone_manager.service_states.get('compute', {}) - return [(host, capabilities) - for host, capabilities in hosts.iteritems()] + return [(host, services) + for host, services in zone_manager.service_state.iteritems()] class FlavorQuery: @@ -72,10 +73,10 @@ class FlavorQuery: def filter_hosts(self, zone_manager, query): """Return a list of hosts that can create instance_type.""" - hosts = zone_manager.service_states.get('compute', {}) + instance_type = query selected_hosts = [] - query_type, instance_type = query - for host, capabilities in hosts.iteritems(): + for host, services in zone_manager.service_states.iteritems(): + capabilities = services.get('compute', {}) host_ram_mb = capabilities['host_memory']['free'] disk_bytes = capabilities['disk']['available'] if host_ram_mb >= instance_type['memory_mb'] and \ @@ -113,31 +114,74 @@ class JsonQuery: """Query plug-in to allow simple JSON-based grammar for selecting hosts.""" def _equals(self, args): - pass + """First term is == all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs != rhs: + return False + return True def _less_than(self, args): - pass + """First term is < all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs >= rhs: + return False + return True def _greater_than(self, args): - pass + """First term is > all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs <= rhs: + return False + return True def _in(self, args): - pass + """First term is in set of remaining terms""" + if len(args) < 2: + return False + return args[0] in args[1:] def _less_than_equal(self, args): - pass + """First term is <= all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs > rhs: + return False + return True def _greater_than_equal(self, args): - pass + """First term is >= all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs < rhs: + return False + return True def _not(self, args): - pass + if len(args) == 0: + return False + return not args[0] def _must(self, args): - pass + return True def _or(self, args): - pass + return True in args + + def _and(self, args): + return False not in args commands = { '=': _equals, @@ -149,15 +193,60 @@ class JsonQuery: 'not': _not, 'must': _must, 'or': _or, + 'and': _and, } def instance_type_to_query(self, instance_type): """Convert instance_type into JSON query object.""" - return (str(self.__class__), instance_type) + required_ram = instance_type['memory_mb'] + required_disk = instance_type['local_gb'] + query = ['and', + ['>=', '$compute.host_memory.free', required_ram], + ['>=', '$compute.disk.available', required_disk] + ] + return (str(self.__class__), json.dumps(query)) + + def _parse_string(self, string, host, services): + """Strings prefixed with $ are capability lookups in the + form '$service.capability[.subcap*]'""" + if not string: + return None + if string[0] != '$': + return string + + path = string[1:].split('.') + for item in path: + services = services.get(item, None) + if not services: + return None + return services + + def _process_query(self, zone_manager, query, host, services): + if len(query) == 0: + return True + cmd = query[0] + method = self.commands[cmd] # Let exception fly. + cooked_args = [] + for arg in query[1:]: + if isinstance(arg, list): + arg = self._process_query(zone_manager, arg, host, services) + elif isinstance(arg, basestring): + arg = self._parse_string(arg, host, services) + if arg != None: + cooked_args.append(arg) + result = method(self, cooked_args) + print "*** %s %s = %s" % (cmd, cooked_args, result) + return result def filter_hosts(self, zone_manager, query): """Return a list of hosts that can fulfill query.""" - return [] + expanded = json.loads(query) + hosts = [] + for host, services in zone_manager.service_states.iteritems(): + print "-----" + if self._process_query(zone_manager, expanded, host, services): + hosts.append((host, services)) + return hosts # Since the caller may specify which driver to use we need diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index d89183e9f..7a036a4d8 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -64,13 +64,11 @@ class QueryTestCase(test.TestCase): rxtx_quota= 30000, rxtx_cap= 200) - hosts = {} - for x in xrange(10): - hosts['host%s' % x] = self._host_caps(x) - self.zone_manager = FakeZoneManager() - self.zone_manager.service_states = {} - self.zone_manager.service_states['compute'] = hosts + states = {} + for x in xrange(10): + states['host%s' % x] = {'compute': self._host_caps(x)} + self.zone_manager.service_states = states def tearDown(self): FLAGS.default_query_engine = self.old_flag @@ -100,7 +98,20 @@ class QueryTestCase(test.TestCase): def test_flavor_driver(self): driver = query.FlavorQuery() # filter all hosts that can support 50 ram and 500 disk - cooked = driver.instance_type_to_query(self.instance_type) + name, cooked = driver.instance_type_to_query(self.instance_type) + self.assertEquals('nova.scheduler.query.FlavorQuery', name) + hosts = driver.filter_hosts(self.zone_manager, cooked) + self.assertEquals(6, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + self.assertEquals('host4', just_hosts[0]) + self.assertEquals('host9', just_hosts[5]) + + def test_json_driver(self): + driver = query.JsonQuery() + # filter all hosts that can support 50 ram and 500 disk + name, cooked = driver.instance_type_to_query(self.instance_type) + self.assertEquals('nova.scheduler.query.JsonQuery', name) hosts = driver.filter_hosts(self.zone_manager, cooked) self.assertEquals(6, len(hosts)) just_hosts = [host for host, caps in hosts] -- cgit From d733eaf6749a5163165119ad164c817c3d7110b4 Mon Sep 17 00:00:00 2001 From: Nirmal Ranganathan Date: Wed, 4 May 2011 17:29:29 -0500 Subject: Adding a test case to show the xml deserialization failure for imageRef and flavorRef --- nova/tests/api/openstack/test_servers.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 5c643fcef..6d3177e64 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1648,6 +1648,19 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""", request = self.deserializer.deserialize(serial_request) self.assertEqual(request, expected) + def test_request_xmlser_with_flavor_image_ref(self): + serial_request = """ + + """ + request = self.deserializer.deserialize(serial_request) + self.assertEquals(request["server"]["flavorRef"], + "http://localhost:8774/v1.1/flavors/1") + self.assertEquals(request["server"]["imageRef"], + "http://localhost:8774/v1.1/images/1") + class TestServerInstanceCreation(test.TestCase): -- cgit From 5bd57d70135e599e068a6b66d402ce6c75c250cc Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 4 May 2011 21:50:54 -0400 Subject: added myself to Authors --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 1cdeeff99..99c07f7cd 100644 --- a/Authors +++ b/Authors @@ -78,6 +78,7 @@ Trey Morris Tushar Patil Vasiliy Shlykov Vishvananda Ishaya +William Wolf Yoshiaki Tamura Youcef Laribi Zhixue Wu -- cgit From 8f1d3ec3719f1c8cd587b653d380365ef0c16f51 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Thu, 5 May 2011 07:50:58 +0400 Subject: Moved all reencoding to compute manager to satisfy both Direct API and internal cloud call. --- nova/api/ec2/cloud.py | 2 +- nova/compute/manager.py | 2 +- nova/tests/test_cloud.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 998d339f8..092b80fa2 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -538,7 +538,7 @@ class CloudController(object): now = datetime.datetime.utcnow() return {"InstanceId": ec2_id, "Timestamp": now, - "output": base64.b64encode(output.encode('utf-8','replace'))} + "output": base64.b64encode(output)} def get_ajax_console(self, context, instance_id, **kwargs): ec2_id = instance_id[0] diff --git a/nova/compute/manager.py b/nova/compute/manager.py index e698b0255..84feb98ec 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -750,7 +750,7 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.audit(_("Get console output for instance %s"), instance_id, context=context) output = self.driver.get_console_output(instance_ref) - return output.decode('utf-8','replace') + return output.decode('utf-8','replace').encode('ascii','replace') @exception.wrap_exception def get_ajax_console(self, context, instance_id): diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 311adfd5b..f271c03f2 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -290,8 +290,7 @@ class CloudTestCase(test.TestCase): instance_id = rv['instancesSet'][0]['instanceId'] output = self.cloud.get_console_output(context=self.context, instance_id=[instance_id]) - self.assertEquals(b64decode(output['output']).decode('utf-8'), - u'FAKE CONSOLE\ufffdOUTPUT') + self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE?OUTPUT') # TODO(soren): We need this until we can stop polling in the rpc code # for unit tests. greenthread.sleep(0.3) -- cgit From 6ee9c2f2b9a7b359336cfad0c5c6b4e1ef78a0da Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Thu, 5 May 2011 07:53:04 +0400 Subject: Removed extra newline after get_console_output in fake virt driver. --- nova/virt/fake.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 832487deb..5ac376e46 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -369,7 +369,6 @@ class FakeConnection(driver.ComputeDriver): def get_console_output(self, instance): return 'FAKE CONSOLE\xffOUTPUT' - def get_ajax_console(self, instance): return {'token': 'FAKETOKEN', 'host': 'fakeajaxconsole.com', -- cgit From cc18ff47ff41ddefd7a31db5b772d55b2e312e8c Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 04:57:25 -0700 Subject: and or test --- nova/scheduler/query.py | 10 +++++----- nova/tests/test_query.py | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 1b84e6b1d..3233cc0d8 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -19,6 +19,10 @@ Three plug-ins are included: AllHosts, Flavor & JSON. AllHosts just returns the full, unfiltered list of hosts. Flavor is a hard coded matching mechanism based on flavor criteria and JSON is an ad-hoc query grammar. + +Note: These are hard filters. All capabilities used must be present +or the host will excluded. If you want soft filters use the weighting +mechanism which is intended for the more touchy-feely capabilities. """ import json @@ -61,7 +65,7 @@ class AllHostsQuery: def filter_hosts(self, zone_manager, query): """Return a list of hosts from ZoneManager list.""" return [(host, services) - for host, services in zone_manager.service_state.iteritems()] + for host, services in zone_manager.service_states.iteritems()] class FlavorQuery: @@ -174,9 +178,6 @@ class JsonQuery: return False return not args[0] - def _must(self, args): - return True - def _or(self, args): return True in args @@ -191,7 +192,6 @@ class JsonQuery: '<=': _less_than_equal, '>=': _greater_than_equal, 'not': _not, - 'must': _must, 'or': _or, 'and': _and, } diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 7a036a4d8..896b2364d 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -16,6 +16,8 @@ Tests For Scheduler Query Drivers """ +import json + from nova import exception from nova import flags from nova import test @@ -118,3 +120,27 @@ class QueryTestCase(test.TestCase): just_hosts.sort() self.assertEquals('host4', just_hosts[0]) self.assertEquals('host9', just_hosts[5]) + + # Try some custom queries + + raw = ['or', + ['and', + ['<', '$compute.host_memory.free', 30], + ['<', '$compute.disk.available', 300] + ], + ['and', + ['>', '$compute.host_memory.free', 70], + ['>', '$compute.disk.available', 700] + ] + ] + cooked = json.dumps(raw) + hosts = driver.filter_hosts(self.zone_manager, cooked) + + self.assertEquals(5, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + self.assertEquals('host0', just_hosts[0]) + self.assertEquals('host1', just_hosts[1]) + self.assertEquals('host7', just_hosts[2]) + self.assertEquals('host8', just_hosts[3]) + self.assertEquals('host9', just_hosts[4]) -- cgit From 5f4fc98c9648fd3f124819e0f4a26cb1d2d7f0e8 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 04:59:26 -0700 Subject: and or test --- nova/tests/test_query.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 896b2364d..90ae80dc0 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -139,8 +139,5 @@ class QueryTestCase(test.TestCase): self.assertEquals(5, len(hosts)) just_hosts = [host for host, caps in hosts] just_hosts.sort() - self.assertEquals('host0', just_hosts[0]) - self.assertEquals('host1', just_hosts[1]) - self.assertEquals('host7', just_hosts[2]) - self.assertEquals('host8', just_hosts[3]) - self.assertEquals('host9', just_hosts[4]) + for index, host in zip([0, 1, 7, 8, 9], just_hosts): + self.assertEquals('host%d' % index, host) -- cgit From 5a066cf5c2b952371eea753dcd0f95f917d08744 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 05:29:31 -0700 Subject: not = --- nova/scheduler/query.py | 7 +++++-- nova/tests/test_query.py | 29 +++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 3233cc0d8..0279efc9e 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -176,7 +176,7 @@ class JsonQuery: def _not(self, args): if len(args) == 0: return False - return not args[0] + return [not arg for arg in args] def _or(self, args): return True in args @@ -244,7 +244,10 @@ class JsonQuery: hosts = [] for host, services in zone_manager.service_states.iteritems(): print "-----" - if self._process_query(zone_manager, expanded, host, services): + r = self._process_query(zone_manager, expanded, host, services) + if isinstance(r, list): + r = True in r + if r: hosts.append((host, services)) return hosts diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 90ae80dc0..9bfc83b75 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -33,7 +33,7 @@ class QueryTestCase(test.TestCase): def _host_caps(self, multiplier): # Returns host capabilities in the following way: - # host0 = memory:free 10 (100max) + # host1 = memory:free 10 (100max) # disk:available 100 (1000max) # hostN = memory:free 10 + 10N # disk:available 100 + 100N @@ -69,7 +69,7 @@ class QueryTestCase(test.TestCase): self.zone_manager = FakeZoneManager() states = {} for x in xrange(10): - states['host%s' % x] = {'compute': self._host_caps(x)} + states['host%02d' % (x + 1)] = {'compute': self._host_caps(x)} self.zone_manager.service_states = states def tearDown(self): @@ -106,8 +106,8 @@ class QueryTestCase(test.TestCase): self.assertEquals(6, len(hosts)) just_hosts = [host for host, caps in hosts] just_hosts.sort() - self.assertEquals('host4', just_hosts[0]) - self.assertEquals('host9', just_hosts[5]) + self.assertEquals('host05', just_hosts[0]) + self.assertEquals('host10', just_hosts[5]) def test_json_driver(self): driver = query.JsonQuery() @@ -118,8 +118,8 @@ class QueryTestCase(test.TestCase): self.assertEquals(6, len(hosts)) just_hosts = [host for host, caps in hosts] just_hosts.sort() - self.assertEquals('host4', just_hosts[0]) - self.assertEquals('host9', just_hosts[5]) + self.assertEquals('host05', just_hosts[0]) + self.assertEquals('host10', just_hosts[5]) # Try some custom queries @@ -139,5 +139,18 @@ class QueryTestCase(test.TestCase): self.assertEquals(5, len(hosts)) just_hosts = [host for host, caps in hosts] just_hosts.sort() - for index, host in zip([0, 1, 7, 8, 9], just_hosts): - self.assertEquals('host%d' % index, host) + for index, host in zip([1, 2, 8, 9, 10], just_hosts): + self.assertEquals('host%02d' % index, host) + + raw = ['not', + ['=', '$compute.host_memory.free', 30], + ] + cooked = json.dumps(raw) + hosts = driver.filter_hosts(self.zone_manager, cooked) + + self.assertEquals(9, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts): + self.assertEquals('host%02d' % index, host) + -- cgit From 4b03036214cff2fcaad079f84605737d7f9dc711 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 05:30:58 -0700 Subject: not = --- nova/tests/test_query.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 9bfc83b75..78e8ee9de 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -153,4 +153,14 @@ class QueryTestCase(test.TestCase): just_hosts.sort() for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts): self.assertEquals('host%02d' % index, host) + + raw = ['in', '$compute.host_memory.free', 20, 40, 60, 80, 100]] + cooked = json.dumps(raw) + hosts = driver.filter_hosts(self.zone_manager, cooked) + + self.assertEquals(5, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + for index, host in zip([2, 4, 6, 8, 10], just_hosts): + self.assertEquals('host%02d' % index, host) -- cgit From 31c7b40b89c076a32d8105219d623320c58b8166 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 06:01:56 -0700 Subject: pep8 --- nova/scheduler/query.py | 43 +++++++++++++++-------------- nova/tests/test_query.py | 71 ++++++++++++++++++++++++------------------------ 2 files changed, 58 insertions(+), 56 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 0279efc9e..4332f43b0 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -114,6 +114,7 @@ class FlavorQuery: #rxtx_quota = Column(Integer, nullable=False, default=0) #rxtx_cap = Column(Integer, nullable=False, default=0) + class JsonQuery: """Query plug-in to allow simple JSON-based grammar for selecting hosts.""" @@ -162,7 +163,7 @@ class JsonQuery: if lhs > rhs: return False return True - + def _greater_than_equal(self, args): """First term is >= all the other terms.""" if len(args) < 2: @@ -180,10 +181,10 @@ class JsonQuery: def _or(self, args): return True in args - + def _and(self, args): return False not in args - + commands = { '=': _equals, '<': _less_than, @@ -200,7 +201,7 @@ class JsonQuery: """Convert instance_type into JSON query object.""" required_ram = instance_type['memory_mb'] required_disk = instance_type['local_gb'] - query = ['and', + query = ['and', ['>=', '$compute.host_memory.free', required_ram], ['>=', '$compute.disk.available', required_disk] ] @@ -219,24 +220,24 @@ class JsonQuery: services = services.get(item, None) if not services: return None - return services + return services def _process_query(self, zone_manager, query, host, services): - if len(query) == 0: - return True - cmd = query[0] - method = self.commands[cmd] # Let exception fly. - cooked_args = [] - for arg in query[1:]: - if isinstance(arg, list): - arg = self._process_query(zone_manager, arg, host, services) - elif isinstance(arg, basestring): - arg = self._parse_string(arg, host, services) - if arg != None: - cooked_args.append(arg) - result = method(self, cooked_args) - print "*** %s %s = %s" % (cmd, cooked_args, result) - return result + if len(query) == 0: + return True + cmd = query[0] + method = self.commands[cmd] # Let exception fly. + cooked_args = [] + for arg in query[1:]: + if isinstance(arg, list): + arg = self._process_query(zone_manager, arg, host, services) + elif isinstance(arg, basestring): + arg = self._parse_string(arg, host, services) + if arg != None: + cooked_args.append(arg) + result = method(self, cooked_args) + print "*** %s %s = %s" % (cmd, cooked_args, result) + return result def filter_hosts(self, zone_manager, query): """Return a list of hosts that can fulfill query.""" @@ -253,7 +254,7 @@ class JsonQuery: # Since the caller may specify which driver to use we need -# to have an authoritative list of what is permissible. +# to have an authoritative list of what is permissible. DRIVERS = [AllHostsQuery, FlavorQuery, JsonQuery] diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 78e8ee9de..8b894a4e6 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -1,4 +1,4 @@ -# Copyright 2011 OpenStack LLC. +# Copyright 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -25,9 +25,11 @@ from nova.scheduler import query FLAGS = flags.FLAGS + class FakeZoneManager: pass + class QueryTestCase(test.TestCase): """Test case for query drivers.""" @@ -39,32 +41,32 @@ class QueryTestCase(test.TestCase): # disk:available 100 + 100N # in other words: hostN has more resources than host0 # which means ... don't go above 10 hosts. - return {'host_name-description':'XenServer %s' % multiplier, - 'host_hostname':'xs-%s' % multiplier, - 'host_memory':{'total': 100, + return {'host_name-description': 'XenServer %s' % multiplier, + 'host_hostname': 'xs-%s' % multiplier, + 'host_memory': {'total': 100, 'overhead': 10, 'free': 10 + multiplier * 10, 'free-computed': 10 + multiplier * 10}, - 'host_other-config':{}, - 'host_ip_address':'192.168.1.%d' % (100 + multiplier), - 'host_cpu_info':{}, - 'disk':{'available': 100 + multiplier * 100, + 'host_other-config': {}, + 'host_ip_address': '192.168.1.%d' % (100 + multiplier), + 'host_cpu_info': {}, + 'disk': {'available': 100 + multiplier * 100, 'total': 1000, 'used': 0}, - 'host_uuid':'xxx-%d' % multiplier, - 'host_name-label':'xs-%s' % multiplier} + 'host_uuid': 'xxx-%d' % multiplier, + 'host_name-label': 'xs-%s' % multiplier} def setUp(self): self.old_flag = FLAGS.default_query_engine FLAGS.default_query_engine = 'nova.scheduler.query.AllHostsQuery' - self.instance_type = dict(name= 'tiny', - memory_mb= 50, - vcpus= 10, - local_gb= 500, - flavorid= 1, - swap= 500, - rxtx_quota= 30000, - rxtx_cap= 200) + self.instance_type = dict(name='tiny', + memory_mb=50, + vcpus=10, + local_gb=500, + flavorid=1, + swap=500, + rxtx_quota=30000, + rxtx_cap=200) self.zone_manager = FakeZoneManager() states = {} @@ -123,16 +125,16 @@ class QueryTestCase(test.TestCase): # Try some custom queries - raw = ['or', - ['and', - ['<', '$compute.host_memory.free', 30], - ['<', '$compute.disk.available', 300] - ], - ['and', - ['>', '$compute.host_memory.free', 70], - ['>', '$compute.disk.available', 700] - ] - ] + raw = ['or', + ['and', + ['<', '$compute.host_memory.free', 30], + ['<', '$compute.disk.available', 300] + ], + ['and', + ['>', '$compute.host_memory.free', 70], + ['>', '$compute.disk.available', 700] + ] + ] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) @@ -141,10 +143,10 @@ class QueryTestCase(test.TestCase): just_hosts.sort() for index, host in zip([1, 2, 8, 9, 10], just_hosts): self.assertEquals('host%02d' % index, host) - - raw = ['not', - ['=', '$compute.host_memory.free', 30], - ] + + raw = ['not', + ['=', '$compute.host_memory.free', 30], + ] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) @@ -153,8 +155,8 @@ class QueryTestCase(test.TestCase): just_hosts.sort() for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts): self.assertEquals('host%02d' % index, host) - - raw = ['in', '$compute.host_memory.free', 20, 40, 60, 80, 100]] + + raw = ['in', '$compute.host_memory.free', 20, 40, 60, 80, 100] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) @@ -163,4 +165,3 @@ class QueryTestCase(test.TestCase): just_hosts.sort() for index, host in zip([2, 4, 6, 8, 10], just_hosts): self.assertEquals('host%02d' % index, host) - -- cgit From 37954e665b874ac2358921d175b30617f456c007 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 07:35:44 -0700 Subject: flipped service_state in ZoneManager and fixed tests --- nova/scheduler/api.py | 8 +++----- nova/scheduler/manager.py | 7 +++---- nova/scheduler/zone_manager.py | 20 +++++++++----------- nova/tests/api/openstack/test_zones.py | 2 +- nova/tests/test_zones.py | 18 ++++++------------ 5 files changed, 22 insertions(+), 33 deletions(-) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 6bb3bf3cd..816ae5513 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -76,11 +76,9 @@ def zone_update(context, zone_id, data): return db.zone_update(context, zone_id, data) -def get_zone_capabilities(context, service=None): - """Returns a dict of key, value capabilities for this zone, - or for a particular class of services running in this zone.""" - return _call_scheduler('get_zone_capabilities', context=context, - params=dict(service=service)) +def get_zone_capabilities(context): + """Returns a dict of key, value capabilities for this zone.""" + return _call_scheduler('get_zone_capabilities', context=context) def update_service_capabilities(context, service_name, host, capabilities): diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 7d62cfc4e..55cd7208b 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -60,10 +60,9 @@ class SchedulerManager(manager.Manager): """Get a list of zones from the ZoneManager.""" return self.zone_manager.get_zone_list() - def get_zone_capabilities(self, context=None, service=None): - """Get the normalized set of capabilites for this zone, - or for a particular service.""" - return self.zone_manager.get_zone_capabilities(context, service) + def get_zone_capabilities(self, context=None): + """Get the normalized set of capabilites for this zone.""" + return self.zone_manager.get_zone_capabilities(context) def update_service_capabilities(self, context=None, service_name=None, host=None, capabilities={}): diff --git a/nova/scheduler/zone_manager.py b/nova/scheduler/zone_manager.py index 198f9d4cc..3ddf6f3c3 100644 --- a/nova/scheduler/zone_manager.py +++ b/nova/scheduler/zone_manager.py @@ -106,28 +106,26 @@ class ZoneManager(object): def __init__(self): self.last_zone_db_check = datetime.min self.zone_states = {} # { : ZoneState } - self.service_states = {} # { : { : { cap k : v }}} + self.service_states = {} # { : { : { cap k : v }}} self.green_pool = greenpool.GreenPool() def get_zone_list(self): """Return the list of zones we know about.""" return [zone.to_dict() for zone in self.zone_states.values()] - def get_zone_capabilities(self, context, service=None): + def get_zone_capabilities(self, context): """Roll up all the individual host info to generic 'service' capabilities. Each capability is aggregated into _min and _max values.""" - service_dict = self.service_states - if service: - service_dict = {service: self.service_states.get(service, {})} + hosts_dict = self.service_states # TODO(sandy) - be smarter about fabricating this structure. # But it's likely to change once we understand what the Best-Match # code will need better. combined = {} # { _ : (min, max), ... } - for service_name, host_dict in service_dict.iteritems(): - for host, caps_dict in host_dict.iteritems(): - for cap, value in caps_dict.iteritems(): + for host, host_dict in hosts_dict.iteritems(): + for service_name, service_dict in host_dict.iteritems(): + for cap, value in service_dict.iteritems(): key = "%s_%s" % (service_name, cap) min_value, max_value = combined.get(key, (value, value)) min_value = min(min_value, value) @@ -171,6 +169,6 @@ class ZoneManager(object): """Update the per-service capabilities based on this notification.""" logging.debug(_("Received %(service_name)s service update from " "%(host)s: %(capabilities)s") % locals()) - service_caps = self.service_states.get(service_name, {}) - service_caps[host] = capabilities - self.service_states[service_name] = service_caps + service_caps = self.service_states.get(host, {}) + service_caps[service_name] = capabilities + self.service_states[host] = service_caps diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index a3f191aaa..5d5799b59 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -75,7 +75,7 @@ def zone_get_all_db(context): ] -def zone_capabilities(method, context, params): +def zone_capabilities(method, context): return dict() diff --git a/nova/tests/test_zones.py b/nova/tests/test_zones.py index 688dc704d..e132809dc 100644 --- a/nova/tests/test_zones.py +++ b/nova/tests/test_zones.py @@ -78,38 +78,32 @@ class ZoneManagerTestCase(test.TestCase): def test_service_capabilities(self): zm = zone_manager.ZoneManager() - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, {}) zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2)) - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, dict(svc1_a=(1, 1), svc1_b=(2, 2))) zm.update_service_capabilities("svc1", "host1", dict(a=2, b=3)) - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, dict(svc1_a=(2, 2), svc1_b=(3, 3))) zm.update_service_capabilities("svc1", "host2", dict(a=20, b=30)) - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30))) zm.update_service_capabilities("svc10", "host1", dict(a=99, b=99)) - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30), svc10_a=(99, 99), svc10_b=(99, 99))) zm.update_service_capabilities("svc1", "host3", dict(c=5)) - caps = zm.get_zone_capabilities(self, None) + caps = zm.get_zone_capabilities(None) self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30), svc1_c=(5, 5), svc10_a=(99, 99), svc10_b=(99, 99))) - caps = zm.get_zone_capabilities(self, 'svc1') - self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30), - svc1_c=(5, 5))) - caps = zm.get_zone_capabilities(self, 'svc10') - self.assertEquals(caps, dict(svc10_a=(99, 99), svc10_b=(99, 99))) - def test_refresh_from_db_replace_existing(self): zm = zone_manager.ZoneManager() zone_state = zone_manager.ZoneState() -- cgit From 27f99a14a381062f87fbbb65d5aaa07914aa82c0 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 08:13:22 -0700 Subject: print statements removed --- nova/scheduler/query.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 4332f43b0..1a6705b42 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -236,7 +236,6 @@ class JsonQuery: if arg != None: cooked_args.append(arg) result = method(self, cooked_args) - print "*** %s %s = %s" % (cmd, cooked_args, result) return result def filter_hosts(self, zone_manager, query): @@ -244,7 +243,6 @@ class JsonQuery: expanded = json.loads(query) hosts = [] for host, services in zone_manager.service_states.iteritems(): - print "-----" r = self._process_query(zone_manager, expanded, host, services) if isinstance(r, list): r = True in r -- cgit From 9797bda1baacf5d90d6ce678c20396bacb513e37 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 10:50:59 -0700 Subject: merge prop fixes --- nova/exception.py | 4 ---- nova/scheduler/query.py | 37 ++++++++++++++++++++++++------------- nova/tests/test_query.py | 43 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index eeeed7952..50f50de9d 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -461,10 +461,6 @@ class SchedulerQueryDriverNotFound(NotFound): message = _("Scheduler Query Driver %(driver_name)s could not be found.") -class BadSchedulerQueryDriver(NotFound): - message = _("Invalid Scheduler Query Driver selected.") - - class InstanceMetadataNotFound(NotFound): message = _("Instance %(instance_id)s has no metadata with " "key %(metadata_key)s.") diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py index 1a6705b42..1e294b595 100644 --- a/nova/scheduler/query.py +++ b/nova/scheduler/query.py @@ -40,19 +40,23 @@ flags.DEFINE_string('default_query_engine', 'Which query engine to use for filtering hosts.') -class Query: +class Query(object): """Base class for query plug-ins.""" def instance_type_to_query(self, instance_type): """Convert instance_type into a query for most common use-case.""" - raise exception.BadSchedulerQueryDriver() + raise NotImplementedError() def filter_hosts(self, zone_manager, query): """Return a list of hosts that fulfill the query.""" - raise exception.BadSchedulerQueryDriver() + raise NotImplementedError() + def _full_name(self): + """module.classname of the Query object""" + return "%s.%s" % (self.__module__, self.__class__.__name__) -class AllHostsQuery: + +class AllHostsQuery(Query): """NOP query plug-in. Returns all hosts in ZoneManager. This essentially does what the old Scheduler+Chance used to give us.""" @@ -60,7 +64,7 @@ class AllHostsQuery: def instance_type_to_query(self, instance_type): """Return anything to prevent base-class from raising exception.""" - return (str(self.__class__), instance_type) + return (self._full_name(), instance_type) def filter_hosts(self, zone_manager, query): """Return a list of hosts from ZoneManager list.""" @@ -68,12 +72,12 @@ class AllHostsQuery: for host, services in zone_manager.service_states.iteritems()] -class FlavorQuery: +class FlavorQuery(Query): """Query plug-in hard-coded to work with flavors.""" def instance_type_to_query(self, instance_type): """Use instance_type to filter hosts.""" - return (str(self.__class__), instance_type) + return (self._full_name(), instance_type) def filter_hosts(self, zone_manager, query): """Return a list of hosts that can create instance_type.""" @@ -115,7 +119,7 @@ class FlavorQuery: #rxtx_cap = Column(Integer, nullable=False, default=0) -class JsonQuery: +class JsonQuery(Query): """Query plug-in to allow simple JSON-based grammar for selecting hosts.""" def _equals(self, args): @@ -175,14 +179,17 @@ class JsonQuery: return True def _not(self, args): + """Flip each of the arguments.""" if len(args) == 0: return False return [not arg for arg in args] def _or(self, args): + """True if any arg is True.""" return True in args def _and(self, args): + """True if all args are True.""" return False not in args commands = { @@ -205,7 +212,7 @@ class JsonQuery: ['>=', '$compute.host_memory.free', required_ram], ['>=', '$compute.disk.available', required_disk] ] - return (str(self.__class__), json.dumps(query)) + return (self._full_name(), json.dumps(query)) def _parse_string(self, string, host, services): """Strings prefixed with $ are capability lookups in the @@ -223,6 +230,7 @@ class JsonQuery: return services def _process_query(self, zone_manager, query, host, services): + """Recursively parse the query structure.""" if len(query) == 0: return True cmd = query[0] @@ -251,15 +259,18 @@ class JsonQuery: return hosts -# Since the caller may specify which driver to use we need -# to have an authoritative list of what is permissible. DRIVERS = [AllHostsQuery, FlavorQuery, JsonQuery] def choose_driver(driver_name=None): + """Since the caller may specify which driver to use we need + to have an authoritative list of what is permissible. This + function checks the driver name against a predefined set + of acceptable drivers.""" + if not driver_name: driver_name = FLAGS.default_query_engine for driver in DRIVERS: - if str(driver) == driver_name: - return driver + if "%s.%s" % (driver.__module__, driver.__name__) == driver_name: + return driver() raise exception.SchedulerQueryDriverNotFound(driver_name=driver_name) diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py index 8b894a4e6..9497a8c96 100644 --- a/nova/tests/test_query.py +++ b/nova/tests/test_query.py @@ -80,10 +80,12 @@ class QueryTestCase(test.TestCase): def test_choose_driver(self): # Test default driver ... driver = query.choose_driver() - self.assertEquals(str(driver), 'nova.scheduler.query.AllHostsQuery') + self.assertEquals(driver._full_name(), + 'nova.scheduler.query.AllHostsQuery') # Test valid driver ... driver = query.choose_driver('nova.scheduler.query.FlavorQuery') - self.assertEquals(str(driver), 'nova.scheduler.query.FlavorQuery') + self.assertEquals(driver._full_name(), + 'nova.scheduler.query.FlavorQuery') # Test invalid driver ... try: query.choose_driver('does not exist') @@ -165,3 +167,40 @@ class QueryTestCase(test.TestCase): just_hosts.sort() for index, host in zip([2, 4, 6, 8, 10], just_hosts): self.assertEquals('host%02d' % index, host) + + # Try some bogus input ... + raw = ['unknown command', ] + cooked = json.dumps(raw) + try: + driver.filter_hosts(self.zone_manager, cooked) + self.fail("Should give KeyError") + except KeyError, e: + pass + + self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps([]))) + self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps({}))) + self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps( + ['not', True, False, True, False] + ))) + + try: + driver.filter_hosts(self.zone_manager, json.dumps( + 'not', True, False, True, False + )) + self.fail("Should give KeyError") + except KeyError, e: + pass + + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['=', '$foo', 100] + ))) + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['=', '$.....', 100] + ))) + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['>', ['and', ['or', ['not', ['<', ['>=', ['<=', ['in', ]]]]]]]] + ))) + + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['=', {}, ['>', '$missing....foo']] + ))) -- cgit From 2cfa0ab48e981e31484287936fbc738d6073f473 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 5 May 2011 15:35:34 -0400 Subject: removing rogue TimeoutException --- nova/api/openstack/servers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3cf78e32c..547310613 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -594,10 +594,7 @@ class ControllerV10(Controller): def _parse_update(self, context, server_id, inst_dict, update_dict): if 'adminPass' in inst_dict['server']: update_dict['admin_pass'] = inst_dict['server']['adminPass'] - try: - self.compute_api.set_admin_password(context, server_id) - except exception.TimeoutException: - return exc.HTTPRequestTimeout() + self.compute_api.set_admin_password(context, server_id) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] -- cgit From 92294ab9b63ecac5baff3c01582faaba63e3b0b1 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 5 May 2011 15:37:53 -0400 Subject: fixed issue with non-existent variable being passed to ImageNotFound exception --- nova/image/local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/image/local.py b/nova/image/local.py index b6d8f3ba1..918180bae 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -102,7 +102,7 @@ class LocalImageService(service.BaseImageService): image = cantidate break if image is None: - raise exception.ImageNotFound(image_id=image_id) + raise exception.ImageNotFound(image_id=name) return image def get(self, context, image_id, data): -- cgit From 900e446a41a930b2585950e711948d6e45f37bdd Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 5 May 2011 15:38:45 -0400 Subject: added self to authors --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 1cdeeff99..8adcde4d2 100644 --- a/Authors +++ b/Authors @@ -78,6 +78,7 @@ Trey Morris Tushar Patil Vasiliy Shlykov Vishvananda Ishaya +William Wolf Yoshiaki Tamura Youcef Laribi Zhixue Wu -- cgit From 8d3f20e776af0fe174474a9fe8ee02eabe64053b Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 5 May 2011 15:56:04 -0400 Subject: Added interfaces to server controller --- nova/api/openstack/servers.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3cf78e32c..1d3eab65b 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -75,6 +75,26 @@ class Controller(common.OpenstackController): """ Returns a list of server details for a given user """ return self._items(req, is_detail=True) + def _image_id_from_req_data(self, data): + raise NotImplementedError + + def _flavor_id_from_req_data(self, data): + raise NotImplementedError + + def _get_view_builder(self, req): + raise NotImplementedError + + def _limit_items(self, items, req): + raise NotImplementedError + + def _limit_items(self, items, req): + raise NotImplementedError + + def _action_rebuild(self, info, request, instance_id): + raise NotImplementedError + + + def _items(self, req, is_detail): """Returns a list of servers for a given user. -- cgit From 5a3e8eea45bc11978112e3fed93768a1daf71530 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 5 May 2011 16:29:31 -0400 Subject: Added interface function to ViewBilder --- nova/api/openstack/flavors.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 40787bd17..dee70bb2b 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -45,6 +45,9 @@ class Controller(common.OpenstackController): items = self._get_flavors(req, is_detail=True) return dict(flavors=items) + def _get_view_builder(self, req): + raise NotImplementedError + def _get_flavors(self, req, is_detail=True): """Helper function that returns a list of flavor dicts.""" ctxt = req.environ['nova.context'] -- cgit From a5e7d039ec9ee9528186fa011021da00d809e683 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 5 May 2011 18:09:11 -0700 Subject: terminology: no more plug-ins or queries. They are host filters and drivers. --- nova/exception.py | 5 +- nova/scheduler/host_filter.py | 286 +++++++++++++++++++++++++++++++++++++++++ nova/scheduler/query.py | 276 --------------------------------------- nova/tests/test_host_filter.py | 208 ++++++++++++++++++++++++++++++ nova/tests/test_query.py | 206 ----------------------------- 5 files changed, 497 insertions(+), 484 deletions(-) create mode 100644 nova/scheduler/host_filter.py delete mode 100644 nova/scheduler/query.py create mode 100644 nova/tests/test_host_filter.py delete mode 100644 nova/tests/test_query.py diff --git a/nova/exception.py b/nova/exception.py index 50f50de9d..9905fb19b 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -457,8 +457,9 @@ class ZoneNotFound(NotFound): message = _("Zone %(zone_id)s could not be found.") -class SchedulerQueryDriverNotFound(NotFound): - message = _("Scheduler Query Driver %(driver_name)s could not be found.") +class SchedulerHostFilterDriverNotFound(NotFound): + message = _("Scheduler Host Filter Driver %(driver_name)s could" + " not be found.") class InstanceMetadataNotFound(NotFound): diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py new file mode 100644 index 000000000..aa6101c93 --- /dev/null +++ b/nova/scheduler/host_filter.py @@ -0,0 +1,286 @@ +# 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. + +""" +Host Filter is a driver mechanism for requesting instance resources. +Three drivers are included: AllHosts, Flavor & JSON. AllHosts just +returns the full, unfiltered list of hosts. Flavor is a hard coded +matching mechanism based on flavor criteria and JSON is an ad-hoc +filter grammar. + +Why JSON? The requests for instances may come in through the +REST interface from a user or a parent Zone. +Currently Flavors and/or InstanceTypes are used for +specifing the type of instance desired. Specific Nova users have +noted a need for a more expressive way of specifying instances. +Since we don't want to get into building full DSL this is a simple +form as an example of how this could be done. In reality, most +consumers will use the more rigid filters such as FlavorFilter. + +Note: These are hard filters. All capabilities used must be present +or the host will be excluded. If you want soft filters use the weighting +mechanism which is intended for the more touchy-feely capabilities. +""" + +import json + +from nova import exception +from nova import flags +from nova import log as logging +from nova import utils + +LOG = logging.getLogger('nova.scheduler.host_filter') + +FLAGS = flags.FLAGS +flags.DEFINE_string('default_host_filter_driver', + 'nova.scheduler.host_filter.AllHostsFilter', + 'Which driver to use for filtering hosts.') + + +class HostFilter(object): + """Base class for host filter drivers.""" + + def instance_type_to_filter(self, instance_type): + """Convert instance_type into a filter for most common use-case.""" + raise NotImplementedError() + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts that fulfill the filter.""" + raise NotImplementedError() + + def _full_name(self): + """module.classname of the filter driver""" + return "%s.%s" % (self.__module__, self.__class__.__name__) + + +class AllHostsFilter(HostFilter): + """NOP host filter driver. Returns all hosts in ZoneManager. + This essentially does what the old Scheduler+Chance used + to give us.""" + + def instance_type_to_filter(self, instance_type): + """Return anything to prevent base-class from raising + exception.""" + return (self._full_name(), instance_type) + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts from ZoneManager list.""" + return [(host, services) + for host, services in zone_manager.service_states.iteritems()] + + +class FlavorFilter(HostFilter): + """HostFilter driver hard-coded to work with flavors.""" + + def instance_type_to_filter(self, instance_type): + """Use instance_type to filter hosts.""" + return (self._full_name(), instance_type) + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts that can create instance_type.""" + instance_type = query + selected_hosts = [] + for host, services in zone_manager.service_states.iteritems(): + capabilities = services.get('compute', {}) + host_ram_mb = capabilities['host_memory']['free'] + disk_bytes = capabilities['disk']['available'] + if host_ram_mb >= instance_type['memory_mb'] and \ + disk_bytes >= instance_type['local_gb']: + selected_hosts.append((host, capabilities)) + return selected_hosts + +#host entries (currently) are like: +# {'host_name-description': 'Default install of XenServer', +# 'host_hostname': 'xs-mini', +# 'host_memory': {'total': 8244539392, +# 'overhead': 184225792, +# 'free': 3868327936, +# 'free-computed': 3840843776}, +# 'host_other-config': {}, +# 'host_ip_address': '192.168.1.109', +# 'host_cpu_info': {}, +# 'disk': {'available': 32954957824, +# 'total': 50394562560, +# 'used': 17439604736}, +# 'host_uuid': 'cedb9b39-9388-41df-8891-c5c9a0c0fe5f', +# 'host_name-label': 'xs-mini'} + +# instance_type table has: +#name = Column(String(255), unique=True) +#memory_mb = Column(Integer) +#vcpus = Column(Integer) +#local_gb = Column(Integer) +#flavorid = Column(Integer, unique=True) +#swap = Column(Integer, nullable=False, default=0) +#rxtx_quota = Column(Integer, nullable=False, default=0) +#rxtx_cap = Column(Integer, nullable=False, default=0) + + +class JsonFilter(HostFilter): + """Host Filter driver to allow simple JSON-based grammar for + selecting hosts.""" + + def _equals(self, args): + """First term is == all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs != rhs: + return False + return True + + def _less_than(self, args): + """First term is < all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs >= rhs: + return False + return True + + def _greater_than(self, args): + """First term is > all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs <= rhs: + return False + return True + + def _in(self, args): + """First term is in set of remaining terms""" + if len(args) < 2: + return False + return args[0] in args[1:] + + def _less_than_equal(self, args): + """First term is <= all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs > rhs: + return False + return True + + def _greater_than_equal(self, args): + """First term is >= all the other terms.""" + if len(args) < 2: + return False + lhs = args[0] + for rhs in args[1:]: + if lhs < rhs: + return False + return True + + def _not(self, args): + """Flip each of the arguments.""" + if len(args) == 0: + return False + return [not arg for arg in args] + + def _or(self, args): + """True if any arg is True.""" + return True in args + + def _and(self, args): + """True if all args are True.""" + return False not in args + + commands = { + '=': _equals, + '<': _less_than, + '>': _greater_than, + 'in': _in, + '<=': _less_than_equal, + '>=': _greater_than_equal, + 'not': _not, + 'or': _or, + 'and': _and, + } + + def instance_type_to_filter(self, instance_type): + """Convert instance_type into JSON filter object.""" + required_ram = instance_type['memory_mb'] + required_disk = instance_type['local_gb'] + query = ['and', + ['>=', '$compute.host_memory.free', required_ram], + ['>=', '$compute.disk.available', required_disk] + ] + return (self._full_name(), json.dumps(query)) + + def _parse_string(self, string, host, services): + """Strings prefixed with $ are capability lookups in the + form '$service.capability[.subcap*]'""" + if not string: + return None + if string[0] != '$': + return string + + path = string[1:].split('.') + for item in path: + services = services.get(item, None) + if not services: + return None + return services + + def _process_filter(self, zone_manager, query, host, services): + """Recursively parse the query structure.""" + if len(query) == 0: + return True + cmd = query[0] + method = self.commands[cmd] # Let exception fly. + cooked_args = [] + for arg in query[1:]: + if isinstance(arg, list): + arg = self._process_filter(zone_manager, arg, host, services) + elif isinstance(arg, basestring): + arg = self._parse_string(arg, host, services) + if arg != None: + cooked_args.append(arg) + result = method(self, cooked_args) + return result + + def filter_hosts(self, zone_manager, query): + """Return a list of hosts that can fulfill filter.""" + expanded = json.loads(query) + hosts = [] + for host, services in zone_manager.service_states.iteritems(): + r = self._process_filter(zone_manager, expanded, host, services) + if isinstance(r, list): + r = True in r + if r: + hosts.append((host, services)) + return hosts + + +DRIVERS = [AllHostsFilter, FlavorFilter, JsonFilter] + + +def choose_driver(driver_name=None): + """Since the caller may specify which driver to use we need + to have an authoritative list of what is permissible. This + function checks the driver name against a predefined set + of acceptable drivers.""" + + if not driver_name: + driver_name = FLAGS.default_host_filter_driver + for driver in DRIVERS: + if "%s.%s" % (driver.__module__, driver.__name__) == driver_name: + return driver() + raise exception.SchedulerHostFilterDriverNotFound(driver_name=driver_name) diff --git a/nova/scheduler/query.py b/nova/scheduler/query.py deleted file mode 100644 index 1e294b595..000000000 --- a/nova/scheduler/query.py +++ /dev/null @@ -1,276 +0,0 @@ -# Copyright (c) 2011 Openstack, LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Query is a plug-in mechanism for requesting instance resources. -Three plug-ins are included: AllHosts, Flavor & JSON. AllHosts just -returns the full, unfiltered list of hosts. Flavor is a hard coded -matching mechanism based on flavor criteria and JSON is an ad-hoc -query grammar. - -Note: These are hard filters. All capabilities used must be present -or the host will excluded. If you want soft filters use the weighting -mechanism which is intended for the more touchy-feely capabilities. -""" - -import json - -from nova import exception -from nova import flags -from nova import log as logging -from nova import utils - -LOG = logging.getLogger('nova.scheduler.query') - -FLAGS = flags.FLAGS -flags.DEFINE_string('default_query_engine', - 'nova.scheduler.query.AllHostsQuery', - 'Which query engine to use for filtering hosts.') - - -class Query(object): - """Base class for query plug-ins.""" - - def instance_type_to_query(self, instance_type): - """Convert instance_type into a query for most common use-case.""" - raise NotImplementedError() - - def filter_hosts(self, zone_manager, query): - """Return a list of hosts that fulfill the query.""" - raise NotImplementedError() - - def _full_name(self): - """module.classname of the Query object""" - return "%s.%s" % (self.__module__, self.__class__.__name__) - - -class AllHostsQuery(Query): - """NOP query plug-in. Returns all hosts in ZoneManager. - This essentially does what the old Scheduler+Chance used - to give us.""" - - def instance_type_to_query(self, instance_type): - """Return anything to prevent base-class from raising - exception.""" - return (self._full_name(), instance_type) - - def filter_hosts(self, zone_manager, query): - """Return a list of hosts from ZoneManager list.""" - return [(host, services) - for host, services in zone_manager.service_states.iteritems()] - - -class FlavorQuery(Query): - """Query plug-in hard-coded to work with flavors.""" - - def instance_type_to_query(self, instance_type): - """Use instance_type to filter hosts.""" - return (self._full_name(), instance_type) - - def filter_hosts(self, zone_manager, query): - """Return a list of hosts that can create instance_type.""" - instance_type = query - selected_hosts = [] - for host, services in zone_manager.service_states.iteritems(): - capabilities = services.get('compute', {}) - host_ram_mb = capabilities['host_memory']['free'] - disk_bytes = capabilities['disk']['available'] - if host_ram_mb >= instance_type['memory_mb'] and \ - disk_bytes >= instance_type['local_gb']: - selected_hosts.append((host, capabilities)) - return selected_hosts - -#host entries (currently) are like: -# {'host_name-description': 'Default install of XenServer', -# 'host_hostname': 'xs-mini', -# 'host_memory': {'total': 8244539392, -# 'overhead': 184225792, -# 'free': 3868327936, -# 'free-computed': 3840843776}, -# 'host_other-config': {}, -# 'host_ip_address': '192.168.1.109', -# 'host_cpu_info': {}, -# 'disk': {'available': 32954957824, -# 'total': 50394562560, -# 'used': 17439604736}, -# 'host_uuid': 'cedb9b39-9388-41df-8891-c5c9a0c0fe5f', -# 'host_name-label': 'xs-mini'} - -# instance_type table has: -#name = Column(String(255), unique=True) -#memory_mb = Column(Integer) -#vcpus = Column(Integer) -#local_gb = Column(Integer) -#flavorid = Column(Integer, unique=True) -#swap = Column(Integer, nullable=False, default=0) -#rxtx_quota = Column(Integer, nullable=False, default=0) -#rxtx_cap = Column(Integer, nullable=False, default=0) - - -class JsonQuery(Query): - """Query plug-in to allow simple JSON-based grammar for selecting hosts.""" - - def _equals(self, args): - """First term is == all the other terms.""" - if len(args) < 2: - return False - lhs = args[0] - for rhs in args[1:]: - if lhs != rhs: - return False - return True - - def _less_than(self, args): - """First term is < all the other terms.""" - if len(args) < 2: - return False - lhs = args[0] - for rhs in args[1:]: - if lhs >= rhs: - return False - return True - - def _greater_than(self, args): - """First term is > all the other terms.""" - if len(args) < 2: - return False - lhs = args[0] - for rhs in args[1:]: - if lhs <= rhs: - return False - return True - - def _in(self, args): - """First term is in set of remaining terms""" - if len(args) < 2: - return False - return args[0] in args[1:] - - def _less_than_equal(self, args): - """First term is <= all the other terms.""" - if len(args) < 2: - return False - lhs = args[0] - for rhs in args[1:]: - if lhs > rhs: - return False - return True - - def _greater_than_equal(self, args): - """First term is >= all the other terms.""" - if len(args) < 2: - return False - lhs = args[0] - for rhs in args[1:]: - if lhs < rhs: - return False - return True - - def _not(self, args): - """Flip each of the arguments.""" - if len(args) == 0: - return False - return [not arg for arg in args] - - def _or(self, args): - """True if any arg is True.""" - return True in args - - def _and(self, args): - """True if all args are True.""" - return False not in args - - commands = { - '=': _equals, - '<': _less_than, - '>': _greater_than, - 'in': _in, - '<=': _less_than_equal, - '>=': _greater_than_equal, - 'not': _not, - 'or': _or, - 'and': _and, - } - - def instance_type_to_query(self, instance_type): - """Convert instance_type into JSON query object.""" - required_ram = instance_type['memory_mb'] - required_disk = instance_type['local_gb'] - query = ['and', - ['>=', '$compute.host_memory.free', required_ram], - ['>=', '$compute.disk.available', required_disk] - ] - return (self._full_name(), json.dumps(query)) - - def _parse_string(self, string, host, services): - """Strings prefixed with $ are capability lookups in the - form '$service.capability[.subcap*]'""" - if not string: - return None - if string[0] != '$': - return string - - path = string[1:].split('.') - for item in path: - services = services.get(item, None) - if not services: - return None - return services - - def _process_query(self, zone_manager, query, host, services): - """Recursively parse the query structure.""" - if len(query) == 0: - return True - cmd = query[0] - method = self.commands[cmd] # Let exception fly. - cooked_args = [] - for arg in query[1:]: - if isinstance(arg, list): - arg = self._process_query(zone_manager, arg, host, services) - elif isinstance(arg, basestring): - arg = self._parse_string(arg, host, services) - if arg != None: - cooked_args.append(arg) - result = method(self, cooked_args) - return result - - def filter_hosts(self, zone_manager, query): - """Return a list of hosts that can fulfill query.""" - expanded = json.loads(query) - hosts = [] - for host, services in zone_manager.service_states.iteritems(): - r = self._process_query(zone_manager, expanded, host, services) - if isinstance(r, list): - r = True in r - if r: - hosts.append((host, services)) - return hosts - - -DRIVERS = [AllHostsQuery, FlavorQuery, JsonQuery] - - -def choose_driver(driver_name=None): - """Since the caller may specify which driver to use we need - to have an authoritative list of what is permissible. This - function checks the driver name against a predefined set - of acceptable drivers.""" - - if not driver_name: - driver_name = FLAGS.default_query_engine - for driver in DRIVERS: - if "%s.%s" % (driver.__module__, driver.__name__) == driver_name: - return driver() - raise exception.SchedulerQueryDriverNotFound(driver_name=driver_name) diff --git a/nova/tests/test_host_filter.py b/nova/tests/test_host_filter.py new file mode 100644 index 000000000..31e40ae1d --- /dev/null +++ b/nova/tests/test_host_filter.py @@ -0,0 +1,208 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Tests For Scheduler Host Filter Drivers. +""" + +import json + +from nova import exception +from nova import flags +from nova import test +from nova.scheduler import host_filter + +FLAGS = flags.FLAGS + + +class FakeZoneManager: + pass + + +class HostFilterTestCase(test.TestCase): + """Test case for host filter drivers.""" + + def _host_caps(self, multiplier): + # Returns host capabilities in the following way: + # host1 = memory:free 10 (100max) + # disk:available 100 (1000max) + # hostN = memory:free 10 + 10N + # disk:available 100 + 100N + # in other words: hostN has more resources than host0 + # which means ... don't go above 10 hosts. + return {'host_name-description': 'XenServer %s' % multiplier, + 'host_hostname': 'xs-%s' % multiplier, + 'host_memory': {'total': 100, + 'overhead': 10, + 'free': 10 + multiplier * 10, + 'free-computed': 10 + multiplier * 10}, + 'host_other-config': {}, + 'host_ip_address': '192.168.1.%d' % (100 + multiplier), + 'host_cpu_info': {}, + 'disk': {'available': 100 + multiplier * 100, + 'total': 1000, + 'used': 0}, + 'host_uuid': 'xxx-%d' % multiplier, + 'host_name-label': 'xs-%s' % multiplier} + + def setUp(self): + self.old_flag = FLAGS.default_host_filter_driver + FLAGS.default_host_filter_driver = \ + 'nova.scheduler.host_filter.AllHostsFilter' + self.instance_type = dict(name='tiny', + memory_mb=50, + vcpus=10, + local_gb=500, + flavorid=1, + swap=500, + rxtx_quota=30000, + rxtx_cap=200) + + self.zone_manager = FakeZoneManager() + states = {} + for x in xrange(10): + states['host%02d' % (x + 1)] = {'compute': self._host_caps(x)} + self.zone_manager.service_states = states + + def tearDown(self): + FLAGS.default_host_filter_driver = self.old_flag + + def test_choose_driver(self): + # Test default driver ... + driver = host_filter.choose_driver() + self.assertEquals(driver._full_name(), + 'nova.scheduler.host_filter.AllHostsFilter') + # Test valid driver ... + driver = host_filter.choose_driver( + 'nova.scheduler.host_filter.FlavorFilter') + self.assertEquals(driver._full_name(), + 'nova.scheduler.host_filter.FlavorFilter') + # Test invalid driver ... + try: + host_filter.choose_driver('does not exist') + self.fail("Should not find driver") + except exception.SchedulerHostFilterDriverNotFound: + pass + + def test_all_host_driver(self): + driver = host_filter.AllHostsFilter() + cooked = driver.instance_type_to_filter(self.instance_type) + hosts = driver.filter_hosts(self.zone_manager, cooked) + self.assertEquals(10, len(hosts)) + for host, capabilities in hosts: + self.assertTrue(host.startswith('host')) + + def test_flavor_driver(self): + driver = host_filter.FlavorFilter() + # filter all hosts that can support 50 ram and 500 disk + name, cooked = driver.instance_type_to_filter(self.instance_type) + self.assertEquals('nova.scheduler.host_filter.FlavorFilter', name) + hosts = driver.filter_hosts(self.zone_manager, cooked) + self.assertEquals(6, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + self.assertEquals('host05', just_hosts[0]) + self.assertEquals('host10', just_hosts[5]) + + def test_json_driver(self): + driver = host_filter.JsonFilter() + # filter all hosts that can support 50 ram and 500 disk + name, cooked = driver.instance_type_to_filter(self.instance_type) + self.assertEquals('nova.scheduler.host_filter.JsonFilter', name) + hosts = driver.filter_hosts(self.zone_manager, cooked) + self.assertEquals(6, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + self.assertEquals('host05', just_hosts[0]) + self.assertEquals('host10', just_hosts[5]) + + # Try some custom queries + + raw = ['or', + ['and', + ['<', '$compute.host_memory.free', 30], + ['<', '$compute.disk.available', 300] + ], + ['and', + ['>', '$compute.host_memory.free', 70], + ['>', '$compute.disk.available', 700] + ] + ] + cooked = json.dumps(raw) + hosts = driver.filter_hosts(self.zone_manager, cooked) + + self.assertEquals(5, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + for index, host in zip([1, 2, 8, 9, 10], just_hosts): + self.assertEquals('host%02d' % index, host) + + raw = ['not', + ['=', '$compute.host_memory.free', 30], + ] + cooked = json.dumps(raw) + hosts = driver.filter_hosts(self.zone_manager, cooked) + + self.assertEquals(9, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts): + self.assertEquals('host%02d' % index, host) + + raw = ['in', '$compute.host_memory.free', 20, 40, 60, 80, 100] + cooked = json.dumps(raw) + hosts = driver.filter_hosts(self.zone_manager, cooked) + + self.assertEquals(5, len(hosts)) + just_hosts = [host for host, caps in hosts] + just_hosts.sort() + for index, host in zip([2, 4, 6, 8, 10], just_hosts): + self.assertEquals('host%02d' % index, host) + + # Try some bogus input ... + raw = ['unknown command', ] + cooked = json.dumps(raw) + try: + driver.filter_hosts(self.zone_manager, cooked) + self.fail("Should give KeyError") + except KeyError, e: + pass + + self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps([]))) + self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps({}))) + self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps( + ['not', True, False, True, False] + ))) + + try: + driver.filter_hosts(self.zone_manager, json.dumps( + 'not', True, False, True, False + )) + self.fail("Should give KeyError") + except KeyError, e: + pass + + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['=', '$foo', 100] + ))) + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['=', '$.....', 100] + ))) + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['>', ['and', ['or', ['not', ['<', ['>=', ['<=', ['in', ]]]]]]]] + ))) + + self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( + ['=', {}, ['>', '$missing....foo']] + ))) diff --git a/nova/tests/test_query.py b/nova/tests/test_query.py deleted file mode 100644 index 9497a8c96..000000000 --- a/nova/tests/test_query.py +++ /dev/null @@ -1,206 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Tests For Scheduler Query Drivers -""" - -import json - -from nova import exception -from nova import flags -from nova import test -from nova.scheduler import query - -FLAGS = flags.FLAGS - - -class FakeZoneManager: - pass - - -class QueryTestCase(test.TestCase): - """Test case for query drivers.""" - - def _host_caps(self, multiplier): - # Returns host capabilities in the following way: - # host1 = memory:free 10 (100max) - # disk:available 100 (1000max) - # hostN = memory:free 10 + 10N - # disk:available 100 + 100N - # in other words: hostN has more resources than host0 - # which means ... don't go above 10 hosts. - return {'host_name-description': 'XenServer %s' % multiplier, - 'host_hostname': 'xs-%s' % multiplier, - 'host_memory': {'total': 100, - 'overhead': 10, - 'free': 10 + multiplier * 10, - 'free-computed': 10 + multiplier * 10}, - 'host_other-config': {}, - 'host_ip_address': '192.168.1.%d' % (100 + multiplier), - 'host_cpu_info': {}, - 'disk': {'available': 100 + multiplier * 100, - 'total': 1000, - 'used': 0}, - 'host_uuid': 'xxx-%d' % multiplier, - 'host_name-label': 'xs-%s' % multiplier} - - def setUp(self): - self.old_flag = FLAGS.default_query_engine - FLAGS.default_query_engine = 'nova.scheduler.query.AllHostsQuery' - self.instance_type = dict(name='tiny', - memory_mb=50, - vcpus=10, - local_gb=500, - flavorid=1, - swap=500, - rxtx_quota=30000, - rxtx_cap=200) - - self.zone_manager = FakeZoneManager() - states = {} - for x in xrange(10): - states['host%02d' % (x + 1)] = {'compute': self._host_caps(x)} - self.zone_manager.service_states = states - - def tearDown(self): - FLAGS.default_query_engine = self.old_flag - - def test_choose_driver(self): - # Test default driver ... - driver = query.choose_driver() - self.assertEquals(driver._full_name(), - 'nova.scheduler.query.AllHostsQuery') - # Test valid driver ... - driver = query.choose_driver('nova.scheduler.query.FlavorQuery') - self.assertEquals(driver._full_name(), - 'nova.scheduler.query.FlavorQuery') - # Test invalid driver ... - try: - query.choose_driver('does not exist') - self.fail("Should not find driver") - except exception.SchedulerQueryDriverNotFound: - pass - - def test_all_host_driver(self): - driver = query.AllHostsQuery() - cooked = driver.instance_type_to_query(self.instance_type) - hosts = driver.filter_hosts(self.zone_manager, cooked) - self.assertEquals(10, len(hosts)) - for host, capabilities in hosts: - self.assertTrue(host.startswith('host')) - - def test_flavor_driver(self): - driver = query.FlavorQuery() - # filter all hosts that can support 50 ram and 500 disk - name, cooked = driver.instance_type_to_query(self.instance_type) - self.assertEquals('nova.scheduler.query.FlavorQuery', name) - hosts = driver.filter_hosts(self.zone_manager, cooked) - self.assertEquals(6, len(hosts)) - just_hosts = [host for host, caps in hosts] - just_hosts.sort() - self.assertEquals('host05', just_hosts[0]) - self.assertEquals('host10', just_hosts[5]) - - def test_json_driver(self): - driver = query.JsonQuery() - # filter all hosts that can support 50 ram and 500 disk - name, cooked = driver.instance_type_to_query(self.instance_type) - self.assertEquals('nova.scheduler.query.JsonQuery', name) - hosts = driver.filter_hosts(self.zone_manager, cooked) - self.assertEquals(6, len(hosts)) - just_hosts = [host for host, caps in hosts] - just_hosts.sort() - self.assertEquals('host05', just_hosts[0]) - self.assertEquals('host10', just_hosts[5]) - - # Try some custom queries - - raw = ['or', - ['and', - ['<', '$compute.host_memory.free', 30], - ['<', '$compute.disk.available', 300] - ], - ['and', - ['>', '$compute.host_memory.free', 70], - ['>', '$compute.disk.available', 700] - ] - ] - cooked = json.dumps(raw) - hosts = driver.filter_hosts(self.zone_manager, cooked) - - self.assertEquals(5, len(hosts)) - just_hosts = [host for host, caps in hosts] - just_hosts.sort() - for index, host in zip([1, 2, 8, 9, 10], just_hosts): - self.assertEquals('host%02d' % index, host) - - raw = ['not', - ['=', '$compute.host_memory.free', 30], - ] - cooked = json.dumps(raw) - hosts = driver.filter_hosts(self.zone_manager, cooked) - - self.assertEquals(9, len(hosts)) - just_hosts = [host for host, caps in hosts] - just_hosts.sort() - for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts): - self.assertEquals('host%02d' % index, host) - - raw = ['in', '$compute.host_memory.free', 20, 40, 60, 80, 100] - cooked = json.dumps(raw) - hosts = driver.filter_hosts(self.zone_manager, cooked) - - self.assertEquals(5, len(hosts)) - just_hosts = [host for host, caps in hosts] - just_hosts.sort() - for index, host in zip([2, 4, 6, 8, 10], just_hosts): - self.assertEquals('host%02d' % index, host) - - # Try some bogus input ... - raw = ['unknown command', ] - cooked = json.dumps(raw) - try: - driver.filter_hosts(self.zone_manager, cooked) - self.fail("Should give KeyError") - except KeyError, e: - pass - - self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps([]))) - self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps({}))) - self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps( - ['not', True, False, True, False] - ))) - - try: - driver.filter_hosts(self.zone_manager, json.dumps( - 'not', True, False, True, False - )) - self.fail("Should give KeyError") - except KeyError, e: - pass - - self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( - ['=', '$foo', 100] - ))) - self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( - ['=', '$.....', 100] - ))) - self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( - ['>', ['and', ['or', ['not', ['<', ['>=', ['<=', ['in', ]]]]]]]] - ))) - - self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps( - ['=', {}, ['>', '$missing....foo']] - ))) -- cgit From 57fbc3f748389410bad29a82e685e0af2ee26646 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 6 May 2011 06:50:48 +0400 Subject: Added myself to Authors file. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 1cdeeff99..8a1571a09 100644 --- a/Authors +++ b/Authors @@ -80,4 +80,5 @@ Vasiliy Shlykov Vishvananda Ishaya Yoshiaki Tamura Youcef Laribi +Yuriy Taraday Zhixue Wu -- cgit From 6160e3dbdf0dcc736fb650d025da89b269edbf59 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 6 May 2011 09:13:46 +0400 Subject: Add two whitespaces to conform PEP8. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 84feb98ec..c6f957073 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -750,7 +750,7 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.audit(_("Get console output for instance %s"), instance_id, context=context) output = self.driver.get_console_output(instance_ref) - return output.decode('utf-8','replace').encode('ascii','replace') + return output.decode('utf-8', 'replace').encode('ascii', 'replace') @exception.wrap_exception def get_ajax_console(self, context, instance_id): -- cgit From d3c89daed971151bec57f66b279876bf926998c6 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 5 May 2011 22:44:08 -0700 Subject: Improved error notification in network create --- bin/nova-manage | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 2f6af6e2d..51a77b0ec 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -523,8 +523,10 @@ class NetworkCommands(object): [network_size=FLAG], [vlan_start=FLAG], [vpn_start=FLAG], [fixed_range_v6=FLAG]""" if not fixed_range: - raise TypeError(_('Fixed range in the form of 10.0.0.0/8 is ' - 'required to create networks.')) + msg = _('Fixed range in the form of 10.0.0.0/8 is ' + 'required to create networks.') + print msg + raise TypeError(msg) if not num_networks: num_networks = FLAGS.num_networks if not network_size: @@ -536,14 +538,19 @@ class NetworkCommands(object): if not fixed_range_v6: fixed_range_v6 = FLAGS.fixed_range_v6 net_manager = utils.import_object(FLAGS.network_manager) - net_manager.create_networks(context.get_admin_context(), - cidr=fixed_range, - num_networks=int(num_networks), - network_size=int(network_size), - vlan_start=int(vlan_start), - vpn_start=int(vpn_start), - cidr_v6=fixed_range_v6, - label=label) + try: + net_manager.create_networks(context.get_admin_context(), + cidr=fixed_range, + num_networks=int(num_networks), + network_size=int(network_size), + vlan_start=int(vlan_start), + vpn_start=int(vpn_start), + cidr_v6=fixed_range_v6, + label=label) + except ValueError, e: + print e + raise e + def list(self): """List all created networks""" -- cgit From 315b1ebdccdbe1d99111cfed71f0de6791f6cd7c Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 5 May 2011 23:23:09 -0700 Subject: pep8 fix --- bin/nova-manage | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 51a77b0ec..ccd2d7ed6 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -550,7 +550,6 @@ class NetworkCommands(object): except ValueError, e: print e raise e - def list(self): """List all created networks""" -- cgit From 21b84aae209a8e02910293f3adc60048e6ef9ce5 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 5 May 2011 23:25:15 -0700 Subject: spacing fix --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index ccd2d7ed6..bc5df1485 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -524,7 +524,7 @@ class NetworkCommands(object): [vpn_start=FLAG], [fixed_range_v6=FLAG]""" if not fixed_range: msg = _('Fixed range in the form of 10.0.0.0/8 is ' - 'required to create networks.') + 'required to create networks.') print msg raise TypeError(msg) if not num_networks: -- cgit From a8919644dfd91ea83654aa34d41680523af27234 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 6 May 2011 09:26:40 -0400 Subject: Removed incorrect, unreachable code --- nova/tests/db/fakes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 58d251b1e..8bdea359a 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -124,7 +124,6 @@ def stub_out_db_instance_api(stubs, injected=True): return FakeModel(vlan_network_fields) else: return FakeModel(flat_network_fields) - return FakeModel(network_fields) def fake_network_get_all_by_instance(context, instance_id): # Even instance numbers are on vlan networks -- cgit From c02ba694e9c5793980f0678c616fac16687f7407 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 6 May 2011 10:02:21 -0400 Subject: Explicitly casted a str to a str to please pylint --- nova/tests/test_virt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 1311ba361..d743f94f7 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -642,7 +642,7 @@ class LibvirtConnTestCase(test.TestCase): try: conn.spawn(instance, network_info) except Exception, e: - count = (0 <= e.message.find('Unexpected method call')) + count = (0 <= str(e.message).find('Unexpected method call')) self.assertTrue(count) -- cgit From fa9eeb65533d897f6e81067986dc614582fb310a Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 6 May 2011 07:19:57 -0700 Subject: grabbed from dist-sched branch --- nova/compute/manager.py | 17 ++++++++++++ nova/virt/hyperv.py | 9 +++++++ nova/virt/libvirt_conn.py | 8 ++++++ nova/virt/xenapi_conn.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 1ff78007b..473f93170 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1082,6 +1082,13 @@ class ComputeManager(manager.SchedulerDependentManager): unicode(ex)) error_list.append(ex) + try: + self._report_driver_status() + except Exception as ex: + LOG.warning(_("Error during report_driver_status(): %s"), + unicode(ex)) + error_list.append(ex) + try: self._poll_instance_states(context) except Exception as ex: @@ -1091,6 +1098,16 @@ class ComputeManager(manager.SchedulerDependentManager): return error_list + def _report_driver_status(self): + curr_time = time.time() + if curr_time - self._last_host_check > FLAGS.host_state_interval: + self._last_host_check = curr_time + LOG.info(_("Updating host status")) + # This will grab info about the host and queue it + # to be sent to the Schedulers. + self.update_service_capabilities( + self.driver.get_host_stats(refresh=True)) + def _poll_instance_states(self, context): vm_instances = self.driver.list_instances_detail() vm_instances = dict((vm.name, vm) for vm in vm_instances) diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 9026e737e..573e5130e 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -486,3 +486,12 @@ class HyperVConnection(driver.ComputeDriver): def update_available_resource(self, ctxt, host): """This method is supported only by libvirt.""" return + + def update_host_status(self): + """See xenapi_conn.py implementation.""" + pass + + def get_host_stats(self, refresh=False): + """See xenapi_conn.py implementation.""" + pass + diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9780c69a6..555e44ce2 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1582,6 +1582,14 @@ class LibvirtConnection(driver.ComputeDriver): """See comments of same method in firewall_driver.""" self.firewall_driver.unfilter_instance(instance_ref) + def update_host_status(self): + """See xenapi_conn.py implementation.""" + pass + + def get_host_stats(self, refresh=False): + """See xenapi_conn.py implementation.""" + pass + class FirewallDriver(object): def prepare_instance_filter(self, instance, network_info=None): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 0cabccf08..63a53af2e 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -168,6 +168,13 @@ class XenAPIConnection(driver.ComputeDriver): session = XenAPISession(url, user, pw) self._vmops = VMOps(session) self._volumeops = VolumeOps(session) + self._host_state = None + + @property + def HostState(self): + if not self._host_state: + self._host_state = HostState(self.session) + return self._host_state def init_host(self, host): #FIXME(armando): implement this @@ -315,6 +322,16 @@ class XenAPIConnection(driver.ComputeDriver): """This method is supported only by libvirt.""" raise NotImplementedError('This method is supported only by libvirt.') + def update_host_status(self): + """Update the status info of the host, and return those values + to the calling program.""" + return self.HostState.update_status() + + def get_host_stats(self, refresh=False): + """Return the current state of the host. If 'refresh' is + True, run the update first.""" + return self.HostState.get_host_stats(refresh=refresh) + class XenAPISession(object): """The session to invoke XenAPI SDK calls""" @@ -436,6 +453,58 @@ class XenAPISession(object): raise +class HostState(object): + """Manages information about the XenServer host this compute + node is running on. + """ + def __init__(self, session): + super(HostState, self).__init__() + self._session = session + self._stats = {} + self.update_status() + + def get_host_stats(self, refresh=False): + """Return the current state of the host. If 'refresh' is + True, run the update first. + """ + if refresh: + self.update_status() + return self._stats + + def update_status(self): + """Since under Xenserver, a compute node runs on a given host, + we can get host status information using xenapi. + """ + LOG.debug(_("Updating host stats")) + # Make it something unlikely to match any actual instance ID + task_id = random.randint(-80000, -70000) + task = self._session.async_call_plugin("xenhost", "host_data", {}) + task_result = self._session.wait_for_task(task, task_id) + if not task_result: + task_result = json.dumps("") + try: + data = json.loads(task_result) + except ValueError as e: + # Invalid JSON object + LOG.error(_("Unable to get updated status: %s") % e) + return + # Get the SR usage + try: + sr_ref = vm_utils.safe_find_sr(self._session) + except exception.NotFound as e: + # No SR configured + LOG.error(_("Unable to get SR for this host: %s") % e) + return + sr_rec = self._session.get_xenapi().SR.get_record(sr_ref) + total = int(sr_rec["virtual_allocation"]) + used = int(sr_rec["physical_utilisation"]) + data["disk"] = dd = {} + dd["total"] = total + dd["used"] = used + dd["available"] = total - used + self._stats = data + + def _parse_xmlrpc_value(val): """Parse the given value as if it were an XML-RPC value. This is sometimes used as the format for the task.result field.""" -- cgit From bbb2bba0f05493ec40c70279b532b26a4a4c235c Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 6 May 2011 10:48:11 -0400 Subject: Added stub function for a referenced, previously non-existant function --- nova/api/ec2/cloud.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 092b80fa2..40ae06750 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -366,6 +366,9 @@ class CloudController(object): g['ipPermissions'] += [r] return g + def _get_instance(instance_id): + raise NotImplementedError + def _revoke_rule_args_to_dict(self, context, to_port=None, from_port=None, ip_protocol=None, cidr_ip=None, user_id=None, source_security_group_name=None, -- cgit From a845a769671066a684e38b82c34ded7ded55eed4 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 6 May 2011 09:06:46 -0700 Subject: revised file docs --- nova/scheduler/host_filter.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index aa6101c93..ed8e65c77 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -29,9 +29,11 @@ Since we don't want to get into building full DSL this is a simple form as an example of how this could be done. In reality, most consumers will use the more rigid filters such as FlavorFilter. -Note: These are hard filters. All capabilities used must be present -or the host will be excluded. If you want soft filters use the weighting -mechanism which is intended for the more touchy-feely capabilities. +Note: These are "required" capability filters. These capabilities +used must be present or the host will be excluded. The hosts +returned are then weighed by the Weighted Scheduler. Weights +can take the more esoteric factors into consideration (such as +server affinity and customer separation). """ import json -- cgit From 595f742763336bb4edeb55f7556ce618bf85481e Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 6 May 2011 11:04:00 -0700 Subject: tests pass again --- nova/tests/test_compute.py | 9 +++++++++ nova/virt/hyperv.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 393110791..55e7ae0c4 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -21,6 +21,7 @@ Tests For Compute import datetime import mox +import stubout from nova import compute from nova import context @@ -52,6 +53,10 @@ class FakeTime(object): self.counter += t +def nop_report_driver_status(self): + pass + + class ComputeTestCase(test.TestCase): """Test case for compute""" def setUp(self): @@ -649,6 +654,10 @@ class ComputeTestCase(test.TestCase): def test_run_kill_vm(self): """Detect when a vm is terminated behind the scenes""" + self.stubs = stubout.StubOutForTesting() + self.stubs.Set(compute_manager.ComputeManager, + '_report_driver_status', nop_report_driver_status) + instance_id = self._create_instance() self.compute.run_instance(self.context, instance_id) diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 573e5130e..1142e97a4 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -494,4 +494,3 @@ class HyperVConnection(driver.ComputeDriver): def get_host_stats(self, refresh=False): """See xenapi_conn.py implementation.""" pass - -- cgit From 4a0142bdcd3d15c629ca96ba6d3d7fdaa13ed278 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 6 May 2011 14:11:18 -0400 Subject: added test for show_by_name ImageNotFound exception --- nova/tests/api/openstack/test_images.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index e5dd93c3f..2c329f920 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -75,6 +75,18 @@ class _BaseImageServiceTests(test.TestCase): self.context, 'bad image id') + def test_create_and_show_non_existing_image_by_name(self): + fixture = self._make_fixture('test image') + num_images = len(self.service.index(self.context)) + + image_id = self.service.create(self.context, fixture)['id'] + + self.assertNotEquals(None, image_id) + self.assertRaises(exception.ImageNotFound, + self.service.show_by_name, + self.context, + 'bad image id') + def test_update(self): fixture = self._make_fixture('test image') image_id = self.service.create(self.context, fixture)['id'] -- cgit From 2946a21f78e4fd2b18bd6eb8c85eb2cc0c764f8a Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 6 May 2011 14:13:27 -0400 Subject: Added interface functions --- nova/api/ec2/cloud.py | 2 +- nova/api/openstack/servers.py | 14 ++++++-------- nova/api/openstack/views/limits.py | 9 +++++++++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 40ae06750..9aa4e7778 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -367,7 +367,7 @@ class CloudController(object): return g def _get_instance(instance_id): - raise NotImplementedError + raise NotImplementedError() def _revoke_rule_args_to_dict(self, context, to_port=None, from_port=None, ip_protocol=None, cidr_ip=None, user_id=None, diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1d3eab65b..2bf405545 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -76,24 +76,22 @@ class Controller(common.OpenstackController): return self._items(req, is_detail=True) def _image_id_from_req_data(self, data): - raise NotImplementedError + raise NotImplementedError() def _flavor_id_from_req_data(self, data): - raise NotImplementedError + raise NotImplementedError() def _get_view_builder(self, req): - raise NotImplementedError + raise NotImplementedError() def _limit_items(self, items, req): - raise NotImplementedError + raise NotImplementedError() def _limit_items(self, items, req): - raise NotImplementedError + raise NotImplementedError() def _action_rebuild(self, info, request, instance_id): - raise NotImplementedError - - + raise NotImplementedError() def _items(self, req, is_detail): """Returns a list of servers for a given user. diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index 552db39ee..22d1c260d 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -23,6 +23,15 @@ from nova.api.openstack import common class ViewBuilder(object): """Openstack API base limits view builder.""" + def _build_rate_limits(self, rate_limits): + raise NotImplementedError() + + def _build_rate_limit(self, rate_limit): + raise NotImplementedError() + + def _build_absolute_limits(self, absolute_limit): + raise NotImplementedError() + def build(self, rate_limits, absolute_limits): rate_limits = self._build_rate_limits(rate_limits) absolute_limits = self._build_absolute_limits(absolute_limits) -- cgit From 417de9b7a695b088c2525470f971455b727c8c38 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 6 May 2011 14:23:51 -0400 Subject: Added Python packages needed for coverage reports to virtualenv packages --- tools/pip-requires | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/pip-requires b/tools/pip-requires index 2f4136732..e438c2a41 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -31,3 +31,5 @@ sphinx glance nova-adminclient suds==0.4 +coverage +nosexcover -- cgit From 791f8dc895b97caa9395a52113823bde37ae6cfa Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 6 May 2011 13:47:47 -0500 Subject: Set root password upon XenServer instance creation. --- nova/compute/api.py | 22 ++++++++++++-- nova/compute/manager.py | 34 ++++++++++++++-------- nova/tests/api/openstack/test_servers.py | 6 ++++ nova/virt/xenapi/vmops.py | 16 +++++----- plugins/xenserver/xenapi/etc/xapi.d/plugins/agent | 1 - .../xenapi/etc/xapi.d/plugins/xenstore.py | 6 ++-- 6 files changed, 60 insertions(+), 25 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index be26d8ca3..e8adf6ba5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -482,6 +482,20 @@ class API(base.Base): """Generic handler for RPC calls to the scheduler.""" rpc.cast(context, FLAGS.scheduler_topic, args) + def _find_host(self, context, instance_id): + """Find the host associated with an instance.""" + host = None + for count in xrange(10): + instance = self.get(context, instance_id) + host = instance["host"] + if host: + return host + elif count >= 10: + raise exception.Error(_("Unable to find host for Instance %s") + % instance_id) + else: + time.sleep(1) + def snapshot(self, context, instance_id, name): """Snapshot the given instance. @@ -635,8 +649,12 @@ class API(base.Base): def set_admin_password(self, context, instance_id, password=None): """Set the root/admin password for the given instance.""" - self._cast_compute_message( - 'set_admin_password', context, instance_id, password) + host = self._find_host(context, instance_id) + + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "set_admin_password", + "args": {"instance_id": instance_id, "new_pass": password}}) def inject_file(self, context, instance_id): """Write a file to the given instance.""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 1ff78007b..d92f7719a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -40,6 +40,7 @@ import os import socket import sys import tempfile +import time import functools from eventlet import greenthread @@ -404,21 +405,30 @@ class ComputeManager(manager.SchedulerDependentManager): def set_admin_password(self, context, instance_id, new_pass=None): """Set the root/admin password for an instance on this host.""" context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) - instance_id = instance_ref['id'] - instance_state = instance_ref['state'] - expected_state = power_state.RUNNING - if instance_state != expected_state: - LOG.warn(_('trying to reset the password on a non-running ' - 'instance: %(instance_id)s (state: %(instance_state)s ' - 'expected: %(expected_state)s)') % locals()) - LOG.audit(_('instance %s: setting admin password'), - instance_ref['name']) + if new_pass is None: # Generate a random password new_pass = utils.generate_password(FLAGS.password_length) - self.driver.set_admin_password(instance_ref, new_pass) - self._update_state(context, instance_id) + + while True: + instance_ref = self.db.instance_get(context, instance_id) + instance_id = instance_ref["id"] + instance_state = instance_ref["state"] + expected_state = power_state.RUNNING + + if instance_state != expected_state: + time.sleep(5) + continue + else: + try: + LOG.audit(_("Instance %s: Setting root password"), + instance_ref["name"]) + self.driver.set_admin_password(instance_ref, new_pass) + break + except Exception, e: + # Catch all here because this could be anything. + LOG.exception(e) + continue @exception.wrap_exception @checks_instance_lock diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 5c643fcef..89edece42 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -134,6 +134,10 @@ def fake_compute_api(cls, req, id): return True +def find_host(self, context, instance_id): + return "nova" + + class ServersTest(test.TestCase): def setUp(self): @@ -473,6 +477,7 @@ class ServersTest(test.TestCase): "_get_kernel_ramdisk_from_image", kernel_ramdisk_mapping) self.stubs.Set(nova.api.openstack.common, "get_image_id_from_image_hash", image_id_from_hash) + self.stubs.Set(nova.compute.api.API, "_find_host", find_host) def _test_create_instance_helper(self): self._setup_for_create_instance() @@ -767,6 +772,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_update', server_update) + self.stubs.Set(nova.compute.api.API, "_find_host", find_host) req = webob.Request.blank('/v1.0/servers/1') req.method = 'PUT' diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 30f31517d..a13febdab 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -428,15 +428,16 @@ class VMOps(object): """ # Need to uniquely identify this request. - transaction_id = str(uuid.uuid4()) + key_init_transaction_id = str(uuid.uuid4()) # The simple Diffie-Hellman class is used to manage key exchange. dh = SimpleDH() - args = {'id': transaction_id, 'pub': str(dh.get_public())} - resp = self._make_agent_call('key_init', instance, '', args) + key_init_args = {'id': key_init_transaction_id, + 'pub': str(dh.get_public())} + resp = self._make_agent_call('key_init', instance, '', key_init_args) if resp is None: # No response from the agent return - resp_dict = json.loads(resp) + resp_dict = json.loads(json.loads(resp)) # Successful return code from key_init is 'D0' if resp_dict['returncode'] != 'D0': # There was some sort of error; the message will contain @@ -446,12 +447,13 @@ class VMOps(object): dh.compute_shared(agent_pub) enc_pass = dh.encrypt(new_pass) # Send the encrypted password - args['enc_pass'] = enc_pass - resp = self._make_agent_call('password', instance, '', args) + password_transaction_id = str(uuid.uuid4()) + password_args = {'id': password_transaction_id, 'enc_pass': enc_pass} + resp = self._make_agent_call('password', instance, '', password_args) if resp is None: # No response from the agent return - resp_dict = json.loads(resp) + resp_dict = json.loads(json.loads(resp)) # Successful return code from password is '0' if resp_dict['returncode'] != '0': raise RuntimeError(resp_dict['message']) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent index 5496a6bd5..0bb377dd3 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent @@ -80,7 +80,6 @@ def password(self, arg_dict): previous call to key_init. The encrypted password value should be passed as the value for the 'enc_pass' key in arg_dict. """ - pub = int(arg_dict["pub"]) enc_pass = arg_dict["enc_pass"] arg_dict["value"] = json.dumps({"name": "password", "value": enc_pass}) request_id = arg_dict["id"] diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py index d33c7346b..5b45a9845 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py @@ -59,12 +59,12 @@ def read_record(self, arg_dict): cmd = ["xenstore-read", "/local/domain/%(dom_id)s/%(path)s" % arg_dict] try: ret, result = _run_command(cmd) - return result.rstrip("\n") + return result.strip() except pluginlib.PluginError, e: if arg_dict.get("ignore_missing_path", False): cmd = ["xenstore-exists", "/local/domain/%(dom_id)s/%(path)s" % arg_dict] - ret, result = _run_command(cmd).strip() + ret, result = _run_command(cmd) # If the path exists, the cmd should return "0" if ret != 0: # No such path, so ignore the error and return the @@ -180,7 +180,7 @@ def _run_command(cmd): err = proc.stderr.read() if err: raise pluginlib.PluginError(err) - return proc.stdout.read() + return (ret, proc.stdout.read()) if __name__ == "__main__": -- cgit From 311c774e1109d6ce0449f0d06346078020ffa4e0 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 6 May 2011 14:48:54 -0500 Subject: Review feedback --- nova/compute/api.py | 12 ++++++------ nova/compute/manager.py | 4 ++-- nova/virt/xenapi/vmops.py | 4 ++-- plugins/xenserver/xenapi/etc/xapi.d/plugins/agent | 2 -- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index e8adf6ba5..a7276ad2d 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -485,16 +485,16 @@ class API(base.Base): def _find_host(self, context, instance_id): """Find the host associated with an instance.""" host = None - for count in xrange(10): + attempts = 10 + while attempts: instance = self.get(context, instance_id) host = instance["host"] if host: return host - elif count >= 10: - raise exception.Error(_("Unable to find host for Instance %s") - % instance_id) - else: - time.sleep(1) + attempts -= 1 + time.sleep(1) + raise exception.Error(_("Unable to find host for Instance %s") + % instance_id) def snapshot(self, context, instance_id, name): """Snapshot the given instance. diff --git a/nova/compute/manager.py b/nova/compute/manager.py index e75371741..ae5b50ef3 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -421,9 +421,9 @@ class ComputeManager(manager.SchedulerDependentManager): continue else: try: - LOG.audit(_("Instance %s: Setting root password"), - instance_ref["name"]) self.driver.set_admin_password(instance_ref, new_pass) + LOG.audit(_("Instance %s: Root password set"), + instance_ref["name"]) break except Exception, e: # Catch all here because this could be anything. diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index a13febdab..fe9a74dd6 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -437,7 +437,7 @@ class VMOps(object): if resp is None: # No response from the agent return - resp_dict = json.loads(json.loads(resp)) + resp_dict = json.loads(resp) # Successful return code from key_init is 'D0' if resp_dict['returncode'] != 'D0': # There was some sort of error; the message will contain @@ -453,7 +453,7 @@ class VMOps(object): if resp is None: # No response from the agent return - resp_dict = json.loads(json.loads(resp)) + resp_dict = json.loads(resp) # Successful return code from password is '0' if resp_dict['returncode'] != '0': raise RuntimeError(resp_dict['message']) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent index 0bb377dd3..9e761f264 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent @@ -53,7 +53,6 @@ class TimeoutError(StandardError): pass -@jsonify def key_init(self, arg_dict): """Handles the Diffie-Hellman key exchange with the agent to establish the shared secret key used to encrypt/decrypt sensitive @@ -72,7 +71,6 @@ def key_init(self, arg_dict): return resp -@jsonify def password(self, arg_dict): """Writes a request to xenstore that tells the agent to set the root password for the given VM. The password should be -- cgit From 8c336fa339d9038f5430f7ffd82df3a54e67196f Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 6 May 2011 15:06:21 -0500 Subject: Review feedback --- nova/compute/api.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index a7276ad2d..63884be97 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -484,14 +484,11 @@ class API(base.Base): def _find_host(self, context, instance_id): """Find the host associated with an instance.""" - host = None - attempts = 10 - while attempts: + for attempts in xrange(10): instance = self.get(context, instance_id) host = instance["host"] if host: return host - attempts -= 1 time.sleep(1) raise exception.Error(_("Unable to find host for Instance %s") % instance_id) -- cgit From 3a7d1422d52f551e870542305ce9bab9e9e6ebad Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 6 May 2011 16:13:35 -0400 Subject: Fixed method in flavors --- nova/api/openstack/flavors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index dee70bb2b..4c5971cf6 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -46,7 +46,7 @@ class Controller(common.OpenstackController): return dict(flavors=items) def _get_view_builder(self, req): - raise NotImplementedError + raise NotImplementedError() def _get_flavors(self, req, is_detail=True): """Helper function that returns a list of flavor dicts.""" -- cgit From 7860f72c911dd91b69082cb5cdb2e625710526c1 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 6 May 2011 15:19:55 -0500 Subject: Review feedback --- plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py index 5b45a9845..55f3911e7 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py @@ -171,7 +171,7 @@ def _paths_from_ls(recs): def _run_command(cmd): """Abstracts out the basics of issuing system commands. If the command returns anything in stderr, a PluginError is raised with that information. - Otherwise, the output from stdout is returned. + Otherwise, a tuple of (return code, stdout) is returned. """ pipe = subprocess.PIPE proc = subprocess.Popen(cmd, stdin=pipe, stdout=pipe, stderr=pipe, -- cgit From 6425095be3bd89c48b73b5305afeb2d5d45e434b Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 6 May 2011 15:41:36 -0500 Subject: Review feedback --- plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py index 55f3911e7..6c589ed29 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py @@ -171,7 +171,7 @@ def _paths_from_ls(recs): def _run_command(cmd): """Abstracts out the basics of issuing system commands. If the command returns anything in stderr, a PluginError is raised with that information. - Otherwise, a tuple of (return code, stdout) is returned. + Otherwise, a tuple of (return code, stdout data) is returned. """ pipe = subprocess.PIPE proc = subprocess.Popen(cmd, stdin=pipe, stdout=pipe, stderr=pipe, -- cgit From e1cfa28fc9e1194bbd4c9ce9c2f06ea3f6e5548e Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 6 May 2011 16:48:38 -0400 Subject: Fixed duplicate function --- nova/api/openstack/servers.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 2bf405545..71cb31c72 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -87,9 +87,6 @@ class Controller(common.OpenstackController): def _limit_items(self, items, req): raise NotImplementedError() - def _limit_items(self, items, req): - raise NotImplementedError() - def _action_rebuild(self, info, request, instance_id): raise NotImplementedError() -- cgit From 50404512a84971fb895f8f174230a7230b8f9474 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Fri, 6 May 2011 17:37:35 -0400 Subject: convert quota table to key-value --- bin/nova-manage | 5 +- nova/db/api.py | 30 ++-- nova/db/sqlalchemy/api.py | 50 ++++-- .../versions/016_make_quotas_key_and_value.py | 176 +++++++++++++++++++++ nova/db/sqlalchemy/models.py | 18 ++- nova/quota.py | 44 ++++-- nova/tests/test_quota.py | 9 +- 7 files changed, 277 insertions(+), 55 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py diff --git a/bin/nova-manage b/bin/nova-manage index 2f6af6e2d..c1144e3a0 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -397,11 +397,10 @@ class ProjectCommands(object): arguments: project_id [key] [value]""" ctxt = context.get_admin_context() if key: - quo = {'project_id': project_id, key: value} try: - db.quota_update(ctxt, project_id, quo) + db.quota_update(ctxt, project_id, key, value) except exception.NotFound: - db.quota_create(ctxt, quo) + db.quota_create(ctxt, project_id, key, value) project_quota = quota.get_quota(ctxt, project_id) for key, value in project_quota.iteritems(): print '%s: %s' % (key, value) diff --git a/nova/db/api.py b/nova/db/api.py index f9a4b5b4b..b5550fcbc 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -756,24 +756,34 @@ def auth_token_create(context, token): ################### -def quota_create(context, values): - """Create a quota from the values dictionary.""" - return IMPL.quota_create(context, values) +def quota_create(context, project_id, resource, limit): + """Create a quota for the given project and resource.""" + return IMPL.quota_create(context, project_id, resource, limit) -def quota_get(context, project_id): +def quota_get(context, project_id, resource): """Retrieve a quota or raise if it does not exist.""" - return IMPL.quota_get(context, project_id) + return IMPL.quota_get(context, project_id, resource) -def quota_update(context, project_id, values): - """Update a quota from the values dictionary.""" - return IMPL.quota_update(context, project_id, values) +def quota_get_all_by_project(context, project_id): + """Retrieve all quotas associated with a given project.""" + return IMPL.quota_get_all_by_project(context, project_id) -def quota_destroy(context, project_id): +def quota_update(context, project_id, resource, limit): + """Update a quota or raise if it does not exist""" + return IMPL.quota_update(context, project_id, resource, limit) + + +def quota_destroy(context, project_id, resource): """Destroy the quota or raise if it does not exist.""" - return IMPL.quota_destroy(context, project_id) + return IMPL.quota_destroy(context, project_id, resource) + + +def quota_destroy_all_by_project(context, project_id): + """Destroy all quotas associated with a given project.""" + return IMPL.quota_get_all_by_project(context, project_id) ################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 285b22a04..929959d8e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1496,45 +1496,71 @@ def auth_token_create(_context, token): @require_admin_context -def quota_get(context, project_id, session=None): +def quota_get(context, project_id, resource, session=None): if not session: session = get_session() - result = session.query(models.Quota).\ - filter_by(project_id=project_id).\ - filter_by(deleted=can_read_deleted(context)).\ - first() + filter_by(project_id=project_id).\ + filter_by(resource=resource).\ + filter_by(deleted=False).\ + first() if not result: raise exception.ProjectQuotaNotFound(project_id=project_id) + return result + +@require_admin_context +def quota_get_all_by_project(context, project_id): + session = get_session() + result = {'project_id': project_id} + rows = session.query(models.Quota).\ + filter_by(project_id=project_id).\ + filter_by(deleted=False).\ + all() + for row in rows: + result[row.resource] = row.limit return result @require_admin_context -def quota_create(context, values): +def quota_create(context, project_id, resource, limit): quota_ref = models.Quota() - quota_ref.update(values) + quota_ref.project_id = project_id + quota_ref.resource = resource + quota_ref.limit = limit quota_ref.save() return quota_ref @require_admin_context -def quota_update(context, project_id, values): +def quota_update(context, project_id, resource, limit): session = get_session() with session.begin(): - quota_ref = quota_get(context, project_id, session=session) - quota_ref.update(values) + quota_ref = quota_get(context, project_id, resource, session=session) + quota_ref.limit = limit quota_ref.save(session=session) @require_admin_context -def quota_destroy(context, project_id): +def quota_destroy(context, project_id, resource): session = get_session() with session.begin(): - quota_ref = quota_get(context, project_id, session=session) + quota_ref = quota_get(context, project_id, resource, session=session) quota_ref.delete(session=session) +@require_admin_context +def quota_destroy_all_by_project(context, project_id): + session = get_session() + with session.begin(): + quotas = session.query(models.Quota).\ + filter_by(project_id=project_id).\ + filter_by(deleted=False).\ + all() + for quota_ref in quotas: + quota_ref.delete(session=session) + + ################### diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py new file mode 100644 index 000000000..6bf2b19af --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py @@ -0,0 +1,176 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# +# 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 sqlalchemy import * +from migrate import * + +import datetime + +meta = MetaData() + +resources = [ + 'instances', + 'cores', + 'volumes', + 'gigabytes', + 'floating_ips', + 'metadata_items', +] + + +def old_style_quotas_table(name): + return Table(name, meta, + Column('id', Integer(), primary_key=True), + Column('created_at', DateTime(), + default=datetime.datetime.utcnow), + Column('updated_at', DateTime(), + onupdate=datetime.datetime.utcnow), + Column('deleted_at', DateTime()), + Column('deleted', Boolean(), default=False), + Column('project_id', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False)), + Column('instances', Integer()), + Column('cores', Integer()), + Column('volumes', Integer()), + Column('gigabytes', Integer()), + Column('floating_ips', Integer()), + Column('metadata_items', Integer()), + ) + + +def new_style_quotas_table(name): + return Table(name, meta, + Column('id', Integer(), primary_key=True), + Column('created_at', DateTime(), + default=datetime.datetime.utcnow), + Column('updated_at', DateTime(), + onupdate=datetime.datetime.utcnow), + Column('deleted_at', DateTime()), + Column('deleted', Boolean(), default=False), + Column('project_id', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False)), + Column('resource', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=False), + Column('limit', Integer(), nullable=True), + ) + + +def existing_quotas_table(migrate_engine): + return Table('quotas', meta, autoload=True, autoload_with=migrate_engine) + + +def _assert_no_duplicate_project_ids(quotas): + project_ids = set() + message = 'There are duplicate active quotas for project %s' + for quota in quotas: + assert quota.project_id not in project_ids, message % quota.project_id + project_ids.add(quota.project_id) + + +def assert_old_quotas_have_no_active_duplicates(migrate_engine, quotas): + """Ensure that there are no duplicate non-deleted quota entries.""" + select = quotas.select().where(quotas.c.deleted == False) + results = migrate_engine.execute(select) + _assert_no_duplicate_project_ids(list(results)) + + +def assert_new_quotas_have_no_active_duplicates(migrate_engine, quotas): + """Ensure that there are no duplicate non-deleted quota entries.""" + for resource in resources: + select = quotas.select().\ + where(quotas.c.deleted == False).\ + where(quotas.c.resource == resource) + results = migrate_engine.execute(select) + _assert_no_duplicate_project_ids(list(results)) + + +def convert_forward(migrate_engine, old_quotas, new_quotas): + quotas = list(migrate_engine.execute(old_quotas.select())) + for quota in quotas: + for resource in resources: + limit = getattr(old_quota, resource) + if limit is None: + continue + insert = new_quotas.insert().values( + created_at=quota.created_at, + updated_at=quota.updated_at, + deleted_at=quota.deleted_at, + deleted=quota.deleted, + project_id=quota.project_id, + resource=resource, + limit=limit) + migrate_engine.execute(insert) + + +def convert_backward(migrate_engine, old_quotas, new_quotas): + quotas = {} + for quota in migrate_engine.execute(new_quotas.select()): + if (quota.resource not in resources + or quota.limit is None or quota.deleted): + continue + if not quota.project_id in quotas: + quotas[quota.project_id] = { + 'project_id': quota.project_id, + 'created_at': quota.created_at, + 'updated_at': quota.updated_at, + quota.resource: quota.limit + } + else: + if quota.created_at < quotas[quota.project_id]['created_at']: + quotas[quota.project_id]['created_at'] = quota.created_at + if quota.updated_at > quotas[quota.project_id]['updated_at']: + quotas[quota.project_id]['updated_at'] = quota.updated_at + quotas[quota.project_id][quota.resource] = quota.limit + + for quota in quotas.itervalues(): + insert = old_quotas.insert().values(**quota) + migrate_engine.execute(insert) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + + old_quotas = existing_quotas_table(migrate_engine) + assert_old_quotas_have_no_active_duplicates(migrate_engine, old_quotas) + + new_quotas = new_style_quotas_table('quotas_new') + new_quotas.create() + convert_forward(migrate_engine, old_quotas, new_quotas) + old_quotas.drop() + new_quotas.rename('quotas') + + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + meta.bind = migrate_engine + + new_quotas = existing_quotas_table(migrate_engine) + assert_new_quotas_have_no_active_duplicates(migrate_engine, new_quotas) + + old_quotas = old_style_quotas_table('quotas_old') + old_quotas.create() + convert_backward(migrate_engine, old_quotas, new_quotas) + new_quotas.drop() + old_quotas.rename('quotas') diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 36a084a1d..e477040d3 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -313,18 +313,20 @@ class Volume(BASE, NovaBase): class Quota(BASE, NovaBase): - """Represents quota overrides for a project.""" + """Represents a single quota override for a project. + + If there is no row for a given project id and resource, then + the default for the deployment is used. If the row is present + but the limit is Null, then the resource is unlimited. + """ + __tablename__ = 'quotas' id = Column(Integer, primary_key=True) - project_id = Column(String(255)) + project_id = Column(String(255), index=True) - instances = Column(Integer) - cores = Column(Integer) - volumes = Column(Integer) - gigabytes = Column(Integer) - floating_ips = Column(Integer) - metadata_items = Column(Integer) + resource = Column(String(255)) + limit = Column(Integer, nullable=True) class ExportDevice(BASE, NovaBase): diff --git a/nova/quota.py b/nova/quota.py index d8b5d9a93..b6090c5b3 100644 --- a/nova/quota.py +++ b/nova/quota.py @@ -52,26 +52,31 @@ def get_quota(context, project_id): 'floating_ips': FLAGS.quota_floating_ips, 'metadata_items': FLAGS.quota_metadata_items} - try: - quota = db.quota_get(context, project_id) - for key in rval.keys(): - if quota[key] is not None: - rval[key] = quota[key] - except exception.NotFound: - pass + quota = db.quota_get_all_by_project(context, project_id) + for key in rval.keys(): + if key in quota: + rval[key] = quota[key] return rval +def _get_allowed_resources(requested, used, quota): + if quota is None: + return requested + return quota - used + + def allowed_instances(context, num_instances, instance_type): """Check quota and return min(num_instances, allowed_instances).""" project_id = context.project_id context = context.elevated() + num_cores = num_instances * instance_type['vcpus'] used_instances, used_cores = db.instance_data_get_for_project(context, project_id) quota = get_quota(context, project_id) - allowed_instances = quota['instances'] - used_instances - allowed_cores = quota['cores'] - used_cores - num_cores = num_instances * instance_type['vcpus'] + allowed_instances = _get_allowed_resources(num_instances, used_instances, + quota['instances']) + allowed_cores = _get_allowed_resources(num_cores, used_cores, + quota['cores']) allowed_instances = min(allowed_instances, int(allowed_cores // instance_type['vcpus'])) return min(num_instances, allowed_instances) @@ -81,13 +86,15 @@ def allowed_volumes(context, num_volumes, size): """Check quota and return min(num_volumes, allowed_volumes).""" project_id = context.project_id context = context.elevated() + size = int(size) + num_gigabytes = num_volumes * size used_volumes, used_gigabytes = db.volume_data_get_for_project(context, project_id) quota = get_quota(context, project_id) - allowed_volumes = quota['volumes'] - used_volumes - allowed_gigabytes = quota['gigabytes'] - used_gigabytes - size = int(size) - num_gigabytes = num_volumes * size + allowed_volumes = _get_allowed_resources(num_volumes, used_volumes, + quota['volumes']) + allowed_gigabytes = _get_allowed_resources(num_gigabytes, used_gigabytes, + quota['gigabytes']) allowed_volumes = min(allowed_volumes, int(allowed_gigabytes // size)) return min(num_volumes, allowed_volumes) @@ -99,7 +106,9 @@ def allowed_floating_ips(context, num_floating_ips): context = context.elevated() used_floating_ips = db.floating_ip_count_by_project(context, project_id) quota = get_quota(context, project_id) - allowed_floating_ips = quota['floating_ips'] - used_floating_ips + allowed_floating_ips = _get_allowed_resources(num_floating_ips, + used_floating_ips, + quota['floating_ips']) return min(num_floating_ips, allowed_floating_ips) @@ -108,8 +117,9 @@ def allowed_metadata_items(context, num_metadata_items): project_id = context.project_id context = context.elevated() quota = get_quota(context, project_id) - num_allowed_metadata_items = quota['metadata_items'] - return min(num_metadata_items, num_allowed_metadata_items) + allowed_metadata_items = _get_allowed_resources(num_metadata_items, 0, + quota['metadata_items']) + return min(num_metadata_items, allowed_metadata_items) def allowed_injected_files(context): diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 39a123158..518a76150 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -96,12 +96,11 @@ class QuotaTestCase(test.TestCase): num_instances = quota.allowed_instances(self.context, 100, self._get_instance_type('m1.small')) self.assertEqual(num_instances, 2) - db.quota_create(self.context, {'project_id': self.project.id, - 'instances': 10}) + db.quota_create(self.context, self.project.id, 'instances', 10) num_instances = quota.allowed_instances(self.context, 100, self._get_instance_type('m1.small')) self.assertEqual(num_instances, 4) - db.quota_update(self.context, self.project.id, {'cores': 100}) + db.quota_create(self.context, self.project.id, 'cores', 100) num_instances = quota.allowed_instances(self.context, 100, self._get_instance_type('m1.small')) self.assertEqual(num_instances, 10) @@ -111,13 +110,13 @@ class QuotaTestCase(test.TestCase): num_metadata_items = quota.allowed_metadata_items(self.context, too_many_items) self.assertEqual(num_metadata_items, FLAGS.quota_metadata_items) - db.quota_update(self.context, self.project.id, {'metadata_items': 5}) + db.quota_create(self.context, self.project.id, 'metadata_items', 5) num_metadata_items = quota.allowed_metadata_items(self.context, too_many_items) self.assertEqual(num_metadata_items, 5) # Cleanup - db.quota_destroy(self.context, self.project.id) + db.quota_destroy_all_by_project(self.context, self.project.id) def test_too_many_instances(self): instance_ids = [] -- cgit From 3c0d31a1ae91e30e06f1b33d35915037472b3691 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 9 May 2011 08:23:25 -0700 Subject: basic test working --- nova/tests/test_xenapi.py | 40 ++++++++++++++++++++++++++++++++++++++++ nova/virt/xenapi_conn.py | 4 ++++ 2 files changed, 44 insertions(+) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 375480a2e..756a289bd 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -17,6 +17,7 @@ """Test suite for XenAPI.""" import functools +import json import os import re import stubout @@ -665,3 +666,42 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_VHD self.fake_instance.kernel_id = None self.assert_disk_type(vm_utils.ImageType.DISK_VHD) + + +class FakeXenApi(object): + """Fake XenApi for testing HostState.""" + + class FakeSR(object): + def get_record(self, ref): + return {'virtual_allocation':10000, + 'physical_utilisation':20000} + + SR = FakeSR() + + +class FakeSession(object): + """Fake Session class for HostState testing.""" + + def async_call_plugin(self, *args): + return None + + def wait_for_task(self, *args): + return json.dumps({}) + + def get_xenapi(self): + return FakeXenApi() + + +class HostStateTestCase(test.TestCase): + """Tests HostState, which holds metrics from XenServer that get + reported back to the Schedulers.""" + + def _fake_safe_find_sr(self, session): + """None SR ref since we're ignoring it in FakeSR.""" + return None + + def test_host_state(self): + self.stubs = stubout.StubOutForTesting() + self.stubs.Set(vm_utils, 'safe_find_sr', self._fake_safe_find_sr) + host_state = xenapi_conn.HostState(FakeSession()) + diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 63a53af2e..0e545150f 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -57,6 +57,8 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block. - suffix "_rec" for record objects """ +import json +import random import sys import urlparse import xmlrpclib @@ -67,10 +69,12 @@ from eventlet import timeout from nova import context from nova import db +from nova import exception from nova import utils from nova import flags from nova import log as logging from nova.virt import driver +from nova.virt.xenapi import vm_utils from nova.virt.xenapi.vmops import VMOps from nova.virt.xenapi.volumeops import VolumeOps -- cgit From d087e1d0f0e235de01a8f140815fbe905008cb36 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 9 May 2011 09:08:56 -0700 Subject: capabilities flattened and tests fixed --- nova/compute/manager.py | 1 + nova/scheduler/host_filter.py | 16 ++++++++-------- nova/tests/test_host_filter.py | 26 +++++++++++++------------- nova/tests/test_xenapi.py | 12 ++++++------ nova/virt/xenapi_conn.py | 4 ++-- 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 97c1bbded..abf1a478b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -130,6 +130,7 @@ class ComputeManager(manager.SchedulerDependentManager): self.network_manager = utils.import_object(FLAGS.network_manager) self.volume_manager = utils.import_object(FLAGS.volume_manager) self.network_api = network.API() + self._last_host_check = 0 super(ComputeManager, self).__init__(service_name="compute", *args, **kwargs) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 3e831b76f..885878e14 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -96,8 +96,8 @@ class FlavorFilter(HostFilter): selected_hosts = [] for host, services in zone_manager.service_states.iteritems(): capabilities = services.get('compute', {}) - host_ram_mb = capabilities['host_memory']['free'] - disk_bytes = capabilities['disk']['available'] + host_ram_mb = capabilities['host_memory_free'] + disk_bytes = capabilities['disk_available'] if host_ram_mb >= instance_type['memory_mb'] and \ disk_bytes >= instance_type['local_gb']: selected_hosts.append((host, capabilities)) @@ -106,10 +106,10 @@ class FlavorFilter(HostFilter): #host entries (currently) are like: # {'host_name-description': 'Default install of XenServer', # 'host_hostname': 'xs-mini', -# 'host_memory': {'total': 8244539392, -# 'overhead': 184225792, -# 'free': 3868327936, -# 'free-computed': 3840843776}, +# 'host_memory_total': 8244539392, +# 'host_memory_overhead': 184225792, +# 'host_memory_free': 3868327936, +# 'host_memory_free-computed': 3840843776}, # 'host_other-config': {}, # 'host_ip_address': '192.168.1.109', # 'host_cpu_info': {}, @@ -221,8 +221,8 @@ class JsonFilter(HostFilter): required_ram = instance_type['memory_mb'] required_disk = instance_type['local_gb'] query = ['and', - ['>=', '$compute.host_memory.free', required_ram], - ['>=', '$compute.disk.available', required_disk] + ['>=', '$compute.host_memory_free', required_ram], + ['>=', '$compute.disk_available', required_disk] ] return (self._full_name(), json.dumps(query)) diff --git a/nova/tests/test_host_filter.py b/nova/tests/test_host_filter.py index 31e40ae1d..c029d41e6 100644 --- a/nova/tests/test_host_filter.py +++ b/nova/tests/test_host_filter.py @@ -43,16 +43,16 @@ class HostFilterTestCase(test.TestCase): # which means ... don't go above 10 hosts. return {'host_name-description': 'XenServer %s' % multiplier, 'host_hostname': 'xs-%s' % multiplier, - 'host_memory': {'total': 100, - 'overhead': 10, - 'free': 10 + multiplier * 10, - 'free-computed': 10 + multiplier * 10}, + 'host_memory_total': 100, + 'host_memory_overhead': 10, + 'host_memory_free': 10 + multiplier * 10, + 'host_memory_free-computed': 10 + multiplier * 10, 'host_other-config': {}, 'host_ip_address': '192.168.1.%d' % (100 + multiplier), 'host_cpu_info': {}, - 'disk': {'available': 100 + multiplier * 100, - 'total': 1000, - 'used': 0}, + 'disk_available': 100 + multiplier * 100, + 'disk_total': 1000, + 'disk_used': 0, 'host_uuid': 'xxx-%d' % multiplier, 'host_name-label': 'xs-%s' % multiplier} @@ -131,12 +131,12 @@ class HostFilterTestCase(test.TestCase): raw = ['or', ['and', - ['<', '$compute.host_memory.free', 30], - ['<', '$compute.disk.available', 300] + ['<', '$compute.host_memory_free', 30], + ['<', '$compute.disk_available', 300] ], ['and', - ['>', '$compute.host_memory.free', 70], - ['>', '$compute.disk.available', 700] + ['>', '$compute.host_memory_free', 70], + ['>', '$compute.disk_available', 700] ] ] cooked = json.dumps(raw) @@ -149,7 +149,7 @@ class HostFilterTestCase(test.TestCase): self.assertEquals('host%02d' % index, host) raw = ['not', - ['=', '$compute.host_memory.free', 30], + ['=', '$compute.host_memory_free', 30], ] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) @@ -160,7 +160,7 @@ class HostFilterTestCase(test.TestCase): for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts): self.assertEquals('host%02d' % index, host) - raw = ['in', '$compute.host_memory.free', 20, 40, 60, 80, 100] + raw = ['in', '$compute.host_memory_free', 20, 40, 60, 80, 100] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 0f1b2aa48..678291579 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -709,9 +709,9 @@ class HostStateTestCase(test.TestCase): self.stubs.Set(vm_utils, 'safe_find_sr', self._fake_safe_find_sr) host_state = xenapi_conn.HostState(FakeSession()) stats = host_state._stats - self.assertEquals('disk_total', 10000) - self.assertEquals('disk_used', 20000) - self.assertEquals('host_memory_total', 10) - self.assertEquals('host_memory_overhead', 20) - self.assertEquals('host_memory_free', 30) - self.assertEquals('host_memory_free-computed', 40) + self.assertEquals(stats['disk_total'], 10000) + self.assertEquals(stats['disk_used'], 20000) + self.assertEquals(stats['host_memory_total'], 10) + self.assertEquals(stats['host_memory_overhead'], 20) + self.assertEquals(stats['host_memory_free'], 30) + self.assertEquals(stats['host_memory_free-computed'], 40) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 563a1da77..671a340c7 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -505,8 +505,8 @@ class HostState(object): data["disk_total"] = total data["disk_used"] = used data["disk_available"] = total - used - memory = data.get('host_memory', None) - if memory: + host_memory = data.get('host_memory', None) + if host_memory: data["host_memory_total"] = host_memory.get('total', 0) data["host_memory_overhead"] = host_memory.get('overhead', 0) data["host_memory_free"] = host_memory.get('free', 0) -- cgit From a3f8d3c8ee77cd7cf764aec19033ab0c71703515 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 9 May 2011 09:10:22 -0700 Subject: pep8 --- nova/tests/test_xenapi.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 678291579..6dbd1aee5 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -673,24 +673,24 @@ class FakeXenApi(object): class FakeSR(object): def get_record(self, ref): - return {'virtual_allocation':10000, - 'physical_utilisation':20000} + return {'virtual_allocation': 10000, + 'physical_utilisation': 20000} SR = FakeSR() class FakeSession(object): """Fake Session class for HostState testing.""" - + def async_call_plugin(self, *args): return None def wait_for_task(self, *args): - vm = {'total':10, - 'overhead':20, - 'free':30, - 'free-computed':40} - return json.dumps({'host_memory':vm}) + vm = {'total': 10, + 'overhead': 20, + 'free': 30, + 'free-computed': 40} + return json.dumps({'host_memory': vm}) def get_xenapi(self): return FakeXenApi() -- cgit From d9220c1af021b6c019207e7b9d24e30522bed149 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Mon, 9 May 2011 14:44:39 -0400 Subject: update tests to handle unlimited resources in the db --- nova/tests/test_quota.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 518a76150..7ace2ad7d 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -118,6 +118,78 @@ class QuotaTestCase(test.TestCase): # Cleanup db.quota_destroy_all_by_project(self.context, self.project.id) + def test_unlimited_instances(self): + FLAGS.quota_instances = 2 + FLAGS.quota_cores = 1000 + instance_type = self._get_instance_type('m1.small') + num_instances = quota.allowed_instances(self.context, 100, + instance_type) + self.assertEqual(num_instances, 2) + db.quota_create(self.context, self.project.id, 'instances', None) + num_instances = quota.allowed_instances(self.context, 100, + instance_type) + self.assertEqual(num_instances, 100) + num_instances = quota.allowed_instances(self.context, 101, + instance_type) + self.assertEqual(num_instances, 101) + + def test_unlimited_cores(self): + FLAGS.quota_instances = 1000 + FLAGS.quota_cores = 2 + instance_type = self._get_instance_type('m1.small') + num_instances = quota.allowed_instances(self.context, 100, + instance_type) + self.assertEqual(num_instances, 2) + db.quota_create(self.context, self.project.id, 'cores', None) + num_instances = quota.allowed_instances(self.context, 100, + instance_type) + self.assertEqual(num_instances, 100) + num_instances = quota.allowed_instances(self.context, 101, + instance_type) + self.assertEqual(num_instances, 101) + + def test_unlimited_volumes(self): + FLAGS.quota_volumes = 10 + FLAGS.quota_gigabytes = 1000 + volumes = quota.allowed_volumes(self.context, 100, 1) + self.assertEqual(volumes, 10) + db.quota_create(self.context, self.project.id, 'volumes', None) + volumes = quota.allowed_volumes(self.context, 100, 1) + self.assertEqual(volumes, 100) + volumes = quota.allowed_volumes(self.context, 101, 1) + self.assertEqual(volumes, 101) + + def test_unlimited_gigabytes(self): + FLAGS.quota_volumes = 1000 + FLAGS.quota_gigabytes = 10 + volumes = quota.allowed_volumes(self.context, 100, 1) + self.assertEqual(volumes, 10) + db.quota_create(self.context, self.project.id, 'gigabytes', None) + volumes = quota.allowed_volumes(self.context, 100, 1) + self.assertEqual(volumes, 100) + volumes = quota.allowed_volumes(self.context, 101, 1) + self.assertEqual(volumes, 101) + + def test_unlimited_floating_ips(self): + FLAGS.quota_floating_ips = 10 + floating_ips = quota.allowed_floating_ips(self.context, 100) + self.assertEqual(floating_ips, 10) + db.quota_create(self.context, self.project.id, 'floating_ips', None) + floating_ips = quota.allowed_floating_ips(self.context, 100) + self.assertEqual(floating_ips, 100) + floating_ips = quota.allowed_floating_ips(self.context, 101) + self.assertEqual(floating_ips, 101) + + def test_unlimited_metadata_items(self): + FLAGS.quota_metadata_items = 10 + items = quota.allowed_metadata_items(self.context, 100) + self.assertEqual(items, 10) + db.quota_create(self.context, self.project.id, 'metadata_items', None) + items = quota.allowed_metadata_items(self.context, 100) + self.assertEqual(items, 100) + items = quota.allowed_metadata_items(self.context, 101) + self.assertEqual(items, 101) + def test_too_many_instances(self): instance_ids = [] for i in range(FLAGS.quota_instances): -- cgit From 559bba1270378a430cc85abec144c0c574e65294 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 9 May 2011 12:57:56 -0700 Subject: unified underscore/dash issue --- nova/scheduler/host_filter.py | 2 +- nova/tests/test_xenapi.py | 2 +- nova/virt/xenapi_conn.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 885878e14..483f3225c 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -109,7 +109,7 @@ class FlavorFilter(HostFilter): # 'host_memory_total': 8244539392, # 'host_memory_overhead': 184225792, # 'host_memory_free': 3868327936, -# 'host_memory_free-computed': 3840843776}, +# 'host_memory_free_computed': 3840843776}, # 'host_other-config': {}, # 'host_ip_address': '192.168.1.109', # 'host_cpu_info': {}, diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 6dbd1aee5..6072f5455 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -714,4 +714,4 @@ class HostStateTestCase(test.TestCase): self.assertEquals(stats['host_memory_total'], 10) self.assertEquals(stats['host_memory_overhead'], 20) self.assertEquals(stats['host_memory_free'], 30) - self.assertEquals(stats['host_memory_free-computed'], 40) + self.assertEquals(stats['host_memory_free_computed'], 40) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 671a340c7..8e9085277 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -510,7 +510,7 @@ class HostState(object): data["host_memory_total"] = host_memory.get('total', 0) data["host_memory_overhead"] = host_memory.get('overhead', 0) data["host_memory_free"] = host_memory.get('free', 0) - data["host_memory_free-computed"] = \ + data["host_memory_free_computed"] = \ host_memory.get('free-computed', 0) del data['host_memory'] self._stats = data -- cgit From 4364c3e4103e41fcb8bb0c2af764c37c1ff4afab Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 9 May 2011 16:52:52 -0500 Subject: Better message format description --- nova/notifier/__init__.py | 40 +++++++++++++++++++++++++++++++++++++--- nova/notifier/rabbit_notifier.py | 6 +++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index 8053b8a0a..e6a4a0165 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -13,12 +13,46 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime +import json + from nova import flags from nova import utils FLAGS = flags.FLAGS -def notify(event_name, model): - """Sends a notification using the specified driver""" +flags.DEFINE_string('default_notification_level', 'info', + 'Default notification level for outgoing notifications') + +WARN = 'WARN' +INFO = 'INFO' +ERROR = 'ERROR' +CRITICAL = 'CRITICAL' +DEBUG = 'DEBUG' + +log_levels = (DEBUG, WARN, INFO, ERROR, CRITICAL) + +def notify(event_name, publisher_id, event_type, priority, payload): + """ + Sends a notification using the specified driver + + Message format is as follows: + + publisher_id - the source worker_type.host of the message + timestamp - the GMT timestamp the notification was sent at + event_type - the literal type of event (ex. Instance Creation) + priority - patterned after the enumeration of Python logging levels in + the set (DEBUG, WARN, INFO, ERROR, CRITICAL) + payload - A python dictionary of attributes + + The payload will be constructed as a dictionary of the above attributes, + and converted into a JSON dump, which will then be sent via the transport + mechanism defined by the driver. + """ driver = utils.import_class(FLAGS.notification_driver)() - driver.notify(event_name, model) + message = dict(publisher_id=publisher_id, + event_type=event_type, + priority=priority, + payload=payload, + time=datetime.datetime.utcnow()) + driver.notify(json.dumps(message)) diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index 33cf06566..e4bd85398 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -25,13 +25,13 @@ FLAGS = flags.FLAGS flags.DEFINE_string('notification_topic', 'notifications', 'RabbitMQ topic used for Nova notifications') + class RabbitNotifier(object): """Sends notifications to a specific RabbitMQ server and topic""" pass - def notify(self, event_name, model): + def notify(self, payload): """Sends a notification to the RabbitMQ""" context = nova.context.get_admin_context() topic = FLAGS.notification_topic - msg = { 'event_name': event_name, 'model': model.__dict__ } - rpc.cast(context, topic, json.dumps(msg)) + rpc.cast(context, topic, msg) -- cgit From 6991faaac1eda14bf6162d1a2383e7f9ad6bdeae Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Mon, 9 May 2011 22:36:01 -0500 Subject: Added GitPython to [install_dir]/tools/pip-requires. --- Authors | 1 + tools/pip-requires | 1 + 2 files changed, 2 insertions(+) diff --git a/Authors b/Authors index 60e1d2dad..d7f70f417 100644 --- a/Authors +++ b/Authors @@ -44,6 +44,7 @@ Josh Kearney Josh Kleinpeter Joshua McKenty Justin Santa Barbara +Justin Shepherd Kei Masumoto Ken Pepple Kevin Bringard diff --git a/tools/pip-requires b/tools/pip-requires index e438c2a41..f7eb1703e 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -33,3 +33,4 @@ nova-adminclient suds==0.4 coverage nosexcover +GitPython -- cgit From aa73995f9ba7e4aaaee00ddd0db0b92dcd92dd54 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 10 May 2011 16:11:59 +0000 Subject: Change xenapi's wait_for_task to handle multiple simultaenous queries to fix lp:766404 --- nova/virt/xenapi_conn.py | 86 ++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 0cabccf08..5dc6b034f 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -326,7 +326,6 @@ class XenAPISession(object): "(is the Dom0 disk full?)")) with timeout.Timeout(FLAGS.xenapi_login_timeout, exception): self._session.login_with_password(user, pw) - self.loop = None def get_imported_xenapi(self): """Stubout point. This can be replaced with a mock xenapi module.""" @@ -363,57 +362,52 @@ class XenAPISession(object): def wait_for_task(self, task, id=None): """Return the result of the given task. The task is polled - until it completes. Not re-entrant.""" + until it completes.""" done = event.Event() - self.loop = utils.LoopingCall(self._poll_task, id, task, done) - self.loop.start(FLAGS.xenapi_task_poll_interval, now=True) - rv = done.wait() - self.loop.stop() - return rv - - def _stop_loop(self): - """Stop polling for task to finish.""" - #NOTE(sandy-walsh) Had to break this call out to support unit tests. - if self.loop: - self.loop.stop() + loop = utils.LoopingCall(f=None) + + def _poll_task(): + """Poll the given XenAPI task, and return the result if the + action was completed successfully or not. + """ + try: + name = self._session.xenapi.task.get_name_label(task) + status = self._session.xenapi.task.get_status(task) + if id: + action = dict( + instance_id=int(id), + action=name[0:255], # Ensure action is never > 255 + error=None) + if status == "pending": + return + elif status == "success": + result = self._session.xenapi.task.get_result(task) + LOG.info(_("Task [%(name)s] %(task)s status:" + " success %(result)s") % locals()) + done.send(_parse_xmlrpc_value(result)) + else: + error_info = self._session.xenapi.task.get_error_info(task) + action["error"] = str(error_info) + LOG.warn(_("Task [%(name)s] %(task)s status:" + " %(status)s %(error_info)s") % locals()) + done.send_exception(self.XenAPI.Failure(error_info)) + + if id: + db.instance_action_create(context.get_admin_context(), + action) + except self.XenAPI.Failure, exc: + LOG.warn(exc) + done.send_exception(*sys.exc_info()) + loop.stop() + + loop.f = _poll_task + loop.start(FLAGS.xenapi_task_poll_interval, now=True) + return done.wait() def _create_session(self, url): """Stubout point. This can be replaced with a mock session.""" return self.XenAPI.Session(url) - def _poll_task(self, id, task, done): - """Poll the given XenAPI task, and fire the given action if we - get a result. - """ - try: - name = self._session.xenapi.task.get_name_label(task) - status = self._session.xenapi.task.get_status(task) - if id: - action = dict( - instance_id=int(id), - action=name[0:255], # Ensure action is never > 255 - error=None) - if status == "pending": - return - elif status == "success": - result = self._session.xenapi.task.get_result(task) - LOG.info(_("Task [%(name)s] %(task)s status:" - " success %(result)s") % locals()) - done.send(_parse_xmlrpc_value(result)) - else: - error_info = self._session.xenapi.task.get_error_info(task) - action["error"] = str(error_info) - LOG.warn(_("Task [%(name)s] %(task)s status:" - " %(status)s %(error_info)s") % locals()) - done.send_exception(self.XenAPI.Failure(error_info)) - - if id: - db.instance_action_create(context.get_admin_context(), action) - except self.XenAPI.Failure, exc: - LOG.warn(exc) - done.send_exception(*sys.exc_info()) - self._stop_loop() - def _unwrap_plugin_exceptions(self, func, *args, **kwargs): """Parse exception details""" try: -- cgit From f0c4bc2ff0182292e667bbcafb349e407596148a Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Tue, 10 May 2011 14:49:47 -0400 Subject: migration bug fixes --- .../versions/016_make_quotas_key_and_value.py | 34 ++++++++++++++++++---- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py index 6bf2b19af..c03015b5e 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py @@ -108,7 +108,7 @@ def convert_forward(migrate_engine, old_quotas, new_quotas): quotas = list(migrate_engine.execute(old_quotas.select())) for quota in quotas: for resource in resources: - limit = getattr(old_quota, resource) + limit = getattr(old_quotas, resource) if limit is None: continue insert = new_quotas.insert().values( @@ -122,6 +122,30 @@ def convert_forward(migrate_engine, old_quotas, new_quotas): migrate_engine.execute(insert) +def earliest(date1, date2): + if date1 is None and date2 is None: + return None + if date1 is None: + return date2 + if date2 is None: + return date1 + if date1 < date2: + return date1 + return date2 + + +def latest(date1, date2): + if date1 is None and date2 is None: + return None + if date1 is None: + return date2 + if date2 is None: + return date1 + if date1 > date2: + return date1 + return date2 + + def convert_backward(migrate_engine, old_quotas, new_quotas): quotas = {} for quota in migrate_engine.execute(new_quotas.select()): @@ -136,10 +160,10 @@ def convert_backward(migrate_engine, old_quotas, new_quotas): quota.resource: quota.limit } else: - if quota.created_at < quotas[quota.project_id]['created_at']: - quotas[quota.project_id]['created_at'] = quota.created_at - if quota.updated_at > quotas[quota.project_id]['updated_at']: - quotas[quota.project_id]['updated_at'] = quota.updated_at + quotas[quota.project_id]['created_at'] = earliest( + quota.created_at, quotas[quota.project_id]['created_at']) + quotas[quota.project_id]['updated_at'] = latest( + quota.created_at, quotas[quota.project_id]['updated_at']) quotas[quota.project_id][quota.resource] = quota.limit for quota in quotas.itervalues(): -- cgit From a67c77ce504ad2e15b013ed40421d0d0b823767f Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 10 May 2011 18:55:07 +0000 Subject: remove stubbing of XenAPISession.wait_for_task for xenapi tests as it doesn't need to be faked. Also removed duplicate code that stubbed xenapi_conn._parse_xmlrpc_value. --- nova/tests/xenapi/stubs.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 205f6c902..6db061444 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -28,29 +28,6 @@ def stubout_instance_snapshot(stubs): @classmethod def fake_fetch_image(cls, session, instance_id, image, user, project, type): - # Stubout wait_for_task - def fake_wait_for_task(self, task, id): - class FakeEvent: - - def send(self, value): - self.rv = value - - def wait(self): - return self.rv - - done = FakeEvent() - self._poll_task(id, task, done) - rv = done.wait() - return rv - - def fake_loop(self): - pass - - stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task', - fake_wait_for_task) - - stubs.Set(xenapi_conn.XenAPISession, '_stop_loop', fake_loop) - from nova.virt.xenapi.fake import create_vdi name_label = "instance-%s" % instance_id #TODO: create fake SR record @@ -63,11 +40,6 @@ def stubout_instance_snapshot(stubs): stubs.Set(vm_utils.VMHelper, 'fetch_image', fake_fetch_image) - def fake_parse_xmlrpc_value(val): - return val - - stubs.Set(xenapi_conn, '_parse_xmlrpc_value', fake_parse_xmlrpc_value) - def fake_wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, original_parent_uuid): from nova.virt.xenapi.fake import create_vdi -- cgit From 288030b2b9834ca65e822a770f1b2d052ee27a10 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 10 May 2011 14:40:28 -0500 Subject: Test --- nova/tests/test_compute.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 393110791..a35132426 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -329,6 +329,32 @@ class ComputeTestCase(test.TestCase): self.compute.terminate_instance(self.context, instance_id) + def test_finish_resize(self): + """Contrived test to ensure finish_resize doesn't raise anything""" + + def fake(*args, **kwargs): pass + + self.stubs.Set(self.compute.driver, 'finish_resize', fake) + self.stubs.Set(self.compute.driver, 'finish_resize', fake) + context = self.context.elevated() + instance_id = self._create_instance() + self.compute.prep_resize(context, instance_id, 1) + migration_ref = db.migration_get_by_instance_and_status(context, + instance_id, 'pre-migrating') + try: + self.compute.finish_resize(context, instance_id, + int(migration_ref['id']), {}) + except KeyError, e: + # Only catch key errors. We want other reasons for the test to + # fail to actually error out so we don't obscure anything + self.fail() + + self.compute.terminate_instance(self.context, instance_id) + + def test_resize_instance(self): + """Ensure instance can be migrated/resized""" + instance_id = self._create_instance() + def test_resize_instance(self): """Ensure instance can be migrated/resized""" instance_id = self._create_instance() -- cgit From 64f9fdc15f744c2646f6f4a519cf0f0df2845239 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 10 May 2011 14:53:03 -0500 Subject: Pep8 --- nova/tests/test_compute.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index a35132426..9926e1ca3 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -332,7 +332,8 @@ class ComputeTestCase(test.TestCase): def test_finish_resize(self): """Contrived test to ensure finish_resize doesn't raise anything""" - def fake(*args, **kwargs): pass + def fake(*args, **kwargs): + pass self.stubs.Set(self.compute.driver, 'finish_resize', fake) self.stubs.Set(self.compute.driver, 'finish_resize', fake) @@ -350,10 +351,10 @@ class ComputeTestCase(test.TestCase): self.fail() self.compute.terminate_instance(self.context, instance_id) - - def test_resize_instance(self): - """Ensure instance can be migrated/resized""" - instance_id = self._create_instance() + + def test_resize_instance(self): + """Ensure instance can be migrated/resized""" + instance_id = self._create_instance() def test_resize_instance(self): """Ensure instance can be migrated/resized""" -- cgit From de9b191905803ff8742c3dde4335682d53b01fcd Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Tue, 10 May 2011 15:54:05 -0400 Subject: fix migration bug --- .../sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py index c03015b5e..d7c4fb960 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py @@ -108,7 +108,7 @@ def convert_forward(migrate_engine, old_quotas, new_quotas): quotas = list(migrate_engine.execute(old_quotas.select())) for quota in quotas: for resource in resources: - limit = getattr(old_quotas, resource) + limit = getattr(quota, resource) if limit is None: continue insert = new_quotas.insert().values( -- cgit From 19f5d2a938ffa4c7bcba849766d2450eaecc94eb Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 10 May 2011 14:57:44 -0500 Subject: Whoops --- nova/tests/test_compute.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 9926e1ca3..1b0e66bef 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -352,10 +352,6 @@ class ComputeTestCase(test.TestCase): self.compute.terminate_instance(self.context, instance_id) - def test_resize_instance(self): - """Ensure instance can be migrated/resized""" - instance_id = self._create_instance() - def test_resize_instance(self): """Ensure instance can be migrated/resized""" instance_id = self._create_instance() -- cgit From 59b593fb70b57864b84677644786d5175b0811be Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Tue, 10 May 2011 16:38:04 -0400 Subject: give a more informative message if pre-migration assertions fail --- .../migrate_repo/versions/016_make_quotas_key_and_value.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py index d7c4fb960..25bd1f4de 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py @@ -81,7 +81,10 @@ def existing_quotas_table(migrate_engine): def _assert_no_duplicate_project_ids(quotas): project_ids = set() - message = 'There are duplicate active quotas for project %s' + message = ('There are multiple active quotas for project "%s" ' + '(among others, possibly). ' + 'Please resolve all ambiguous quotas before ' + 'reattempting the migration.') for quota in quotas: assert quota.project_id not in project_ids, message % quota.project_id project_ids.add(quota.project_id) -- cgit From 3d756a8343845acfead201621a6d658c8ac616fb Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 10 May 2011 15:42:00 -0500 Subject: Add example --- nova/notifier/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index e6a4a0165..aacbf8ac3 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -48,11 +48,20 @@ def notify(event_name, publisher_id, event_type, priority, payload): The payload will be constructed as a dictionary of the above attributes, and converted into a JSON dump, which will then be sent via the transport mechanism defined by the driver. + + Message example: + + { 'publisher_id': 'compute.host1', + 'timestamp': '2011-05-09 22:00:14.621831', + 'priority': 'WARN', + 'event_type': 'compute.create_instance', + 'payload': {'instance_id': 12, ... }} + """ driver = utils.import_class(FLAGS.notification_driver)() message = dict(publisher_id=publisher_id, event_type=event_type, priority=priority, payload=payload, - time=datetime.datetime.utcnow()) + time=str(datetime.datetime.utcnow())) driver.notify(json.dumps(message)) -- cgit From fa3195b6206cffc26d421db891e1a580a18f0fb0 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 10 May 2011 16:40:47 -0500 Subject: Better tests --- nova/notifier/__init__.py | 5 +++++ nova/notifier/rabbit_notifier.py | 2 +- nova/tests/test_notifier.py | 40 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index aacbf8ac3..942c1a1a2 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -32,6 +32,9 @@ DEBUG = 'DEBUG' log_levels = (DEBUG, WARN, INFO, ERROR, CRITICAL) +class BadPriorityException(Exception): + pass + def notify(event_name, publisher_id, event_type, priority, payload): """ Sends a notification using the specified driver @@ -58,6 +61,8 @@ def notify(event_name, publisher_id, event_type, priority, payload): 'payload': {'instance_id': 12, ... }} """ + if priority not in log_levels: + raise BadPriorityException('%s not in valid priorities' % priority) driver = utils.import_class(FLAGS.notification_driver)() message = dict(publisher_id=publisher_id, event_type=event_type, diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index e4bd85398..1d62005a8 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -34,4 +34,4 @@ class RabbitNotifier(object): """Sends a notification to the RabbitMQ""" context = nova.context.get_admin_context() topic = FLAGS.notification_topic - rpc.cast(context, topic, msg) + rpc.cast(context, topic, payload) diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 4d6289e6a..396ce13b1 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json + import nova from nova import flags @@ -42,9 +44,27 @@ class NotifierTestCase(test.TestCase): class Mock(object): pass - notifier.notify('derp', Mock()) + nova.notifier.notify('event_name', 'publisher_id', 'event_type', + nova.notifier.WARN, dict(a=3)) self.assertEqual(self.notify_called, True) + def test_verify_message_format(self): + """A test to ensure changing the message format is prohibitively + annoying""" + def message_assert(cls, blob): + message = json.loads(blob) + fields = [ ('publisher_id', 'publisher_id'), + ('event_type', 'event_type'), + ('priority', 'WARN'), + ('payload', dict(a=3))] + for k, v in fields: + self.assertEqual(message[k], v) + + self.stubs.Set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', + message_assert) + nova.notifier.notify('event_name', 'publisher_id', 'event_type', + nova.notifier.WARN, dict(a=3)) + def test_send_rabbit_notification(self): self.stubs.Set(nova.flags.FLAGS, 'notification_driver', 'nova.notifier.rabbit_notifier.RabbitNotifier') @@ -55,6 +75,22 @@ class NotifierTestCase(test.TestCase): class Mock(object): pass self.stubs.Set(nova.rpc, 'cast', mock_cast) - notifier.notify('derp', Mock()) + nova.notifier.notify('event_name', 'publisher_id', 'event_type', + nova.notifier.WARN, dict(a=3)) self.assertEqual(self.mock_cast, True) + + def test_invalid_priority(self): + self.stubs.Set(nova.flags.FLAGS, 'notification_driver', + 'nova.notifier.rabbit_notifier.RabbitNotifier') + self.mock_cast = False + def mock_cast(cls, *args): + pass + + class Mock(object): + pass + + self.stubs.Set(nova.rpc, 'cast', mock_cast) + self.assertRaises(nova.notifier.BadPriorityException, + nova.notifier.notify, 'event_name', 'publisher_id', + 'event_type', 'not a priority', dict(a=3)) -- cgit From 5b1616bff43ee67f0307a9e8b2233d1f1ed8472c Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Tue, 10 May 2011 17:00:24 -0500 Subject: removed unused wild card imports, replaced sqlalchemy wildcard import with explicit imports previous pylint score: -32.76/10 new pylint score: 4.44/10 --- .../migrate_repo/versions/014_add_instance_type_id_to_instances.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py index 334d1f255..bb4a9619c 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py @@ -14,16 +14,11 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from sqlalchemy.sql import text -from migrate import * - +from sqlalchemy import MetaData, Column, String, Table, Integer #from nova import log as logging - meta = MetaData() - c_instance_type = Column('instance_type', String(length=255, convert_unicode=False, assert_unicode=None, unicode_error=None, -- cgit From 849c5f10dc15d9c2272d5768748feac1ded6b635 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Tue, 10 May 2011 17:12:09 -0500 Subject: removed unused wild card imports, replaced sqlalchemy wildcard import with explicit imports previous pylint score: -75.00/10 new pylint score: -7.86/10 --- .../migrate_repo/versions/013_add_flavors_to_migrations.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py index 3fb92e85c..6e24c4517 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py @@ -15,11 +15,8 @@ # License for the specific language governing permissions and limitations # under the License.from sqlalchemy import * -from sqlalchemy import * -from migrate import * - -from nova import log as logging - +from sqlalchemy import MetaData, Column, Table, Integer +#from nova import log as logging meta = MetaData() -- cgit From 172ce9f39564eb4d416dae3ce7abafc46af8f695 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Tue, 10 May 2011 17:21:37 -0500 Subject: removed unused wild card imports, replaced sqlalchemy wildcard import with explicit imports previous pylint score: -63.75/10 new pylint score: 2.67/10 --- .../sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py b/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py index e87085668..317e8431d 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py @@ -13,15 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - -from nova import log as logging - - +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer +from sqlalchemy import MetaData, String, Table meta = MetaData() - # Table stub-definitions # Just for the ForeignKey and column creation to succeed, these are not the # actual definitions of instances or services. -- cgit From 351c07f43c8ee072b0351973db9b5b9bd1656571 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Tue, 10 May 2011 23:29:16 +0000 Subject: Add priority based queues to notifications. Remove duplicate json encoding in notifier (rpc.cast does encoding... ) make no_op_notifier match rabbit one for signature on notify() --- nova/notifier/__init__.py | 5 ++--- nova/notifier/no_op_notifier.py | 2 +- nova/notifier/rabbit_notifier.py | 7 ++++--- nova/tests/test_notifier.py | 29 +++++++++++++++++++++++------ 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index 942c1a1a2..6429ea965 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -14,14 +14,13 @@ # under the License. import datetime -import json from nova import flags from nova import utils FLAGS = flags.FLAGS -flags.DEFINE_string('default_notification_level', 'info', +flags.DEFINE_string('default_notification_level', 'INFO', 'Default notification level for outgoing notifications') WARN = 'WARN' @@ -69,4 +68,4 @@ def notify(event_name, publisher_id, event_type, priority, payload): priority=priority, payload=payload, time=str(datetime.datetime.utcnow())) - driver.notify(json.dumps(message)) + driver.notify(message) diff --git a/nova/notifier/no_op_notifier.py b/nova/notifier/no_op_notifier.py index 3fefe6f8f..f425f06ea 100644 --- a/nova/notifier/no_op_notifier.py +++ b/nova/notifier/no_op_notifier.py @@ -14,6 +14,6 @@ # under the License. class NoopNotifier(object): - def notify(self, event_name, model): + def notify(self, payload): """Notifies the recipient of the desired event given the model""" pass diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index 1d62005a8..4b6538696 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import json import nova.context @@ -28,10 +27,12 @@ flags.DEFINE_string('notification_topic', 'notifications', class RabbitNotifier(object): """Sends notifications to a specific RabbitMQ server and topic""" - pass def notify(self, payload): """Sends a notification to the RabbitMQ""" context = nova.context.get_admin_context() - topic = FLAGS.notification_topic + priority = payload.get('priority', + FLAGS.default_notification_level) + priority = priority.lower() + topic = '%s.%s' % (FLAGS.notification_topic, priority) rpc.cast(context, topic, payload) diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 396ce13b1..8fc43d9de 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -13,13 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. -import json - import nova +from nova import context from nova import flags +from nova import rpc from nova import notifier from nova.notifier import no_op_notifier +from nova.notifier import rabbit_notifier from nova import test import stubout @@ -51,8 +52,7 @@ class NotifierTestCase(test.TestCase): def test_verify_message_format(self): """A test to ensure changing the message format is prohibitively annoying""" - def message_assert(cls, blob): - message = json.loads(blob) + def message_assert(cls, message): fields = [ ('publisher_id', 'publisher_id'), ('event_type', 'event_type'), ('priority', 'WARN'), @@ -71,7 +71,7 @@ class NotifierTestCase(test.TestCase): self.mock_cast = False def mock_cast(cls, *args): self.mock_cast = True - + class Mock(object): pass self.stubs.Set(nova.rpc, 'cast', mock_cast) @@ -86,7 +86,7 @@ class NotifierTestCase(test.TestCase): self.mock_cast = False def mock_cast(cls, *args): pass - + class Mock(object): pass @@ -94,3 +94,20 @@ class NotifierTestCase(test.TestCase): self.assertRaises(nova.notifier.BadPriorityException, nova.notifier.notify, 'event_name', 'publisher_id', 'event_type', 'not a priority', dict(a=3)) + + def test_rabbit_priority_queue(self): + self.stubs.Set(nova.flags.FLAGS, 'notification_driver', + 'nova.notifier.rabbit_notifier.RabbitNotifier') + self.stubs.Set(nova.flags.FLAGS, 'notification_topic', + 'testnotify') + + self.test_topic = None + + def mock_cast(context, topic, msg): + self.test_topic = topic + + self.stubs.Set(nova.rpc, 'cast', mock_cast) + nova.notifier.notify('event_name', 'publisher_id', + 'event_type', 'DEBUG', dict(a=3)) + self.assertEqual(self.test_topic, 'testnotify.debug') + -- cgit From e1dc9cfb521f21dd0cdd4d9771d78ef5024cebad Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Tue, 10 May 2011 23:57:38 +0000 Subject: added in log_notifier for easier debugging --- nova/notifier/log_notifier.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 nova/notifier/log_notifier.py diff --git a/nova/notifier/log_notifier.py b/nova/notifier/log_notifier.py new file mode 100644 index 000000000..05126b591 --- /dev/null +++ b/nova/notifier/log_notifier.py @@ -0,0 +1,33 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from nova import flags +from nova import log as logging + +FLAGS = flags.FLAGS + +class LogNotifier(object): + """ log notifications using nova's default logging system """ + + def notify(self, payload): + """Notifies the recipient of the desired event given the model""" + priority = payload.get('priority', + FLAGS.default_notification_level) + priority = priority.lower() + logger = logging.getLogger('nova.notification.%s' % payload['event_type']) + getattr(logger, priority)(json.dumps(payload)) + -- cgit From a5c31944708f7afe75c51ef84f2712df3e8ad416 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Tue, 10 May 2011 20:10:10 -0400 Subject: migrate back updated_at correctly --- .../sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py index 25bd1f4de..82129cfc4 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py @@ -166,7 +166,7 @@ def convert_backward(migrate_engine, old_quotas, new_quotas): quotas[quota.project_id]['created_at'] = earliest( quota.created_at, quotas[quota.project_id]['created_at']) quotas[quota.project_id]['updated_at'] = latest( - quota.created_at, quotas[quota.project_id]['updated_at']) + quota.updated_at, quotas[quota.project_id]['updated_at']) quotas[quota.project_id][quota.resource] = quota.limit for quota in quotas.itervalues(): -- cgit From bdc1225c97af653c6702743912f0b5ed251bcac3 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Tue, 10 May 2011 20:22:56 -0500 Subject: removed unused wild card imports, replaced sqlalchemy wildcard import with explicit imports previous pylint score: -55.00/10 new pylint score: -1.76/10 --- nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py b/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py index 23ccccb4e..1b0d08015 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py @@ -16,10 +16,9 @@ # License for the specific language governing permissions and limitations # under the License. -from migrate import * -from nova import log as logging -from sqlalchemy import * - +from sqlalchemy import Boolean, Column, DateTime, Integer, MetaData +from sqlalchemy import Table, Text +# from nova import log as logging meta = MetaData() -- cgit From 1f5313c5417d7417d20a75551d57156f90b6eb64 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Tue, 10 May 2011 22:22:45 -0500 Subject: removed unused wild card imports, replaced sqlalchemy wildcard import with explicit imports --- nova/db/sqlalchemy/migrate_repo/versions/001_austin.py | 8 +++----- nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py | 7 ++----- .../migrate_repo/versions/003_add_label_to_networks.py | 8 ++------ nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py | 7 ++----- .../migrate_repo/versions/005_add_instance_metadata.py | 7 ++----- .../migrate_repo/versions/006_add_provider_data_to_volumes.py | 7 ++----- .../migrate_repo/versions/007_add_ipv6_to_fixed_ips.py | 8 ++------ .../sqlalchemy/migrate_repo/versions/008_add_instance_types.py | 9 ++------- .../migrate_repo/versions/009_add_instance_migrations.py | 6 ++---- .../migrate_repo/versions/010_add_os_type_to_instances.py | 8 ++------ nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py | 2 +- .../sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py | 2 ++ .../migrate_repo/versions/013_add_flavors_to_migrations.py | 4 ++-- .../versions/014_add_instance_type_id_to_instances.py | 2 +- .../migrate_repo/versions/015_add_auto_assign_to_floating_ips.py | 7 ++----- 15 files changed, 29 insertions(+), 63 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py b/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py index 9e7ab3554..63bbaccc1 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py @@ -17,15 +17,13 @@ # under the License. ## Table code mostly autogenerated by genmodel.py -from sqlalchemy import * -from migrate import * - +from sqlalchemy import Boolean, Column, DateTime, ForeignKey +from sqlalchemy import ForeignKeyConstraint, Integer, MetaData, String +from sqlalchemy import Table, Text from nova import log as logging - meta = MetaData() - auth_tokens = Table('auth_tokens', meta, Column('created_at', DateTime(timezone=False)), Column('updated_at', DateTime(timezone=False)), diff --git a/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py b/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py index 413536a59..9bb8a8ada 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py @@ -16,15 +16,12 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - +from sqlalchemy import Boolean, Column, DateTime, ForeignKey +from sqlalchemy import Integer, MetaData, String, Table, Text from nova import log as logging - meta = MetaData() - # Just for the ForeignKey and column creation to succeed, these are not the # actual definitions of instances or services. instances = Table('instances', meta, diff --git a/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py b/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py index 5ba7910f1..4a6fd7599 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py @@ -15,15 +15,11 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - -from nova import log as logging - +from sqlalchemy import Column, Integer, MetaData, String, Table +# from nova import log as logging meta = MetaData() - networks = Table('networks', meta, Column('id', Integer(), primary_key=True, nullable=False), ) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py b/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py index ade981687..0abea374c 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py @@ -13,15 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - +from sqlalchemy import Boolean, Column, DateTime, Integer +from sqlalchemy import MetaData, String, Table from nova import log as logging - meta = MetaData() - # # New Tables # diff --git a/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py b/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py index 4cb07e0d8..a1a86e3b4 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py @@ -15,15 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer +from sqlalchemy import MetaData, String, Table from nova import log as logging - meta = MetaData() - # Just for the ForeignKey and column creation to succeed, these are not the # actual definitions of instances or services. instances = Table('instances', meta, diff --git a/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py b/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py index 705fc8ff3..81a924d42 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py @@ -15,11 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - -from nova import log as logging - +from sqlalchemy import Column, Integer, MetaData, String, Table +# from nova import log as logging meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py index 427934d53..56b43ae48 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py @@ -13,15 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - -from nova import log as logging - +from sqlalchemy import Column, Integer, MetaData, String, Table +# from nova import log as logging meta = MetaData() - # Table stub-definitions # Just for the ForeignKey and column creation to succeed, these are not the # actual definitions of instances or services. diff --git a/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py b/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py index 5e2cb69d9..63999f6ff 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py @@ -13,15 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - -from nova import api -from nova import db +from sqlalchemy import Boolean, Column, DateTime, Integer +from sqlalchemy import MetaData, String, Table from nova import log as logging -import datetime - meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py index 4fda525f1..0f2d0079a 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py @@ -15,12 +15,10 @@ # License for the specific language governing permissions and limitations # under the License.from sqlalchemy import * -from sqlalchemy import * -from migrate import * - +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer +from sqlalchemy import MetaData, String, Table from nova import log as logging - meta = MetaData() # Just for the ForeignKey and column creation to succeed, these are not the diff --git a/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py index eb3066894..0f7ee92e6 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py @@ -14,12 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from sqlalchemy.sql import text -from migrate import * - -from nova import log as logging - +from sqlalchemy import Column, Integer, MetaData, String, Table +# from nova import log as logging meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py b/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py index 1b0d08015..b2b0256d2 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py @@ -18,7 +18,7 @@ from sqlalchemy import Boolean, Column, DateTime, Integer, MetaData from sqlalchemy import Table, Text -# from nova import log as logging +from nova import log as logging meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py b/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py index 317e8431d..294d3e698 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py @@ -15,6 +15,8 @@ from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer from sqlalchemy import MetaData, String, Table +# from nova import log as loggingo + meta = MetaData() # Table stub-definitions diff --git a/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py index 6e24c4517..77015f58f 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py @@ -15,8 +15,8 @@ # License for the specific language governing permissions and limitations # under the License.from sqlalchemy import * -from sqlalchemy import MetaData, Column, Table, Integer -#from nova import log as logging +from sqlalchemy import Column, Integer, MetaData, Table +# from nova import log as logging meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py index bb4a9619c..62216be12 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import MetaData, Column, String, Table, Integer +from sqlalchemy import Column, Integer, MetaData, String, Table #from nova import log as logging meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py index 29b26b3dd..5b2950f32 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -15,14 +15,11 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from sqlalchemy.sql import text -from migrate import * - +from sqlalchemy import Boolean, Column, MetaData, Table +# from nova import log as loggingo meta = MetaData() - c_auto_assigned = Column('auto_assigned', Boolean, default=False) -- cgit From ffabb107d858c64261fd56adab9fa57d29ad322f Mon Sep 17 00:00:00 2001 From: Lvov Maxim Date: Wed, 11 May 2011 11:47:38 +0400 Subject: changing Authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 1cdeeff99..0762fd016 100644 --- a/Authors +++ b/Authors @@ -50,6 +50,7 @@ Kevin Bringard Kevin L. Mitchell Koji Iida Lorin Hochstein +Lvov Maxim Mark Washenberger Masanori Itoh Matt Dietz -- cgit From 43fa5afac9e5af74e2e3977a5dafd9640d064cf1 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 11 May 2011 15:12:12 +0000 Subject: Abstract out IPv6 address generation to pluggable backends --- nova/api/ec2/cloud.py | 3 ++- nova/db/sqlalchemy/api.py | 5 +++-- nova/ipv6/__init__.py | 17 +++++++++++++++++ nova/ipv6/api.py | 34 ++++++++++++++++++++++++++++++++++ nova/ipv6/rfc2462.py | 42 ++++++++++++++++++++++++++++++++++++++++++ nova/tests/network/base.py | 8 ++++---- nova/utils.py | 20 -------------------- nova/virt/libvirt_conn.py | 3 ++- nova/virt/xenapi/vmops.py | 3 ++- 9 files changed, 106 insertions(+), 29 deletions(-) create mode 100644 nova/ipv6/__init__.py create mode 100644 nova/ipv6/api.py create mode 100644 nova/ipv6/rfc2462.py diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 092b80fa2..993c91fe1 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -39,6 +39,7 @@ from nova import log as logging from nova import network from nova import utils from nova import volume +from nova import ipv6 from nova.api.ec2 import ec2utils from nova.compute import instance_types from nova.image import s3 @@ -718,7 +719,7 @@ class CloudController(object): fixed = instance['fixed_ip'] floating_addr = fixed['floating_ips'][0]['address'] if instance['fixed_ip']['network'] and 'use_v6' in kwargs: - i['dnsNameV6'] = utils.to_global_ipv6( + i['dnsNameV6'] = ipv6.to_global( instance['fixed_ip']['network']['cidr_v6'], instance['mac_address']) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 285b22a04..6c76248ce 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -26,6 +26,7 @@ from nova import db from nova import exception from nova import flags from nova import utils +from nova import ipv6 from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from sqlalchemy import or_ @@ -744,7 +745,7 @@ def fixed_ip_get_all_by_instance(context, instance_id): @require_context def fixed_ip_get_instance_v6(context, address): session = get_session() - mac = utils.to_mac(address) + mac = ipv6.to_mac(address) result = session.query(models.Instance).\ filter_by(mac_address=mac).\ @@ -974,7 +975,7 @@ def instance_get_fixed_address_v6(context, instance_id): network_ref = network_get_by_instance(context, instance_id) prefix = network_ref.cidr_v6 mac = instance_ref.mac_address - return utils.to_global_ipv6(prefix, mac) + return ipv6.to_global(prefix, mac) @require_context diff --git a/nova/ipv6/__init__.py b/nova/ipv6/__init__.py new file mode 100644 index 000000000..da4567cfb --- /dev/null +++ b/nova/ipv6/__init__.py @@ -0,0 +1,17 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Openstack, LLC. +# +# 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.ipv6.api import * diff --git a/nova/ipv6/api.py b/nova/ipv6/api.py new file mode 100644 index 000000000..95b20c945 --- /dev/null +++ b/nova/ipv6/api.py @@ -0,0 +1,34 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Openstack, LLC. +# +# 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 +from nova import utils + + +FLAGS = flags.FLAGS +flags.DEFINE_string('ipv6_backend', + 'rfc2462', + 'Backend to use for IPv6 generation') + +IMPL = utils.LazyPluggable(FLAGS['ipv6_backend'], + rfc2462='nova.ipv6.rfc2462') + + +def to_global(prefix, mac): + return IMPL.to_global(prefix, mac) + +def to_mac(ipv6_address): + return IMPL.to_mac(ipv6_address) diff --git a/nova/ipv6/rfc2462.py b/nova/ipv6/rfc2462.py new file mode 100644 index 000000000..3af4556e7 --- /dev/null +++ b/nova/ipv6/rfc2462.py @@ -0,0 +1,42 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 Justin Santa Barbara +# 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. + +"""RFC2462 style IPv6 address generation""" + +import netaddr + + +def to_global(prefix, mac): + try: + mac64 = netaddr.EUI(mac).eui64().words + int_addr = int(''.join(['%02x' % i for i in mac64]), 16) + mac64_addr = netaddr.IPAddress(int_addr) + maskIP = netaddr.IPNetwork(prefix).ip + return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\ + format() + except TypeError: + raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac) + + +def to_mac(ipv6_address): + address = netaddr.IPAddress(ipv6_address) + mask1 = netaddr.IPAddress('::ffff:ffff:ffff:ffff') + mask2 = netaddr.IPAddress('::0200:0:0:0') + mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words + return ':'.join(['%02x' % i for i in mac64[0:3] + mac64[5:8]]) diff --git a/nova/tests/network/base.py b/nova/tests/network/base.py index 988a1de72..5de1255cd 100644 --- a/nova/tests/network/base.py +++ b/nova/tests/network/base.py @@ -28,6 +28,7 @@ from nova import flags from nova import log as logging from nova import test from nova import utils +from nova import ipv6 from nova.auth import manager FLAGS = flags.FLAGS @@ -117,15 +118,14 @@ class NetworkTestCase(test.TestCase): context.get_admin_context(), instance_ref['id']) self.assertEqual(instance_ref['mac_address'], - utils.to_mac(address_v6)) + ipv6.to_mac(address_v6)) instance_ref2 = db.fixed_ip_get_instance_v6( context.get_admin_context(), address_v6) self.assertEqual(instance_ref['id'], instance_ref2['id']) self.assertEqual(address_v6, - utils.to_global_ipv6( - network_ref['cidr_v6'], - instance_ref['mac_address'])) + ipv6.to_global(network_ref['cidr_v6'], + instance_ref['mac_address'])) self._deallocate_address(0, address) db.instance_destroy(context.get_admin_context(), instance_ref['id']) diff --git a/nova/utils.py b/nova/utils.py index 80bf1197f..aa77caf71 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -303,26 +303,6 @@ def get_my_linklocal(interface): " :%(ex)s") % locals()) -def to_global_ipv6(prefix, mac): - try: - mac64 = netaddr.EUI(mac).eui64().words - int_addr = int(''.join(['%02x' % i for i in mac64]), 16) - mac64_addr = netaddr.IPAddress(int_addr) - maskIP = netaddr.IPNetwork(prefix).ip - return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\ - format() - except TypeError: - raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac) - - -def to_mac(ipv6_address): - address = netaddr.IPAddress(ipv6_address) - mask1 = netaddr.IPAddress('::ffff:ffff:ffff:ffff') - mask2 = netaddr.IPAddress('::0200:0:0:0') - mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words - return ':'.join(['%02x' % i for i in mac64[0:3] + mac64[5:8]]) - - def utcnow(): """Overridable version of datetime.datetime.utcnow.""" if utcnow.override_time: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9780c69a6..4dce3b41f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -60,6 +60,7 @@ from nova import flags from nova import log as logging from nova import utils from nova import vnc +from nova import ipv6 from nova.auth import manager from nova.compute import instance_types from nova.compute import power_state @@ -185,7 +186,7 @@ def _get_network_info(instance): prefix = network['cidr_v6'] mac = instance['mac_address'] return { - 'ip': utils.to_global_ipv6(prefix, mac), + 'ip': ipv6.to_global(prefix, mac), 'netmask': network['netmask_v6'], 'enabled': '1'} diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index fe9a74dd6..0b05e702a 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -34,6 +34,7 @@ from nova import log as logging from nova import exception from nova import utils from nova import flags +from nova import ipv6 from nova.auth.manager import AuthManager from nova.compute import power_state @@ -808,7 +809,7 @@ class VMOps(object): def ip6_dict(): return { - "ip": utils.to_global_ipv6(network['cidr_v6'], + "ip": ipv6.to_global(network['cidr_v6'], instance['mac_address']), "netmask": network['netmask_v6'], "enabled": "1"} -- cgit From 2e44facea2f7b2c12dec9a14ea3595aadd8a35fc Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 11 May 2011 10:40:54 -0500 Subject: Code cleanup --- nova/notifier/__init__.py | 22 +++++++++++++--------- nova/tests/test_notifier.py | 12 +++++------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index 942c1a1a2..58809f179 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -15,6 +15,7 @@ import datetime import json +import uuid from nova import flags from nova import utils @@ -41,6 +42,7 @@ def notify(event_name, publisher_id, event_type, priority, payload): Message format is as follows: + message_id - a UUID representing the id for this notification publisher_id - the source worker_type.host of the message timestamp - the GMT timestamp the notification was sent at event_type - the literal type of event (ex. Instance Creation) @@ -48,23 +50,25 @@ def notify(event_name, publisher_id, event_type, priority, payload): the set (DEBUG, WARN, INFO, ERROR, CRITICAL) payload - A python dictionary of attributes - The payload will be constructed as a dictionary of the above attributes, - and converted into a JSON dump, which will then be sent via the transport - mechanism defined by the driver. + The message body will be constructed as a dictionary of the above + attributes, and converted into a JSON dump, which will then be sent + via the transport mechanism defined by the driver. Message example: - { 'publisher_id': 'compute.host1', - 'timestamp': '2011-05-09 22:00:14.621831', - 'priority': 'WARN', - 'event_type': 'compute.create_instance', - 'payload': {'instance_id': 12, ... }} + {'message_id': str(uuid.uuid4()), + 'publisher_id': 'compute.host1', + 'timestamp': datetime.datetime.utcnow(), + 'priority': 'WARN', + 'event_type': 'compute.create_instance', + 'payload': {'instance_id': 12, ... }} """ if priority not in log_levels: raise BadPriorityException('%s not in valid priorities' % priority) driver = utils.import_class(FLAGS.notification_driver)() - message = dict(publisher_id=publisher_id, + message = dict(message_id=str(uuid.uuid4()), + publisher_id=publisher_id, event_type=event_type, priority=priority, payload=payload, diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 396ce13b1..640a0cb34 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -53,12 +53,13 @@ class NotifierTestCase(test.TestCase): annoying""" def message_assert(cls, blob): message = json.loads(blob) - fields = [ ('publisher_id', 'publisher_id'), - ('event_type', 'event_type'), - ('priority', 'WARN'), - ('payload', dict(a=3))] + fields = [('publisher_id', 'publisher_id'), + ('event_type', 'event_type'), + ('priority', 'WARN'), + ('payload', dict(a=3))] for k, v in fields: self.assertEqual(message[k], v) + self.assertTrue(len(message['message_id']) > 0) self.stubs.Set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', message_assert) @@ -81,9 +82,6 @@ class NotifierTestCase(test.TestCase): self.assertEqual(self.mock_cast, True) def test_invalid_priority(self): - self.stubs.Set(nova.flags.FLAGS, 'notification_driver', - 'nova.notifier.rabbit_notifier.RabbitNotifier') - self.mock_cast = False def mock_cast(cls, *args): pass -- cgit From c1fdb9a2e26b8d2d4f8faa4b7412e8f17ea939e9 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 11 May 2011 13:00:06 -0400 Subject: better pylint scores on imports --- .../sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py index 82129cfc4..03d346af4 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py @@ -14,8 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * +from sqlalchemy import Boolean, Column, DateTime, Integer +from sqlalchemy import MetaData, String, Table import datetime -- cgit From 44a482081b44d25738549a5a445c4d581f6816ae Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 11 May 2011 13:21:57 -0400 Subject: align filters on query --- nova/db/sqlalchemy/api.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 929959d8e..84b472264 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1500,10 +1500,10 @@ def quota_get(context, project_id, resource, session=None): if not session: session = get_session() result = session.query(models.Quota).\ - filter_by(project_id=project_id).\ - filter_by(resource=resource).\ - filter_by(deleted=False).\ - first() + filter_by(project_id=project_id).\ + filter_by(resource=resource).\ + filter_by(deleted=False).\ + first() if not result: raise exception.ProjectQuotaNotFound(project_id=project_id) return result @@ -1514,9 +1514,9 @@ def quota_get_all_by_project(context, project_id): session = get_session() result = {'project_id': project_id} rows = session.query(models.Quota).\ - filter_by(project_id=project_id).\ - filter_by(deleted=False).\ - all() + filter_by(project_id=project_id).\ + filter_by(deleted=False).\ + all() for row in rows: result[row.resource] = row.limit return result -- cgit From b3c07b0473fc1d1de805f7a538189b99873aaab3 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Wed, 11 May 2011 12:33:44 -0500 Subject: Removed commented out 'from nova import log as logging' line, per request from Brian Lamar --- nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py | 1 - .../sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py | 1 - nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py | 1 - nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py | 1 - nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py | 1 - .../db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py | 1 - .../migrate_repo/versions/015_add_auto_assign_to_floating_ips.py | 1 - 7 files changed, 7 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py b/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py index 4a6fd7599..8e0de4d2b 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py @@ -16,7 +16,6 @@ # under the License. from sqlalchemy import Column, Integer, MetaData, String, Table -# from nova import log as logging meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py b/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py index 81a924d42..4627d3332 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py @@ -16,7 +16,6 @@ # under the License. from sqlalchemy import Column, Integer, MetaData, String, Table -# from nova import log as logging meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py index 56b43ae48..6f2668040 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py @@ -14,7 +14,6 @@ # under the License. from sqlalchemy import Column, Integer, MetaData, String, Table -# from nova import log as logging meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py index 0f7ee92e6..a5b80586e 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py @@ -15,7 +15,6 @@ # under the License. from sqlalchemy import Column, Integer, MetaData, String, Table -# from nova import log as logging meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py b/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py index 294d3e698..10d250522 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py @@ -15,7 +15,6 @@ from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer from sqlalchemy import MetaData, String, Table -# from nova import log as loggingo meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py index 77015f58f..7246839b7 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py @@ -16,7 +16,6 @@ # under the License.from sqlalchemy import * from sqlalchemy import Column, Integer, MetaData, Table -# from nova import log as logging meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py index 5b2950f32..375760c84 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -16,7 +16,6 @@ # under the License. from sqlalchemy import Boolean, Column, MetaData, Table -# from nova import log as loggingo meta = MetaData() -- cgit From afac61da9bb77cb2b4b0d6e79f47d4579ba9c9fc Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 11 May 2011 13:34:01 -0400 Subject: more filter alignment --- nova/db/sqlalchemy/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 84b472264..56b40f95b 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1554,9 +1554,9 @@ def quota_destroy_all_by_project(context, project_id): session = get_session() with session.begin(): quotas = session.query(models.Quota).\ - filter_by(project_id=project_id).\ - filter_by(deleted=False).\ - all() + filter_by(project_id=project_id).\ + filter_by(deleted=False).\ + all() for quota_ref in quotas: quota_ref.delete(session=session) -- cgit From d19d03fcfecfe51b63b1e681d9e94b9996cd9aef Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 11 May 2011 14:01:41 -0400 Subject: better function name --- nova/quota.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nova/quota.py b/nova/quota.py index b6090c5b3..a93cd0766 100644 --- a/nova/quota.py +++ b/nova/quota.py @@ -59,7 +59,7 @@ def get_quota(context, project_id): return rval -def _get_allowed_resources(requested, used, quota): +def _get_request_allotment(requested, used, quota): if quota is None: return requested return quota - used @@ -73,9 +73,9 @@ def allowed_instances(context, num_instances, instance_type): used_instances, used_cores = db.instance_data_get_for_project(context, project_id) quota = get_quota(context, project_id) - allowed_instances = _get_allowed_resources(num_instances, used_instances, + allowed_instances = _get_request_allotment(num_instances, used_instances, quota['instances']) - allowed_cores = _get_allowed_resources(num_cores, used_cores, + allowed_cores = _get_request_allotment(num_cores, used_cores, quota['cores']) allowed_instances = min(allowed_instances, int(allowed_cores // instance_type['vcpus'])) @@ -91,9 +91,9 @@ def allowed_volumes(context, num_volumes, size): used_volumes, used_gigabytes = db.volume_data_get_for_project(context, project_id) quota = get_quota(context, project_id) - allowed_volumes = _get_allowed_resources(num_volumes, used_volumes, + allowed_volumes = _get_request_allotment(num_volumes, used_volumes, quota['volumes']) - allowed_gigabytes = _get_allowed_resources(num_gigabytes, used_gigabytes, + allowed_gigabytes = _get_request_allotment(num_gigabytes, used_gigabytes, quota['gigabytes']) allowed_volumes = min(allowed_volumes, int(allowed_gigabytes // size)) @@ -106,7 +106,7 @@ def allowed_floating_ips(context, num_floating_ips): context = context.elevated() used_floating_ips = db.floating_ip_count_by_project(context, project_id) quota = get_quota(context, project_id) - allowed_floating_ips = _get_allowed_resources(num_floating_ips, + allowed_floating_ips = _get_request_allotment(num_floating_ips, used_floating_ips, quota['floating_ips']) return min(num_floating_ips, allowed_floating_ips) @@ -117,7 +117,7 @@ def allowed_metadata_items(context, num_metadata_items): project_id = context.project_id context = context.elevated() quota = get_quota(context, project_id) - allowed_metadata_items = _get_allowed_resources(num_metadata_items, 0, + allowed_metadata_items = _get_request_allotment(num_metadata_items, 0, quota['metadata_items']) return min(num_metadata_items, allowed_metadata_items) -- cgit From 3b0b69ddc02f57859b351d6d354a12d5955c09f1 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 11 May 2011 11:02:01 -0700 Subject: make sure proper exceptions are raised for ec2 id conversion and add tests --- bin/nova-manage | 2 +- nova/api/ec2/cloud.py | 2 +- nova/api/ec2/ec2utils.py | 5 ++++- nova/exception.py | 4 ++++ nova/tests/test_api.py | 19 ++++++++++++++++++- nova/tests/test_utils.py | 2 +- 6 files changed, 29 insertions(+), 5 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 2f6af6e2d..a36ec86d0 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -972,7 +972,7 @@ class ImageCommands(object): try: internal_id = ec2utils.ec2_id_to_id(old_image_id) image = self.image_service.show(context, internal_id) - except exception.NotFound: + except (exception.InvalidEc2Id, exception.ImageNotFound): image = self.image_service.show_by_name(context, old_image_id) return image['id'] diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 092b80fa2..be5dd38a0 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -906,7 +906,7 @@ class CloudController(object): try: internal_id = ec2utils.ec2_id_to_id(ec2_id) return self.image_service.show(context, internal_id) - except ValueError: + except (exception.InvalidEc2Id, exception.ImageNotFound): try: return self.image_service.show_by_name(context, ec2_id) except exception.NotFound: diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index 1ac48163c..163aa4ed2 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -21,7 +21,10 @@ from nova import exception def ec2_id_to_id(ec2_id): """Convert an ec2 ID (i-[base 16 number]) to an instance id (int)""" - return int(ec2_id.split('-')[-1], 16) + try: + return int(ec2_id.split('-')[-1], 16) + except ValueError: + raise exception.InvalidEc2Id(ec2_id=ec2_id) def id_to_ec2_id(instance_id, template='i-%08x'): diff --git a/nova/exception.py b/nova/exception.py index 9905fb19b..cf6069454 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -244,6 +244,10 @@ class InstanceUnacceptable(Invalid): message = _("Instance %(instance_id)s is unacceptable") + ": %(reason)s" +class InvalidEc2Id(Invalid): + message = _("Ec2 id %(ec2_id)s is unacceptable.") + + class NotFound(NovaException): message = _("Resource could not be found.") diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index fa0e56597..97f401b87 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -28,10 +28,12 @@ import StringIO import webob from nova import context +from nova import exception from nova import test from nova.api import ec2 -from nova.api.ec2 import cloud from nova.api.ec2 import apirequest +from nova.api.ec2 import cloud +from nova.api.ec2 import ec2utils from nova.auth import manager @@ -101,6 +103,21 @@ class XmlConversionTestCase(test.TestCase): self.assertEqual(conv('-0'), 0) +class Ec2utilsTestCase(test.TestCase): + def test_ec2_id_to_id(self): + self.assertEqual(ec2utils.ec2_id_to_id('i-0000001e'), 30) + self.assertEqual(ec2utils.ec2_id_to_id('ami-1d'), 29) + + def test_bad_ec2_id(self): + self.assertRaises(exception.InvalidEc2Id, + ec2utils.ec2_id_to_id, + 'badone') + + def test_id_to_ec2_id(self): + self.assertEqual(ec2utils.id_to_ec2_id(30), 'i-0000001e') + self.assertEqual(ec2utils.id_to_ec2_id(29, 'ami-%08x'), 'ami-0000001d') + + class ApiEc2TestCase(test.TestCase): """Unit test for the cloud controller on an EC2 API""" def setUp(self): diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index e7b5c826e..8f7e83c3e 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -17,9 +17,9 @@ import os import tempfile +from nova import exception from nova import test from nova import utils -from nova import exception class ExecuteTestCase(test.TestCase): -- cgit From 96f59724eaf57c8eae57b853484137de5fff672c Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 11 May 2011 13:10:40 -0500 Subject: Moved everything into notifier/api --- nova/notifier/__init__.py | 61 ------------------------------------ nova/notifier/api.py | 75 +++++++++++++++++++++++++++++++++++++++++++++ nova/tests/test_notifier.py | 22 +++++++------ 3 files changed, 87 insertions(+), 71 deletions(-) create mode 100644 nova/notifier/api.py diff --git a/nova/notifier/__init__.py b/nova/notifier/__init__.py index 0d4c970df..482d54e4f 100644 --- a/nova/notifier/__init__.py +++ b/nova/notifier/__init__.py @@ -12,64 +12,3 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - -import datetime -import uuid - -from nova import flags -from nova import utils - -FLAGS = flags.FLAGS - -flags.DEFINE_string('default_notification_level', 'INFO', - 'Default notification level for outgoing notifications') - -WARN = 'WARN' -INFO = 'INFO' -ERROR = 'ERROR' -CRITICAL = 'CRITICAL' -DEBUG = 'DEBUG' - -log_levels = (DEBUG, WARN, INFO, ERROR, CRITICAL) - -class BadPriorityException(Exception): - pass - -def notify(event_name, publisher_id, event_type, priority, payload): - """ - Sends a notification using the specified driver - - Message format is as follows: - - message_id - a UUID representing the id for this notification - publisher_id - the source worker_type.host of the message - timestamp - the GMT timestamp the notification was sent at - event_type - the literal type of event (ex. Instance Creation) - priority - patterned after the enumeration of Python logging levels in - the set (DEBUG, WARN, INFO, ERROR, CRITICAL) - payload - A python dictionary of attributes - - The message body will be constructed as a dictionary of the above - attributes, and converted into a JSON dump, which will then be sent - via the transport mechanism defined by the driver. - - Message example: - - {'message_id': str(uuid.uuid4()), - 'publisher_id': 'compute.host1', - 'timestamp': datetime.datetime.utcnow(), - 'priority': 'WARN', - 'event_type': 'compute.create_instance', - 'payload': {'instance_id': 12, ... }} - - """ - if priority not in log_levels: - raise BadPriorityException('%s not in valid priorities' % priority) - driver = utils.import_class(FLAGS.notification_driver)() - message = dict(message_id=str(uuid.uuid4()), - publisher_id=publisher_id, - event_type=event_type, - priority=priority, - payload=payload, - time=str(datetime.datetime.utcnow())) - driver.notify(message) diff --git a/nova/notifier/api.py b/nova/notifier/api.py new file mode 100644 index 000000000..04da8153e --- /dev/null +++ b/nova/notifier/api.py @@ -0,0 +1,75 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License.import datetime + +import datetime +import uuid + +from nova import flags +from nova import utils + +FLAGS = flags.FLAGS + +flags.DEFINE_string('default_notification_level', 'INFO', + 'Default notification level for outgoing notifications') + +WARN = 'WARN' +INFO = 'INFO' +ERROR = 'ERROR' +CRITICAL = 'CRITICAL' +DEBUG = 'DEBUG' + +log_levels = (DEBUG, WARN, INFO, ERROR, CRITICAL) + +class BadPriorityException(Exception): + pass + +def notify(event_name, publisher_id, event_type, priority, payload): + """ + Sends a notification using the specified driver + + Message format is as follows: + + message_id - a UUID representing the id for this notification + publisher_id - the source worker_type.host of the message + timestamp - the GMT timestamp the notification was sent at + event_type - the literal type of event (ex. Instance Creation) + priority - patterned after the enumeration of Python logging levels in + the set (DEBUG, WARN, INFO, ERROR, CRITICAL) + payload - A python dictionary of attributes + + The message body will be constructed as a dictionary of the above + attributes, and converted into a JSON dump, which will then be sent + via the transport mechanism defined by the driver. + + Message example: + + {'message_id': str(uuid.uuid4()), + 'publisher_id': 'compute.host1', + 'timestamp': datetime.datetime.utcnow(), + 'priority': 'WARN', + 'event_type': 'compute.create_instance', + 'payload': {'instance_id': 12, ... }} + + """ + if priority not in log_levels: + raise BadPriorityException('%s not in valid priorities' % priority) + driver = utils.import_class(FLAGS.notification_driver)() + message = dict(message_id=str(uuid.uuid4()), + publisher_id=publisher_id, + event_type=event_type, + priority=priority, + payload=payload, + timestamp=str(datetime.datetime.utcnow())) + driver.notify(message) diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index d2964c42f..64ec1dec5 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -18,7 +18,8 @@ import nova from nova import context from nova import flags from nova import rpc -from nova import notifier +import nova.notifier.api +from nova.notifier.api import notify from nova.notifier import no_op_notifier from nova.notifier import rabbit_notifier from nova import test @@ -45,8 +46,8 @@ class NotifierTestCase(test.TestCase): class Mock(object): pass - nova.notifier.notify('event_name', 'publisher_id', 'event_type', - nova.notifier.WARN, dict(a=3)) + notify('event_name', 'publisher_id', 'event_type', + nova.notifier.api.WARN, dict(a=3)) self.assertEqual(self.notify_called, True) def test_verify_message_format(self): @@ -60,11 +61,12 @@ class NotifierTestCase(test.TestCase): for k, v in fields: self.assertEqual(message[k], v) self.assertTrue(len(message['message_id']) > 0) + self.assertTrue(len(message['timestamp']) > 0) self.stubs.Set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', message_assert) - nova.notifier.notify('event_name', 'publisher_id', 'event_type', - nova.notifier.WARN, dict(a=3)) + notify('event_name', 'publisher_id', 'event_type', + nova.notifier.api.WARN, dict(a=3)) def test_send_rabbit_notification(self): self.stubs.Set(nova.flags.FLAGS, 'notification_driver', @@ -76,8 +78,8 @@ class NotifierTestCase(test.TestCase): class Mock(object): pass self.stubs.Set(nova.rpc, 'cast', mock_cast) - nova.notifier.notify('event_name', 'publisher_id', 'event_type', - nova.notifier.WARN, dict(a=3)) + notify('event_name', 'publisher_id', 'event_type', + nova.notifier.api.WARN, dict(a=3)) self.assertEqual(self.mock_cast, True) @@ -89,8 +91,8 @@ class NotifierTestCase(test.TestCase): pass self.stubs.Set(nova.rpc, 'cast', mock_cast) - self.assertRaises(nova.notifier.BadPriorityException, - nova.notifier.notify, 'event_name', 'publisher_id', + self.assertRaises(nova.notifier.api.BadPriorityException, + notify, 'event_name', 'publisher_id', 'event_type', 'not a priority', dict(a=3)) def test_rabbit_priority_queue(self): @@ -105,7 +107,7 @@ class NotifierTestCase(test.TestCase): self.test_topic = topic self.stubs.Set(nova.rpc, 'cast', mock_cast) - nova.notifier.notify('event_name', 'publisher_id', + notify('event_name', 'publisher_id', 'event_type', 'DEBUG', dict(a=3)) self.assertEqual(self.test_topic, 'testnotify.debug') -- cgit From 4d18824aee8598473ba2c05b23466ac7be199dc7 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 11 May 2011 13:22:55 -0500 Subject: Pep8 stuff --- nova/notifier/api.py | 2 ++ nova/notifier/log_notifier.py | 5 +++-- nova/notifier/no_op_notifier.py | 3 +++ nova/notifier/rabbit_notifier.py | 2 +- nova/tests/test_notifier.py | 16 ++++++++++------ 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 04da8153e..7090af5f4 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -32,9 +32,11 @@ DEBUG = 'DEBUG' log_levels = (DEBUG, WARN, INFO, ERROR, CRITICAL) + class BadPriorityException(Exception): pass + def notify(event_name, publisher_id, event_type, priority, payload): """ Sends a notification using the specified driver diff --git a/nova/notifier/log_notifier.py b/nova/notifier/log_notifier.py index 05126b591..4f99c589a 100644 --- a/nova/notifier/log_notifier.py +++ b/nova/notifier/log_notifier.py @@ -20,6 +20,7 @@ from nova import log as logging FLAGS = flags.FLAGS + class LogNotifier(object): """ log notifications using nova's default logging system """ @@ -28,6 +29,6 @@ class LogNotifier(object): priority = payload.get('priority', FLAGS.default_notification_level) priority = priority.lower() - logger = logging.getLogger('nova.notification.%s' % payload['event_type']) + logger = logging.getLogger( + 'nova.notification.%s' % payload['event_type']) getattr(logger, priority)(json.dumps(payload)) - diff --git a/nova/notifier/no_op_notifier.py b/nova/notifier/no_op_notifier.py index f425f06ea..400216f32 100644 --- a/nova/notifier/no_op_notifier.py +++ b/nova/notifier/no_op_notifier.py @@ -13,7 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. + class NoopNotifier(object): + """A notifier that doesn't actually do anything. Simply a placeholder""" + def notify(self, payload): """Notifies the recipient of the desired event given the model""" pass diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index 4b6538696..6f0927e9f 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -21,7 +21,7 @@ from nova import rpc FLAGS = flags.FLAGS -flags.DEFINE_string('notification_topic', 'notifications', +flags.DEFINE_string('notification_topic', 'notifications', 'RabbitMQ topic used for Nova notifications') diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 64ec1dec5..b9a74a761 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -26,6 +26,7 @@ from nova import test import stubout + class NotifierTestCase(test.TestCase): """Test case for notifications""" def setUp(self): @@ -38,6 +39,7 @@ class NotifierTestCase(test.TestCase): def test_send_notification(self): self.notify_called = False + def mock_notify(cls, *args): self.notify_called = True @@ -52,7 +54,8 @@ class NotifierTestCase(test.TestCase): def test_verify_message_format(self): """A test to ensure changing the message format is prohibitively - annoying""" + annoying""" + def message_assert(cls, message): fields = [('publisher_id', 'publisher_id'), ('event_type', 'event_type'), @@ -72,12 +75,14 @@ class NotifierTestCase(test.TestCase): self.stubs.Set(nova.flags.FLAGS, 'notification_driver', 'nova.notifier.rabbit_notifier.RabbitNotifier') self.mock_cast = False + def mock_cast(cls, *args): self.mock_cast = True class Mock(object): pass - self.stubs.Set(nova.rpc, 'cast', mock_cast) + + self.stubs.Set(nova.rpc, 'cast', mock_cast) notify('event_name', 'publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3)) @@ -90,8 +95,8 @@ class NotifierTestCase(test.TestCase): class Mock(object): pass - self.stubs.Set(nova.rpc, 'cast', mock_cast) - self.assertRaises(nova.notifier.api.BadPriorityException, + self.stubs.Set(nova.rpc, 'cast', mock_cast) + self.assertRaises(nova.notifier.api.BadPriorityException, notify, 'event_name', 'publisher_id', 'event_type', 'not a priority', dict(a=3)) @@ -106,8 +111,7 @@ class NotifierTestCase(test.TestCase): def mock_cast(context, topic, msg): self.test_topic = topic - self.stubs.Set(nova.rpc, 'cast', mock_cast) + self.stubs.Set(nova.rpc, 'cast', mock_cast) notify('event_name', 'publisher_id', 'event_type', 'DEBUG', dict(a=3)) self.assertEqual(self.test_topic, 'testnotify.debug') - -- cgit From 79466a3a7b67478871f178115d95378643caf29f Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 11 May 2011 14:32:28 -0400 Subject: oops fixed a docstring --- nova/db/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/db/api.py b/nova/db/api.py index b5550fcbc..ef8aa1143 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -772,7 +772,7 @@ def quota_get_all_by_project(context, project_id): def quota_update(context, project_id, resource, limit): - """Update a quota or raise if it does not exist""" + """Update a quota or raise if it does not exist.""" return IMPL.quota_update(context, project_id, resource, limit) -- cgit From 81a65d46d261cb6998c6d714ca5769a661ad31ab Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 11 May 2011 15:17:14 -0400 Subject: make instance.instance_type_id an integer to support joins in postgres --- .../016_make_instance_type_id_an_integer.py | 61 ++++++++++++++++++++++ nova/db/sqlalchemy/models.py | 2 +- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py new file mode 100644 index 000000000..5d95c1024 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py @@ -0,0 +1,61 @@ +from sqlalchemy import Column, Integer, MetaData, String, Table + +meta = MetaData() + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + types = {} + for instance in migrate_engine.execute(instances.select()): + try: + types[instance.id] = int(instance.instance_type_id) + except ValueError: + types[instance.id] = None + + integer_column = Column('instance_type_id_int', Integer(), nullable=True) + string_column = instances.c.instance_type_id + + integer_column.create(instances) + for instance_id, instance_type_id in types.iteritems(): + update = instances.update().\ + where(instances.c.id == instance_id).\ + values(instance_type_id_int=instance_type_id) + migrate_engine.execute(update) + + string_column.alter(name='instance_type_id_str') + integer_column.alter(name='instance_type_id') + string_column.drop() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + integer_column = instances.c.instance_type_id + string_column = Column('instance_type_id_str', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + + types = {} + for instance in migrate_engine.execute(instances.select()): + if instance.instance_type_id is None: + types[instance.id] = None + else: + types[instance.id] = str(instance.instance_type_id) + + string_column.create(instances) + for instance_id, instance_type_id in types.iteritems(): + update = instances.update().\ + where(instances.c.id == instance_id).\ + values(instance_type_id_str=instance_type_id) + migrate_engine.execute(update) + + integer_column.alter(name='instance_type_id_int') + string_column.alter(name='instance_type_id') + integer_column.drop() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 36a084a1d..486364322 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -209,7 +209,7 @@ class Instance(BASE, NovaBase): hostname = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) - instance_type_id = Column(String(255)) + instance_type_id = Column(Integer) user_data = Column(Text) -- cgit From 8447c6e18e68eeb23175ddafdac1da93c538d734 Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 11 May 2011 14:24:01 -0500 Subject: docstring cleanup, nova/network dir --- nova/network/api.py | 45 +++++---- nova/network/linux_net.py | 223 ++++++++++++++++++++++-------------------- nova/network/manager.py | 67 +++++++------ nova/network/vmwareapi_net.py | 14 +-- nova/network/xenapi_net.py | 18 ++-- 5 files changed, 188 insertions(+), 179 deletions(-) diff --git a/nova/network/api.py b/nova/network/api.py index 1d8193b28..e2eacdf42 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -16,9 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Handles all requests relating to instances (guest vms). -""" +"""Handles all requests relating to instances (guest vms).""" from nova import db from nova import exception @@ -28,6 +26,7 @@ from nova import quota from nova import rpc from nova.db import base + FLAGS = flags.FLAGS LOG = logging.getLogger('nova.network') @@ -37,19 +36,19 @@ class API(base.Base): def allocate_floating_ip(self, context): if quota.allowed_floating_ips(context, 1) < 1: - LOG.warn(_("Quota exceeeded for %s, tried to allocate " - "address"), - context.project_id) - raise quota.QuotaError(_("Address quota exceeded. You cannot " - "allocate any more addresses")) + LOG.warn(_('Quota exceeeded for %s, tried to allocate ' + 'address'), + context.project_id) + raise quota.QuotaError(_('Address quota exceeded. You cannot ' + 'allocate any more addresses')) # NOTE(vish): We don't know which network host should get the ip # when we allocate, so just send it to any one. This # will probably need to move into a network supervisor # at some point. return rpc.call(context, FLAGS.network_topic, - {"method": "allocate_floating_ip", - "args": {"project_id": context.project_id}}) + {'method': 'allocate_floating_ip', + 'args': {'project_id': context.project_id}}) def release_floating_ip(self, context, address, affect_auto_assigned=False): @@ -62,8 +61,8 @@ class API(base.Base): # at some point. rpc.cast(context, FLAGS.network_topic, - {"method": "deallocate_floating_ip", - "args": {"floating_address": floating_ip['address']}}) + {'method': 'deallocate_floating_ip', + 'args': {'floating_address': floating_ip['address']}}) def associate_floating_ip(self, context, floating_ip, fixed_ip, affect_auto_assigned=False): @@ -74,17 +73,17 @@ class API(base.Base): return # Check if the floating ip address is allocated if floating_ip['project_id'] is None: - raise exception.ApiError(_("Address (%s) is not allocated") % + raise exception.ApiError(_('Address (%s) is not allocated') % floating_ip['address']) # Check if the floating ip address is allocated to the same project if floating_ip['project_id'] != context.project_id: - LOG.warn(_("Address (%(address)s) is not allocated to your " - "project (%(project)s)"), + LOG.warn(_('Address (%(address)s) is not allocated to your ' + 'project (%(project)s)'), {'address': floating_ip['address'], 'project': context.project_id}) - raise exception.ApiError(_("Address (%(address)s) is not " - "allocated to your project" - "(%(project)s)") % + raise exception.ApiError(_('Address (%(address)s) is not ' + 'allocated to your project' + '(%(project)s)') % {'address': floating_ip['address'], 'project': context.project_id}) # NOTE(vish): Perhaps we should just pass this on to compute and @@ -92,9 +91,9 @@ class API(base.Base): host = fixed_ip['network']['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.network_topic, host), - {"method": "associate_floating_ip", - "args": {"floating_address": floating_ip['address'], - "fixed_address": fixed_ip['address']}}) + {'method': 'associate_floating_ip', + 'args': {'floating_address': floating_ip['address'], + 'fixed_address': fixed_ip['address']}}) def disassociate_floating_ip(self, context, address, affect_auto_assigned=False): @@ -108,5 +107,5 @@ class API(base.Base): host = floating_ip['fixed_ip']['network']['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.network_topic, host), - {"method": "disassociate_floating_ip", - "args": {"floating_address": floating_ip['address']}}) + {'method': 'disassociate_floating_ip', + 'args': {'floating_address': floating_ip['address']}}) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index b50a4b4ea..af91804a1 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -15,13 +15,12 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -""" -Implements vlans, bridges, and iptables rules using linux utilities. -""" +"""Implements vlans, bridges, and iptables rules using linux utilities.""" + +import calendar import inspect import os -import calendar from nova import db from nova import exception @@ -29,12 +28,13 @@ from nova import flags from nova import log as logging from nova import utils + LOG = logging.getLogger("nova.linux_net") def _bin_file(script): - """Return the absolute path to scipt in the bin directory""" - return os.path.abspath(os.path.join(__file__, "../../../bin", script)) + """Return the absolute path to scipt in the bin directory.""" + return os.path.abspath(os.path.join(__file__, '../../../bin', script)) FLAGS = flags.FLAGS @@ -66,11 +66,13 @@ binary_name = os.path.basename(inspect.stack()[-1][1]) class IptablesRule(object): - """An iptables rule + """An iptables rule. You shouldn't need to use this class directly, it's only used by - IptablesManager + IptablesManager. + """ + def __init__(self, chain, rule, wrap=True, top=False): self.chain = chain self.rule = rule @@ -95,7 +97,7 @@ class IptablesRule(object): class IptablesTable(object): - """An iptables table""" + """An iptables table.""" def __init__(self): self.rules = [] @@ -103,15 +105,16 @@ class IptablesTable(object): self.unwrapped_chains = set() def add_chain(self, name, wrap=True): - """Adds a named chain to the table + """Adds a named chain to the table. The chain name is wrapped to be unique for the component creating it, so different components of Nova can safely create identically named chains without interfering with one another. At the moment, its wrapped name is -, - so if nova-compute creates a chain named "OUTPUT", it'll actually - end up named "nova-compute-OUTPUT". + so if nova-compute creates a chain named 'OUTPUT', it'll actually + end up named 'nova-compute-OUTPUT'. + """ if wrap: self.chains.add(name) @@ -119,12 +122,13 @@ class IptablesTable(object): self.unwrapped_chains.add(name) def remove_chain(self, name, wrap=True): - """Remove named chain + """Remove named chain. This removal "cascades". All rule in the chain are removed, as are all rules in other chains that jump to it. If the chain is not found, this is merely logged. + """ if wrap: chain_set = self.chains @@ -132,7 +136,7 @@ class IptablesTable(object): chain_set = self.unwrapped_chains if name not in chain_set: - LOG.debug(_("Attempted to remove chain %s which doesn't exist"), + LOG.debug(_('Attempted to remove chain %s which does not exist'), name) return @@ -147,17 +151,18 @@ class IptablesTable(object): self.rules = filter(lambda r: jump_snippet not in r.rule, self.rules) def add_rule(self, chain, rule, wrap=True, top=False): - """Add a rule to the table + """Add a rule to the table. This is just like what you'd feed to iptables, just without - the "-A " bit at the start. + the '-A ' bit at the start. However, if you need to jump to one of your wrapped chains, prepend its name with a '$' which will ensure the wrapping is applied correctly. + """ if wrap and chain not in self.chains: - raise ValueError(_("Unknown chain: %r") % chain) + raise ValueError(_('Unknown chain: %r') % chain) if '$' in rule: rule = ' '.join(map(self._wrap_target_chain, rule.split(' '))) @@ -170,23 +175,24 @@ class IptablesTable(object): return s def remove_rule(self, chain, rule, wrap=True, top=False): - """Remove a rule from a chain + """Remove a rule from a chain. Note: The rule must be exactly identical to the one that was added. You cannot switch arguments around like you can with the iptables CLI tool. + """ try: self.rules.remove(IptablesRule(chain, rule, wrap, top)) except ValueError: - LOG.debug(_("Tried to remove rule that wasn't there:" - " %(chain)r %(rule)r %(wrap)r %(top)r"), + LOG.debug(_('Tried to remove rule that was not there:' + ' %(chain)r %(rule)r %(wrap)r %(top)r'), {'chain': chain, 'rule': rule, 'top': top, 'wrap': wrap}) class IptablesManager(object): - """Wrapper for iptables + """Wrapper for iptables. See IptablesTable for some usage docs @@ -205,7 +211,9 @@ class IptablesManager(object): For ipv4, the builtin PREROUTING, OUTPUT, and POSTROUTING nat chains are wrapped in the same was as the builtin filter chains. Additionally, there's a snat chain that is applied after the POSTROUTING chain. + """ + def __init__(self, execute=None): if not execute: self.execute = _execute @@ -267,11 +275,12 @@ class IptablesManager(object): @utils.synchronized('iptables', external=True) def apply(self): - """Apply the current in-memory set of iptables rules + """Apply the current in-memory set of iptables rules. This will blow away any rules left over from previous runs of the same component of Nova, and replace them with our current set of rules. This happens atomically, thanks to iptables-restore. + """ s = [('iptables', self.ipv4)] if FLAGS.use_ipv6: @@ -348,63 +357,63 @@ class IptablesManager(object): def metadata_forward(): - """Create forwarding rule for metadata""" - iptables_manager.ipv4['nat'].add_rule("PREROUTING", - "-s 0.0.0.0/0 -d 169.254.169.254/32 " - "-p tcp -m tcp --dport 80 -j DNAT " - "--to-destination %s:%s" % \ + """Create forwarding rule for metadata.""" + iptables_manager.ipv4['nat'].add_rule('PREROUTING', + '-s 0.0.0.0/0 -d 169.254.169.254/32 ' + '-p tcp -m tcp --dport 80 -j DNAT ' + '--to-destination %s:%s' % \ (FLAGS.ec2_dmz_host, FLAGS.ec2_port)) iptables_manager.apply() def init_host(): - """Basic networking setup goes here""" + """Basic networking setup goes here.""" # NOTE(devcamcar): Cloud public SNAT entries and the default # SNAT rule for outbound traffic. - iptables_manager.ipv4['nat'].add_rule("snat", - "-s %s -j SNAT --to-source %s" % \ + iptables_manager.ipv4['nat'].add_rule('snat', + '-s %s -j SNAT --to-source %s' % \ (FLAGS.fixed_range, FLAGS.routing_source_ip)) - iptables_manager.ipv4['nat'].add_rule("POSTROUTING", - "-s %s -d %s -j ACCEPT" % \ + iptables_manager.ipv4['nat'].add_rule('POSTROUTING', + '-s %s -d %s -j ACCEPT' % \ (FLAGS.fixed_range, FLAGS.dmz_cidr)) - iptables_manager.ipv4['nat'].add_rule("POSTROUTING", - "-s %(range)s -d %(range)s " - "-j ACCEPT" % \ + iptables_manager.ipv4['nat'].add_rule('POSTROUTING', + '-s %(range)s -d %(range)s ' + '-j ACCEPT' % \ {'range': FLAGS.fixed_range}) iptables_manager.apply() def bind_floating_ip(floating_ip, check_exit_code=True): - """Bind ip to public interface""" + """Bind ip to public interface.""" _execute('sudo', 'ip', 'addr', 'add', floating_ip, 'dev', FLAGS.public_interface, check_exit_code=check_exit_code) def unbind_floating_ip(floating_ip): - """Unbind a public ip from public interface""" + """Unbind a public ip from public interface.""" _execute('sudo', 'ip', 'addr', 'del', floating_ip, 'dev', FLAGS.public_interface) def ensure_metadata_ip(): - """Sets up local metadata ip""" + """Sets up local metadata ip.""" _execute('sudo', 'ip', 'addr', 'add', '169.254.169.254/32', 'scope', 'link', 'dev', 'lo', check_exit_code=False) def ensure_vlan_forward(public_ip, port, private_ip): - """Sets up forwarding rules for vlan""" - iptables_manager.ipv4['filter'].add_rule("FORWARD", - "-d %s -p udp " - "--dport 1194 " - "-j ACCEPT" % private_ip) - iptables_manager.ipv4['nat'].add_rule("PREROUTING", - "-d %s -p udp " - "--dport %s -j DNAT --to %s:1194" % + """Sets up forwarding rules for vlan.""" + iptables_manager.ipv4['filter'].add_rule('FORWARD', + '-d %s -p udp ' + '--dport 1194 ' + '-j ACCEPT' % private_ip) + iptables_manager.ipv4['nat'].add_rule('PREROUTING', + '-d %s -p udp ' + '--dport %s -j DNAT --to %s:1194' % (public_ip, port, private_ip)) iptables_manager.ipv4['nat'].add_rule("OUTPUT", "-d %s -p udp " @@ -414,37 +423,37 @@ def ensure_vlan_forward(public_ip, port, private_ip): def ensure_floating_forward(floating_ip, fixed_ip): - """Ensure floating ip forwarding rule""" + """Ensure floating ip forwarding rule.""" for chain, rule in floating_forward_rules(floating_ip, fixed_ip): iptables_manager.ipv4['nat'].add_rule(chain, rule) iptables_manager.apply() def remove_floating_forward(floating_ip, fixed_ip): - """Remove forwarding for floating ip""" + """Remove forwarding for floating ip.""" for chain, rule in floating_forward_rules(floating_ip, fixed_ip): iptables_manager.ipv4['nat'].remove_rule(chain, rule) iptables_manager.apply() def floating_forward_rules(floating_ip, fixed_ip): - return [("PREROUTING", "-d %s -j DNAT --to %s" % (floating_ip, fixed_ip)), - ("OUTPUT", "-d %s -j DNAT --to %s" % (floating_ip, fixed_ip)), - ("floating-snat", - "-s %s -j SNAT --to %s" % (fixed_ip, floating_ip))] + return [('PREROUTING', '-d %s -j DNAT --to %s' % (floating_ip, fixed_ip)), + ('OUTPUT', '-d %s -j DNAT --to %s' % (floating_ip, fixed_ip)), + ('floating-snat', + '-s %s -j SNAT --to %s' % (fixed_ip, floating_ip))] def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): - """Create a vlan and bridge unless they already exist""" + """Create a vlan and bridge unless they already exist.""" interface = ensure_vlan(vlan_num) ensure_bridge(bridge, interface, net_attrs) def ensure_vlan(vlan_num): - """Create a vlan unless it already exists""" - interface = "vlan%s" % vlan_num + """Create a vlan unless it already exists.""" + interface = 'vlan%s' % vlan_num if not _device_exists(interface): - LOG.debug(_("Starting VLAN inteface %s"), interface) + LOG.debug(_('Starting VLAN inteface %s'), interface) _execute('sudo', 'vconfig', 'set_name_type', 'VLAN_PLUS_VID_NO_PAD') _execute('sudo', 'vconfig', 'add', FLAGS.vlan_interface, vlan_num) _execute('sudo', 'ip', 'link', 'set', interface, 'up') @@ -464,12 +473,13 @@ def ensure_bridge(bridge, interface, net_attrs=None): The code will attempt to move any ips that already exist on the interface onto the bridge and reset the default gateway if necessary. + """ if not _device_exists(bridge): - LOG.debug(_("Starting Bridge interface for %s"), interface) + LOG.debug(_('Starting Bridge interface for %s'), interface) _execute('sudo', 'brctl', 'addbr', bridge) _execute('sudo', 'brctl', 'setfd', bridge, 0) - # _execute("sudo brctl setageing %s 10" % bridge) + # _execute('sudo brctl setageing %s 10' % bridge) _execute('sudo', 'brctl', 'stp', bridge, 'off') _execute('sudo', 'ip', 'link', 'set', bridge, 'up') if net_attrs: @@ -477,15 +487,15 @@ def ensure_bridge(bridge, interface, net_attrs=None): # bridge for it to respond to reqests properly suffix = net_attrs['cidr'].rpartition('/')[2] out, err = _execute('sudo', 'ip', 'addr', 'add', - "%s/%s" % + '%s/%s' % (net_attrs['gateway'], suffix), 'brd', net_attrs['broadcast'], 'dev', bridge, check_exit_code=False) - if err and err != "RTNETLINK answers: File exists\n": - raise exception.Error("Failed to add ip: %s" % err) + if err and err != 'RTNETLINK answers: File exists\n': + raise exception.Error('Failed to add ip: %s' % err) if(FLAGS.use_ipv6): _execute('sudo', 'ip', '-f', 'inet6', 'addr', 'change', net_attrs['cidr_v6'], @@ -501,17 +511,17 @@ def ensure_bridge(bridge, interface, net_attrs=None): # interface, so we move any ips to the bridge gateway = None out, err = _execute('sudo', 'route', '-n') - for line in out.split("\n"): + for line in out.split('\n'): fields = line.split() - if fields and fields[0] == "0.0.0.0" and fields[-1] == interface: + if fields and fields[0] == '0.0.0.0' and fields[-1] == interface: gateway = fields[1] _execute('sudo', 'route', 'del', 'default', 'gw', gateway, 'dev', interface, check_exit_code=False) out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface, 'scope', 'global') - for line in out.split("\n"): + for line in out.split('\n'): fields = line.split() - if fields and fields[0] == "inet": + if fields and fields[0] == 'inet': params = fields[1:-1] _execute(*_ip_bridge_cmd('del', params, fields[-1])) _execute(*_ip_bridge_cmd('add', params, bridge)) @@ -522,18 +532,18 @@ def ensure_bridge(bridge, interface, net_attrs=None): if (err and err != "device %s is already a member of a bridge; can't " "enslave it to bridge %s.\n" % (interface, bridge)): - raise exception.Error("Failed to add interface: %s" % err) + raise exception.Error('Failed to add interface: %s' % err) - iptables_manager.ipv4['filter'].add_rule("FORWARD", - "--in-interface %s -j ACCEPT" % \ + iptables_manager.ipv4['filter'].add_rule('FORWARD', + '--in-interface %s -j ACCEPT' % \ bridge) - iptables_manager.ipv4['filter'].add_rule("FORWARD", - "--out-interface %s -j ACCEPT" % \ + iptables_manager.ipv4['filter'].add_rule('FORWARD', + '--out-interface %s -j ACCEPT' % \ bridge) def get_dhcp_leases(context, network_id): - """Return a network's hosts config in dnsmasq leasefile format""" + """Return a network's hosts config in dnsmasq leasefile format.""" hosts = [] for fixed_ip_ref in db.network_get_associated_fixed_ips(context, network_id): @@ -542,7 +552,7 @@ def get_dhcp_leases(context, network_id): def get_dhcp_hosts(context, network_id): - """Get a string containing a network's hosts config in dhcp-host format""" + """Get network's hosts config in dhcp-host format.""" hosts = [] for fixed_ip_ref in db.network_get_associated_fixed_ips(context, network_id): @@ -555,10 +565,11 @@ def get_dhcp_hosts(context, network_id): # aren't reloaded. @utils.synchronized('dnsmasq_start') def update_dhcp(context, network_id): - """(Re)starts a dnsmasq server for a given network + """(Re)starts a dnsmasq server for a given network. + + If a dnsmasq instance is already running then send a HUP + signal causing it to reload, otherwise spawn a new instance. - if a dnsmasq instance is already running then send a HUP - signal causing it to reload, otherwise spawn a new instance """ network_ref = db.network_get(context, network_id) @@ -573,16 +584,16 @@ def update_dhcp(context, network_id): # if dnsmasq is already running, then tell it to reload if pid: - out, _err = _execute('cat', "/proc/%d/cmdline" % pid, + out, _err = _execute('cat', '/proc/%d/cmdline' % pid, check_exit_code=False) if conffile in out: try: _execute('sudo', 'kill', '-HUP', pid) return except Exception as exc: # pylint: disable=W0703 - LOG.debug(_("Hupping dnsmasq threw %s"), exc) + LOG.debug(_('Hupping dnsmasq threw %s'), exc) else: - LOG.debug(_("Pid %d is stale, relaunching dnsmasq"), pid) + LOG.debug(_('Pid %d is stale, relaunching dnsmasq'), pid) # FLAGFILE and DNSMASQ_INTERFACE in env env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile, @@ -625,18 +636,18 @@ interface %s try: _execute('sudo', 'kill', pid) except Exception as exc: # pylint: disable=W0703 - LOG.debug(_("killing radvd threw %s"), exc) + LOG.debug(_('killing radvd threw %s'), exc) else: - LOG.debug(_("Pid %d is stale, relaunching radvd"), pid) + LOG.debug(_('Pid %d is stale, relaunching radvd'), pid) command = _ra_cmd(network_ref) _execute(*command) db.network_update(context, network_id, - {"gateway_v6": + {'gateway_v6': utils.get_my_linklocal(network_ref['bridge'])}) def _host_lease(fixed_ip_ref): - """Return a host string for an address in leasefile format""" + """Return a host string for an address in leasefile format.""" instance_ref = fixed_ip_ref['instance'] if instance_ref['updated_at']: timestamp = instance_ref['updated_at'] @@ -645,39 +656,39 @@ def _host_lease(fixed_ip_ref): seconds_since_epoch = calendar.timegm(timestamp.utctimetuple()) - return "%d %s %s %s *" % (seconds_since_epoch + FLAGS.dhcp_lease_time, + return '%d %s %s %s *' % (seconds_since_epoch + FLAGS.dhcp_lease_time, instance_ref['mac_address'], fixed_ip_ref['address'], instance_ref['hostname'] or '*') def _host_dhcp(fixed_ip_ref): - """Return a host string for an address in dhcp-host format""" + """Return a host string for an address in dhcp-host format.""" instance_ref = fixed_ip_ref['instance'] - return "%s,%s.%s,%s" % (instance_ref['mac_address'], + return '%s,%s.%s,%s' % (instance_ref['mac_address'], instance_ref['hostname'], FLAGS.dhcp_domain, fixed_ip_ref['address']) def _execute(*cmd, **kwargs): - """Wrapper around utils._execute for fake_network""" + """Wrapper around utils._execute for fake_network.""" if FLAGS.fake_network: - LOG.debug("FAKE NET: %s", " ".join(map(str, cmd))) - return "fake", 0 + LOG.debug('FAKE NET: %s', ' '.join(map(str, cmd))) + return 'fake', 0 else: return utils.execute(*cmd, **kwargs) def _device_exists(device): - """Check if ethernet device exists""" + """Check if ethernet device exists.""" (_out, err) = _execute('ip', 'link', 'show', 'dev', device, check_exit_code=False) return not err def _dnsmasq_cmd(net): - """Builds dnsmasq command""" + """Builds dnsmasq command.""" cmd = ['sudo', '-E', 'dnsmasq', '--strict-order', '--bind-interfaces', @@ -696,7 +707,7 @@ def _dnsmasq_cmd(net): def _ra_cmd(net): - """Builds radvd command""" + """Builds radvd command.""" cmd = ['sudo', '-E', 'radvd', # '-u', 'nobody', '-C', '%s' % _ra_file(net['bridge'], 'conf'), @@ -705,44 +716,44 @@ def _ra_cmd(net): def _stop_dnsmasq(network): - """Stops the dnsmasq instance for a given network""" + """Stops the dnsmasq instance for a given network.""" pid = _dnsmasq_pid_for(network) if pid: try: _execute('sudo', 'kill', '-TERM', pid) except Exception as exc: # pylint: disable=W0703 - LOG.debug(_("Killing dnsmasq threw %s"), exc) + LOG.debug(_('Killing dnsmasq threw %s'), exc) def _dhcp_file(bridge, kind): - """Return path to a pid, leases or conf file for a bridge""" + """Return path to a pid, leases or conf file for a bridge.""" if not os.path.exists(FLAGS.networks_path): os.makedirs(FLAGS.networks_path) - return os.path.abspath("%s/nova-%s.%s" % (FLAGS.networks_path, + return os.path.abspath('%s/nova-%s.%s' % (FLAGS.networks_path, bridge, kind)) def _ra_file(bridge, kind): - """Return path to a pid or conf file for a bridge""" + """Return path to a pid or conf file for a bridge.""" if not os.path.exists(FLAGS.networks_path): os.makedirs(FLAGS.networks_path) - return os.path.abspath("%s/nova-ra-%s.%s" % (FLAGS.networks_path, + return os.path.abspath('%s/nova-ra-%s.%s' % (FLAGS.networks_path, bridge, kind)) def _dnsmasq_pid_for(bridge): - """Returns the pid for prior dnsmasq instance for a bridge + """Returns the pid for prior dnsmasq instance for a bridge. - Returns None if no pid file exists + Returns None if no pid file exists. - If machine has rebooted pid might be incorrect (caller should check) - """ + If machine has rebooted pid might be incorrect (caller should check). + """ pid_file = _dhcp_file(bridge, 'pid') if os.path.exists(pid_file): @@ -751,13 +762,13 @@ def _dnsmasq_pid_for(bridge): def _ra_pid_for(bridge): - """Returns the pid for prior radvd instance for a bridge + """Returns the pid for prior radvd instance for a bridge. - Returns None if no pid file exists + Returns None if no pid file exists. - If machine has rebooted pid might be incorrect (caller should check) - """ + If machine has rebooted pid might be incorrect (caller should check). + """ pid_file = _ra_file(bridge, 'pid') if os.path.exists(pid_file): @@ -766,7 +777,7 @@ def _ra_pid_for(bridge): def _ip_bridge_cmd(action, params, device): - """Build commands to add/del ips to bridges/devices""" + """Build commands to add/del ips to bridges/devices.""" cmd = ['sudo', 'ip', 'addr', action] cmd.extend(params) diff --git a/nova/network/manager.py b/nova/network/manager.py index 0dd7f2360..5a6fdde5a 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -16,8 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Network Hosts are responsible for allocating ips and setting up network. +"""Network Hosts are responsible for allocating ips and setting up network. There are multiple backend drivers that handle specific types of networking topologies. All of the network commands are issued to a subclass of @@ -61,6 +60,8 @@ from nova import rpc LOG = logging.getLogger("nova.network.manager") + + FLAGS = flags.FLAGS flags.DEFINE_string('flat_network_bridge', 'br100', 'Bridge for simple network instances') @@ -111,7 +112,9 @@ class NetworkManager(manager.SchedulerDependentManager): """Implements common network manager functionality. This class must be subclassed to support specific topologies. + """ + timeout_fixed_ips = True def __init__(self, network_driver=None, *args, **kwargs): @@ -122,9 +125,7 @@ class NetworkManager(manager.SchedulerDependentManager): *args, **kwargs) def init_host(self): - """Do any initialization that needs to be run if this is a - standalone service. - """ + """Do any initialization for a standalone service.""" self.driver.init_host() self.driver.ensure_metadata_ip() # Set up networking for the projects for which we're already @@ -154,11 +155,11 @@ class NetworkManager(manager.SchedulerDependentManager): self.host, time) if num: - LOG.debug(_("Dissassociated %s stale fixed ip(s)"), num) + LOG.debug(_('Dissassociated %s stale fixed ip(s)'), num) def set_network_host(self, context, network_id): """Safely sets the host of the network.""" - LOG.debug(_("setting network host"), context=context) + LOG.debug(_('setting network host'), context=context) host = self.db.network_set_host(context, network_id, self.host) @@ -224,39 +225,39 @@ class NetworkManager(manager.SchedulerDependentManager): def lease_fixed_ip(self, context, mac, address): """Called by dhcp-bridge when ip is leased.""" - LOG.debug(_("Leasing IP %s"), address, context=context) + LOG.debug(_('Leasing IP %s'), address, context=context) fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] if not instance_ref: - raise exception.Error(_("IP %s leased that isn't associated") % + raise exception.Error(_('IP %s leased that is not associated') % address) if instance_ref['mac_address'] != mac: inst_addr = instance_ref['mac_address'] - raise exception.Error(_("IP %(address)s leased to bad" - " mac %(inst_addr)s vs %(mac)s") % locals()) + raise exception.Error(_('IP %(address)s leased to bad mac' + ' %(inst_addr)s vs %(mac)s') % locals()) now = datetime.datetime.utcnow() self.db.fixed_ip_update(context, fixed_ip_ref['address'], {'leased': True, 'updated_at': now}) if not fixed_ip_ref['allocated']: - LOG.warn(_("IP %s leased that was already deallocated"), address, + LOG.warn(_('IP %s leased that was already deallocated'), address, context=context) def release_fixed_ip(self, context, mac, address): """Called by dhcp-bridge when ip is released.""" - LOG.debug(_("Releasing IP %s"), address, context=context) + LOG.debug(_('Releasing IP %s'), address, context=context) fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] if not instance_ref: - raise exception.Error(_("IP %s released that isn't associated") % + raise exception.Error(_('IP %s released that is not associated') % address) if instance_ref['mac_address'] != mac: inst_addr = instance_ref['mac_address'] - raise exception.Error(_("IP %(address)s released from" - " bad mac %(inst_addr)s vs %(mac)s") % locals()) + raise exception.Error(_('IP %(address)s released from bad mac' + ' %(inst_addr)s vs %(mac)s') % locals()) if not fixed_ip_ref['leased']: - LOG.warn(_("IP %s released that was not leased"), address, + LOG.warn(_('IP %s released that was not leased'), address, context=context) self.db.fixed_ip_update(context, fixed_ip_ref['address'], @@ -286,8 +287,8 @@ class NetworkManager(manager.SchedulerDependentManager): return self.set_network_host(context, network_ref['id']) host = rpc.call(context, FLAGS.network_topic, - {"method": "set_network_host", - "args": {"network_id": network_ref['id']}}) + {'method': 'set_network_host', + 'args': {'network_id': network_ref['id']}}) return host def create_networks(self, context, cidr, num_networks, network_size, @@ -302,7 +303,7 @@ class NetworkManager(manager.SchedulerDependentManager): start = index * network_size start_v6 = index * network_size_v6 significant_bits = 32 - int(math.log(network_size, 2)) - cidr = "%s/%s" % (fixed_net[start], significant_bits) + cidr = '%s/%s' % (fixed_net[start], significant_bits) project_net = IPy.IP(cidr) net = {} net['bridge'] = FLAGS.flat_network_bridge @@ -313,13 +314,13 @@ class NetworkManager(manager.SchedulerDependentManager): net['broadcast'] = str(project_net.broadcast()) net['dhcp_start'] = str(project_net[2]) if num_networks > 1: - net['label'] = "%s_%d" % (label, count) + net['label'] = '%s_%d' % (label, count) else: net['label'] = label count += 1 if(FLAGS.use_ipv6): - cidr_v6 = "%s/%s" % (fixed_net_v6[start_v6], + cidr_v6 = '%s/%s' % (fixed_net_v6[start_v6], significant_bits_v6) net['cidr_v6'] = cidr_v6 project_net_v6 = IPy.IP(cidr_v6) @@ -386,13 +387,13 @@ class FlatManager(NetworkManager): Metadata forwarding must be handled by the gateway, and since nova does not do any setup in this mode, it must be done manually. Requests to 169.254.169.254 port 80 will need to be forwarded to the api server. + """ + timeout_fixed_ips = False def init_host(self): - """Do any initialization that needs to be run if this is a - standalone service. - """ + """Do any initialization for a standalone service.""" #Fix for bug 723298 - do not call init_host on superclass #Following code has been copied for NetworkManager.init_host ctxt = context.get_admin_context() @@ -433,12 +434,11 @@ class FlatDHCPManager(NetworkManager): FlatDHCPManager will start up one dhcp server to give out addresses. It never injects network settings into the guest. Otherwise it behaves like FlatDHCPManager. + """ def init_host(self): - """Do any initialization that needs to be run if this is a - standalone service. - """ + """Do any initialization for a standalone service.""" super(FlatDHCPManager, self).init_host() self.driver.metadata_forward() @@ -490,12 +490,11 @@ class VlanManager(NetworkManager): A dhcp server is run for each subnet, so each project will have its own. For this mode to be useful, each project will need a vpn to access the instances in its subnet. + """ def init_host(self): - """Do any initialization that needs to be run if this is a - standalone service. - """ + """Do any initialization for a standalone service.""" super(VlanManager, self).init_host() self.driver.metadata_forward() @@ -566,7 +565,7 @@ class VlanManager(NetworkManager): net['vlan'] = vlan net['bridge'] = 'br%s' % vlan if(FLAGS.use_ipv6): - cidr_v6 = "%s/%s" % (fixed_net_v6[start_v6], + cidr_v6 = '%s/%s' % (fixed_net_v6[start_v6], significant_bits_v6) net['cidr_v6'] = cidr_v6 @@ -600,8 +599,8 @@ class VlanManager(NetworkManager): return self.set_network_host(context, network_ref['id']) host = rpc.call(context, FLAGS.network_topic, - {"method": "set_network_host", - "args": {"network_id": network_ref['id']}}) + {'method': 'set_network_host', + 'args': {'network_id': network_ref['id']}}) return host diff --git a/nova/network/vmwareapi_net.py b/nova/network/vmwareapi_net.py index 9b2db7b8f..bf1070995 100644 --- a/nova/network/vmwareapi_net.py +++ b/nova/network/vmwareapi_net.py @@ -15,9 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Implements vlans for vmwareapi. -""" +"""Implements vlans for vmwareapi.""" from nova import db from nova import exception @@ -27,8 +25,10 @@ from nova import utils from nova.virt.vmwareapi_conn import VMWareAPISession from nova.virt.vmwareapi import network_utils + LOG = logging.getLogger("nova.network.vmwareapi_net") + FLAGS = flags.FLAGS flags.DEFINE_string('vlan_interface', 'vmnic0', 'Physical network adapter name in VMware ESX host for ' @@ -42,10 +42,10 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): host_username = FLAGS.vmwareapi_host_username host_password = FLAGS.vmwareapi_host_password if not host_ip or host_username is None or host_password is None: - raise Exception(_("Must specify vmwareapi_host_ip," - "vmwareapi_host_username " - "and vmwareapi_host_password to use" - "connection_type=vmwareapi")) + raise Exception(_('Must specify vmwareapi_host_ip,' + 'vmwareapi_host_username ' + 'and vmwareapi_host_password to use' + 'connection_type=vmwareapi')) session = VMWareAPISession(host_ip, host_username, host_password, FLAGS.vmwareapi_api_retry_count) vlan_interface = FLAGS.vlan_interface diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py index 8c22a7d4b..709ef7f34 100644 --- a/nova/network/xenapi_net.py +++ b/nova/network/xenapi_net.py @@ -15,9 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Implements vlans, bridges, and iptables rules using linux utilities. -""" +"""Implements vlans, bridges, and iptables rules using linux utilities.""" import os @@ -26,22 +24,24 @@ from nova import exception from nova import flags from nova import log as logging from nova import utils -from nova.virt.xenapi_conn import XenAPISession +from nova.virt import xenapi_conn from nova.virt.xenapi import network_utils + LOG = logging.getLogger("nova.xenapi_net") + FLAGS = flags.FLAGS def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): """Create a vlan and bridge unless they already exist.""" # Open xenapi session - LOG.debug("ENTERING ensure_vlan_bridge in xenapi net") + LOG.debug('ENTERING ensure_vlan_bridge in xenapi net') url = FLAGS.xenapi_connection_url username = FLAGS.xenapi_connection_username password = FLAGS.xenapi_connection_password - session = XenAPISession(url, username, password) + session = xenapi_conn.XenAPISession(url, username, password) # Check whether bridge already exists # Retrieve network whose name_label is "bridge" network_ref = network_utils.NetworkHelper.find_network_with_name_label( @@ -50,14 +50,14 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): if network_ref is None: # If bridge does not exists # 1 - create network - description = "network for nova bridge %s" % bridge + description = 'network for nova bridge %s' % bridge network_rec = {'name_label': bridge, 'name_description': description, 'other_config': {}} network_ref = session.call_xenapi('network.create', network_rec) # 2 - find PIF for VLAN - expr = 'field "device" = "%s" and \ - field "VLAN" = "-1"' % FLAGS.vlan_interface + expr = "field 'device' = '%s' and \ + field 'VLAN' = '-1'" % FLAGS.vlan_interface pifs = session.call_xenapi('PIF.get_all_records_where', expr) pif_ref = None # Multiple PIF are ok: we are dealing with a pool -- cgit From 17e06aa079b2961b7d6ba23f8032d003a2bf8b6a Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 11 May 2011 14:24:01 -0500 Subject: changes per review --- nova/network/linux_net.py | 2 -- nova/network/vmwareapi_net.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index af91804a1..85c4c278c 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -728,7 +728,6 @@ def _stop_dnsmasq(network): def _dhcp_file(bridge, kind): """Return path to a pid, leases or conf file for a bridge.""" - if not os.path.exists(FLAGS.networks_path): os.makedirs(FLAGS.networks_path) return os.path.abspath('%s/nova-%s.%s' % (FLAGS.networks_path, @@ -778,7 +777,6 @@ def _ra_pid_for(bridge): def _ip_bridge_cmd(action, params, device): """Build commands to add/del ips to bridges/devices.""" - cmd = ['sudo', 'ip', 'addr', action] cmd.extend(params) cmd.extend(['dev', device]) diff --git a/nova/network/vmwareapi_net.py b/nova/network/vmwareapi_net.py index bf1070995..373060add 100644 --- a/nova/network/vmwareapi_net.py +++ b/nova/network/vmwareapi_net.py @@ -42,9 +42,9 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): host_username = FLAGS.vmwareapi_host_username host_password = FLAGS.vmwareapi_host_password if not host_ip or host_username is None or host_password is None: - raise Exception(_('Must specify vmwareapi_host_ip,' + raise Exception(_('Must specify vmwareapi_host_ip, ' 'vmwareapi_host_username ' - 'and vmwareapi_host_password to use' + 'and vmwareapi_host_password to use ' 'connection_type=vmwareapi')) session = VMWareAPISession(host_ip, host_username, host_password, FLAGS.vmwareapi_api_retry_count) -- cgit From 6de6da879c37f0a5983f4c72692db84c3dd10b22 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 11 May 2011 14:41:31 -0500 Subject: Redundant line --- nova/tests/test_compute.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 1b0e66bef..136d7a915 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -335,7 +335,6 @@ class ComputeTestCase(test.TestCase): def fake(*args, **kwargs): pass - self.stubs.Set(self.compute.driver, 'finish_resize', fake) self.stubs.Set(self.compute.driver, 'finish_resize', fake) context = self.context.elevated() instance_id = self._create_instance() -- cgit From d2b8350a026e0f00eae7cadbacaa15d4b44331af Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 11 May 2011 21:04:40 +0000 Subject: Implement IPv6 address generation that includes account identifier --- nova/api/ec2/cloud.py | 3 ++- nova/db/sqlalchemy/api.py | 3 ++- nova/ipv6/account_identifier.py | 45 +++++++++++++++++++++++++++++++++++++++++ nova/ipv6/api.py | 7 ++++--- nova/virt/libvirt_conn.py | 3 ++- nova/virt/xenapi/vmops.py | 3 ++- 6 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 nova/ipv6/account_identifier.py diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 1b51b5463..63baf8036 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -721,7 +721,8 @@ class CloudController(object): if instance['fixed_ip']['network'] and 'use_v6' in kwargs: i['dnsNameV6'] = ipv6.to_global( instance['fixed_ip']['network']['cidr_v6'], - instance['mac_address']) + instance['mac_address'], + instance['project_id']) i['privateDnsName'] = fixed_addr i['privateIpAddress'] = fixed_addr diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 6c76248ce..11d07c9e9 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -975,7 +975,8 @@ def instance_get_fixed_address_v6(context, instance_id): network_ref = network_get_by_instance(context, instance_id) prefix = network_ref.cidr_v6 mac = instance_ref.mac_address - return ipv6.to_global(prefix, mac) + project_id = instance_ref.project_id + return ipv6.to_global(prefix, mac, project_id) @require_context diff --git a/nova/ipv6/account_identifier.py b/nova/ipv6/account_identifier.py new file mode 100644 index 000000000..258678f0a --- /dev/null +++ b/nova/ipv6/account_identifier.py @@ -0,0 +1,45 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 Justin Santa Barbara +# 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. + +"""IPv6 address generation with account identifier embedded""" + +import hashlib +import netaddr + + +def to_global(prefix, mac, project_id): + project_hash = netaddr.IPAddress(int(hashlib.sha1(project_id).\ + hexdigest()[:8], 16) << 32) + static_num = netaddr.IPAddress(0xff << 24) + + try: + mac_suffix = netaddr.EUI(mac).words[3:] + int_addr = int(''.join(['%02x' % i for i in mac_suffix]), 16) + mac_addr = netaddr.IPAddress(int_addr) + maskIP = netaddr.IPNetwork(prefix).ip + return (project_hash ^ static_num ^ mac_addr | maskIP).format() + except TypeError: + raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac) + + +def to_mac(ipv6_address): + address = netaddr.IPAddress(ipv6_address) + mask1 = netaddr.IPAddress('::ff:ffff') + mac = netaddr.EUI(int(address & mask1)).words + return ':'.join(['02', '16', '3e'] + ['%02x' % i for i in mac[3:6]]) diff --git a/nova/ipv6/api.py b/nova/ipv6/api.py index 95b20c945..b7fa6bd8f 100644 --- a/nova/ipv6/api.py +++ b/nova/ipv6/api.py @@ -24,11 +24,12 @@ flags.DEFINE_string('ipv6_backend', 'Backend to use for IPv6 generation') IMPL = utils.LazyPluggable(FLAGS['ipv6_backend'], - rfc2462='nova.ipv6.rfc2462') + rfc2462='nova.ipv6.rfc2462', + account_identifier='nova.ipv6.account_identifier') -def to_global(prefix, mac): - return IMPL.to_global(prefix, mac) +def to_global(prefix, mac, project_id): + return IMPL.to_global(prefix, mac, project_id) def to_mac(ipv6_address): return IMPL.to_mac(ipv6_address) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index cde864b0d..80e1a1f85 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -185,8 +185,9 @@ def _get_network_info(instance): def ip6_dict(): prefix = network['cidr_v6'] mac = instance['mac_address'] + project_id = instance['project_id'] return { - 'ip': ipv6.to_global(prefix, mac), + 'ip': ipv6.to_global(prefix, mac, project_id), 'netmask': network['netmask_v6'], 'enabled': '1'} diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 0b05e702a..cc2b54331 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -810,7 +810,8 @@ class VMOps(object): def ip6_dict(): return { "ip": ipv6.to_global(network['cidr_v6'], - instance['mac_address']), + instance['mac_address'], + instance['project_id']), "netmask": network['netmask_v6'], "enabled": "1"} -- cgit From 81b1cfc2db7f898263c0c40665769424ca5530ef Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Thu, 12 May 2011 02:27:47 -0400 Subject: rename quota column to 'hard_limit' to make it simpler to avoid collisions with sql keyword 'limit' --- nova/db/sqlalchemy/api.py | 6 +++--- .../migrate_repo/versions/016_make_quotas_key_and_value.py | 14 +++++++------- nova/db/sqlalchemy/models.py | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 56b40f95b..ea0bbb06e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1518,7 +1518,7 @@ def quota_get_all_by_project(context, project_id): filter_by(deleted=False).\ all() for row in rows: - result[row.resource] = row.limit + result[row.resource] = row.hard_limit return result @@ -1527,7 +1527,7 @@ def quota_create(context, project_id, resource, limit): quota_ref = models.Quota() quota_ref.project_id = project_id quota_ref.resource = resource - quota_ref.limit = limit + quota_ref.hard_limit = limit quota_ref.save() return quota_ref @@ -1537,7 +1537,7 @@ def quota_update(context, project_id, resource, limit): session = get_session() with session.begin(): quota_ref = quota_get(context, project_id, resource, session=session) - quota_ref.limit = limit + quota_ref.hard_limit = limit quota_ref.save(session=session) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py index 03d346af4..a2d8192ca 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py @@ -71,7 +71,7 @@ def new_style_quotas_table(name): assert_unicode=None, unicode_error=None, _warn_on_bytestring=False), nullable=False), - Column('limit', Integer(), nullable=True), + Column('hard_limit', Integer(), nullable=True), ) @@ -111,8 +111,8 @@ def convert_forward(migrate_engine, old_quotas, new_quotas): quotas = list(migrate_engine.execute(old_quotas.select())) for quota in quotas: for resource in resources: - limit = getattr(quota, resource) - if limit is None: + hard_limit = getattr(quota, resource) + if hard_limit is None: continue insert = new_quotas.insert().values( created_at=quota.created_at, @@ -121,7 +121,7 @@ def convert_forward(migrate_engine, old_quotas, new_quotas): deleted=quota.deleted, project_id=quota.project_id, resource=resource, - limit=limit) + hard_limit=hard_limit) migrate_engine.execute(insert) @@ -153,21 +153,21 @@ def convert_backward(migrate_engine, old_quotas, new_quotas): quotas = {} for quota in migrate_engine.execute(new_quotas.select()): if (quota.resource not in resources - or quota.limit is None or quota.deleted): + or quota.hard_limit is None or quota.deleted): continue if not quota.project_id in quotas: quotas[quota.project_id] = { 'project_id': quota.project_id, 'created_at': quota.created_at, 'updated_at': quota.updated_at, - quota.resource: quota.limit + quota.resource: quota.hard_limit } else: quotas[quota.project_id]['created_at'] = earliest( quota.created_at, quotas[quota.project_id]['created_at']) quotas[quota.project_id]['updated_at'] = latest( quota.updated_at, quotas[quota.project_id]['updated_at']) - quotas[quota.project_id][quota.resource] = quota.limit + quotas[quota.project_id][quota.resource] = quota.hard_limit for quota in quotas.itervalues(): insert = old_quotas.insert().values(**quota) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index e477040d3..0b46d5a05 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -317,7 +317,7 @@ class Quota(BASE, NovaBase): If there is no row for a given project id and resource, then the default for the deployment is used. If the row is present - but the limit is Null, then the resource is unlimited. + but the hard limit is Null, then the resource is unlimited. """ __tablename__ = 'quotas' @@ -326,7 +326,7 @@ class Quota(BASE, NovaBase): project_id = Column(String(255), index=True) resource = Column(String(255)) - limit = Column(Integer, nullable=True) + hard_limit = Column(Integer, nullable=True) class ExportDevice(BASE, NovaBase): -- cgit From 1330241020d68f941a56ce1cf93e6523884cccc1 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 12 May 2011 09:19:01 -0400 Subject: fixed pep8 spacing issue --- tools/install_venv.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/install_venv.py b/tools/install_venv.py index 03d93ac7d..8149a3afa 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -1,3 +1,4 @@ + # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the @@ -38,7 +39,7 @@ def die(message, *args): sys.exit(1) def check_python_version(): - if sys.version_info < (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): -- cgit From ad3f578a37001957361014c7400dbe2e8ddd0baf Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 12 May 2011 17:44:07 +0400 Subject: Added network_info into refresh_security_group_rules --- nova/tests/test_virt.py | 12 +++++++++++- nova/virt/libvirt_conn.py | 20 ++++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 1311ba361..874c4693f 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -849,7 +849,7 @@ class IptablesFirewallTestCase(test.TestCase): self.assertEquals(len(rulesv4), 2) self.assertEquals(len(rulesv6), 0) - def multinic_iptables_test(self): + def test_multinic_iptables(self): ipv4_rules_per_network = 2 ipv6_rules_per_network = 3 networks_count = 5 @@ -869,6 +869,16 @@ class IptablesFirewallTestCase(test.TestCase): self.assertEquals(ipv6_network_rules, ipv6_rules_per_network * networks_count) + def test_do_refresh_security_group_rules(self): + instance_ref = self._create_instance_ref() + self.mox.StubOutWithMock(self.fw, + 'add_filters_for_instance', + use_mock_anything=True) + self.fw.add_filters_for_instance(instance_ref, mox.IgnoreArg()) + self.fw.instances[instance_ref['id']] = instance_ref + self.mox.ReplayAll() + self.fw.do_refresh_security_group_rules("fake") + class NWFilterTestCase(test.TestCase): def setUp(self): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 555e44ce2..1e0a25a17 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1612,7 +1612,9 @@ class FirewallDriver(object): """ raise NotImplementedError() - def refresh_security_group_rules(self, security_group_id): + def refresh_security_group_rules(self, + security_group_id, + network_info=None): """Refresh security group rules from data store Gets called when a rule has been added to or removed from @@ -1911,7 +1913,9 @@ class NWFilterFirewall(FirewallDriver): self._define_filter(self._filter_container(filter_name, filter_children)) - def refresh_security_group_rules(self, security_group_id): + def refresh_security_group_rules(self, + security_group_id, + network_info=None): return self._define_filter( self.security_group_to_nwfilter_xml(security_group_id)) @@ -2169,15 +2173,19 @@ class IptablesFirewallDriver(FirewallDriver): def refresh_security_group_members(self, security_group): pass - def refresh_security_group_rules(self, security_group): - self.do_refresh_security_group_rules(security_group) + def refresh_security_group_rules(self, security_group, network_info=None): + self.do_refresh_security_group_rules(security_group, network_info) self.iptables.apply() @utils.synchronized('iptables', external=True) - def do_refresh_security_group_rules(self, security_group): + def do_refresh_security_group_rules(self, + security_group, + network_info=None): for instance in self.instances.values(): self.remove_filters_for_instance(instance) - self.add_filters_for_instance(instance) + if not network_info: + network_info = _get_network_info(instance) + self.add_filters_for_instance(instance, network_info) def _security_group_chain_name(self, security_group_id): return 'nova-sg-%s' % (security_group_id,) -- cgit From 33466d3ca067b8fec75380a27d5a2a196515bb50 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Thu, 12 May 2011 18:40:56 +0000 Subject: Accept and ignore project_id --- nova/ipv6/rfc2462.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/ipv6/rfc2462.py b/nova/ipv6/rfc2462.py index 3af4556e7..0074efe98 100644 --- a/nova/ipv6/rfc2462.py +++ b/nova/ipv6/rfc2462.py @@ -22,7 +22,7 @@ import netaddr -def to_global(prefix, mac): +def to_global(prefix, mac, project_id): try: mac64 = netaddr.EUI(mac).eui64().words int_addr = int(''.join(['%02x' % i for i in mac64]), 16) -- cgit From 6d140b61cd146613b282c2f1f046c529d3112553 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Thu, 12 May 2011 18:41:22 +0000 Subject: Add test suite for IPv6 address generation --- nova/ipv6/api.py | 11 ++++++--- nova/tests/test_ipv6.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 nova/tests/test_ipv6.py diff --git a/nova/ipv6/api.py b/nova/ipv6/api.py index b7fa6bd8f..cdda2c253 100644 --- a/nova/ipv6/api.py +++ b/nova/ipv6/api.py @@ -23,13 +23,18 @@ flags.DEFINE_string('ipv6_backend', 'rfc2462', 'Backend to use for IPv6 generation') -IMPL = utils.LazyPluggable(FLAGS['ipv6_backend'], - rfc2462='nova.ipv6.rfc2462', - account_identifier='nova.ipv6.account_identifier') +def reset_backend(): + global IMPL + IMPL = utils.LazyPluggable(FLAGS['ipv6_backend'], + rfc2462='nova.ipv6.rfc2462', + account_identifier= + 'nova.ipv6.account_identifier') def to_global(prefix, mac, project_id): return IMPL.to_global(prefix, mac, project_id) def to_mac(ipv6_address): return IMPL.to_mac(ipv6_address) + +reset_backend() diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py new file mode 100644 index 000000000..01d28df73 --- /dev/null +++ b/nova/tests/test_ipv6.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 OpenStack LLC +# +# 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. + +"""Test suite for IPv6.""" + +from nova import ipv6 +from nova import flags +from nova import log as logging +from nova import test + +LOG = logging.getLogger('nova.tests.test_ipv6') + +FLAGS = flags.FLAGS + +import sys + +class IPv6RFC2462TestCase(test.TestCase): + """Unit tests for IPv6 rfc2462 backend operations.""" + def setUp(self): + super(IPv6RFC2462TestCase, self).setUp() + self.flags(ipv6_backend='rfc2462') + ipv6.reset_backend() + + def test_to_global(self): + addr = ipv6.to_global('2001:db8::', '02:16:3e:33:44:55', 'test') + self.assertEquals(addr, '2001:db8::16:3eff:fe33:4455') + + def test_to_mac(self): + mac = ipv6.to_mac('2001:db8::216:3eff:fe33:4455') + self.assertEquals(mac, '00:16:3e:33:44:55') + + +class IPv6AccountIdentiferTestCase(test.TestCase): + """Unit tests for IPv6 account_identifier backend operations.""" + def setUp(self): + super(IPv6AccountIdentiferTestCase, self).setUp() + self.flags(ipv6_backend='account_identifier') + ipv6.reset_backend() + + def test_to_global(self): + addr = ipv6.to_global('2001:db8::', '02:16:3e:33:44:55', 'test') + self.assertEquals(addr, '2001:db8::a94a:8fe5:ff33:4455') + + def test_to_mac(self): + mac = ipv6.to_mac('2001:db8::a94a:8fe5:ff33:4455') + self.assertEquals(mac, '02:16:3e:33:44:55') -- cgit From 0cf0b89f57392688c0a443b29408813ccb028c38 Mon Sep 17 00:00:00 2001 From: John Tran Date: Thu, 12 May 2011 12:51:03 -0700 Subject: incorporated ImageNotFound instead of NotFound --- nova/api/ec2/cloud.py | 6 +----- nova/tests/test_cloud.py | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 2fcf647fb..be5dd38a0 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -963,11 +963,7 @@ class CloudController(object): def deregister_image(self, context, image_id, **kwargs): LOG.audit(_("De-registering image %s"), image_id, context=context) - try: - image = self._get_image(context, image_id) - except exception.NotFound: - raise exception.NotFound(_('Image %s not found') % - image_id) + image = self._get_image(context, image_id) internal_id = image['id'] self.image_service.delete(context, internal_id) return {'imageId': image_id} diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 82dd14cb2..af1dbfd4d 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -282,7 +282,7 @@ class CloudTestCase(test.TestCase): def test_deregister_image(self): deregister_image = self.cloud.deregister_image - def fake_delete(meh, context, id): + def fake_delete(self, context, id): return None self.stubs.Set(local.LocalImageService, 'delete', fake_delete) @@ -292,11 +292,11 @@ class CloudTestCase(test.TestCase): # invalid image self.stubs.UnsetAll() - def fake_detail_empty(meh, context): + def fake_detail_empty(self, context): return [] self.stubs.Set(local.LocalImageService, 'detail', fake_detail_empty) - self.assertRaises(exception.NotFound, deregister_image, + self.assertRaises(exception.ImageNotFound, deregister_image, self.context, 'ami-bad001') def test_console_output(self): -- cgit From e0dab6d678867e11e107a9418c7baeb5ac055de7 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 12 May 2011 20:01:32 +0000 Subject: Add a test for parallel builds. verified this test fails before this fix and succeeds after this fix --- nova/tests/test_xenapi.py | 23 +++++++++++++++++++++++ nova/tests/xenapi/stubs.py | 9 +++++++++ 2 files changed, 32 insertions(+) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 375480a2e..a4e679817 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -16,6 +16,7 @@ """Test suite for XenAPI.""" +import eventlet import functools import os import re @@ -197,6 +198,28 @@ class XenAPIVMTestCase(test.TestCase): self.context = context.RequestContext('fake', 'fake', False) self.conn = xenapi_conn.get_connection(False) + def test_parallel_builds(self): + stubs.stubout_loopingcall_delay(self.stubs) + + def _do_build(id, proj, user, *args): + values = { + 'id': id, + 'project_id': proj, + 'user_id': user, + 'image_id': 1, + 'kernel_id': 2, + 'ramdisk_id': 3, + 'instance_type_id': '3', # m1.large + 'mac_address': 'aa:bb:cc:dd:ee:ff', + 'os_type': 'linux'} + instance = db.instance_create(self.context, values) + self.conn.spawn(instance) + + gt1 = eventlet.spawn(_do_build, 1, self.project.id, self.user.id) + gt2 = eventlet.spawn(_do_build, 2, self.project.id, self.user.id) + gt1.wait() + gt2.wait() + def test_list_instances_0(self): instances = self.conn.list_instances() self.assertEquals(instances, []) diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 6db061444..f3d3d0ceb 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -16,6 +16,7 @@ """Stubouts, mocks and fixtures for the test suite""" +import eventlet from nova.virt import xenapi_conn from nova.virt.xenapi import fake from nova.virt.xenapi import volume_utils @@ -115,6 +116,14 @@ def stubout_loopingcall_start(stubs): self.f(*self.args, **self.kw) stubs.Set(utils.LoopingCall, 'start', fake_start) +def stubout_loopingcall_delay(stubs): + def fake_start(self, interval, now=True): + self._running = True + eventlet.sleep(1) + self.f(*self.args, **self.kw) + # This would fail before parallel xenapi calls were fixed + assert self._running == False + stubs.Set(utils.LoopingCall, 'start', fake_start) class FakeSessionForVMTests(fake.SessionBase): """ Stubs out a XenAPISession for VM tests """ -- cgit From 7dc76508d38370f3cf68029fae2ffb7749a580b2 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 12 May 2011 20:12:22 +0000 Subject: if a LoopingCall has canceled the loop, break out early instead of sleeping any more than needed --- nova/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/utils.py b/nova/utils.py index 80bf1197f..44ee1b623 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -459,6 +459,8 @@ class LoopingCall(object): try: while self._running: self.f(*self.args, **self.kw) + if not self._running: + break greenthread.sleep(interval) except LoopingCallDone, e: self.stop() -- cgit From dbff37b9ae0893ce209ff0b8c8893987226bd081 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Thu, 12 May 2011 17:24:38 -0400 Subject: handle instance_type_ids that are NULL during upgrade to integers --- .../migrate_repo/versions/016_make_instance_type_id_an_integer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py index 5d95c1024..e2d03ebf2 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py @@ -12,7 +12,7 @@ def upgrade(migrate_engine): for instance in migrate_engine.execute(instances.select()): try: types[instance.id] = int(instance.instance_type_id) - except ValueError: + except (ValueError, TypeError): types[instance.id] = None integer_column = Column('instance_type_id_int', Integer(), nullable=True) -- cgit From 1aad930383fa425b88e59929aa1698e31978eb62 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Thu, 12 May 2011 22:19:52 +0000 Subject: Make sure imports are in alphabetical order --- nova/api/ec2/cloud.py | 2 +- nova/db/sqlalchemy/api.py | 2 +- nova/tests/network/base.py | 2 +- nova/tests/test_ipv6.py | 2 +- nova/virt/libvirt_conn.py | 2 +- nova/virt/xenapi/vmops.py | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 63baf8036..1fa07d042 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -35,11 +35,11 @@ from nova import crypto from nova import db from nova import exception from nova import flags +from nova import ipv6 from nova import log as logging from nova import network from nova import utils from nova import volume -from nova import ipv6 from nova.api.ec2 import ec2utils from nova.compute import instance_types from nova.image import s3 diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 11d07c9e9..2949eec49 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -25,8 +25,8 @@ import warnings from nova import db from nova import exception from nova import flags -from nova import utils from nova import ipv6 +from nova import utils from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from sqlalchemy import or_ diff --git a/nova/tests/network/base.py b/nova/tests/network/base.py index 5de1255cd..5236b1dfe 100644 --- a/nova/tests/network/base.py +++ b/nova/tests/network/base.py @@ -25,10 +25,10 @@ from nova import context from nova import db from nova import exception from nova import flags +from nova import ipv6 from nova import log as logging from nova import test from nova import utils -from nova import ipv6 from nova.auth import manager FLAGS = flags.FLAGS diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py index 01d28df73..45b64cba8 100644 --- a/nova/tests/test_ipv6.py +++ b/nova/tests/test_ipv6.py @@ -16,8 +16,8 @@ """Test suite for IPv6.""" -from nova import ipv6 from nova import flags +from nova import ipv6 from nova import log as logging from nova import test diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 80e1a1f85..6ee23d1df 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -57,10 +57,10 @@ from nova import context from nova import db from nova import exception from nova import flags +from nova import ipv6 from nova import log as logging from nova import utils from nova import vnc -from nova import ipv6 from nova.auth import manager from nova.compute import instance_types from nova.compute import power_state diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index cc2b54331..13d7d215b 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -28,13 +28,13 @@ import subprocess import tempfile import uuid -from nova import db from nova import context -from nova import log as logging +from nova import db from nova import exception -from nova import utils from nova import flags from nova import ipv6 +from nova import log as logging +from nova import utils from nova.auth.manager import AuthManager from nova.compute import power_state -- cgit From a84e484d1401d7c82373203b6964c54ab7166dea Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 12 May 2011 20:05:41 -0700 Subject: added flag lost in migration --- nova/compute/manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 556b3b3b9..a2dffb16e 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -77,7 +77,8 @@ flags.DEFINE_integer("rescue_timeout", 0, " Set to 0 to disable.") flags.DEFINE_bool('auto_assign_floating_ip', False, 'Autoassigning floating ip to VM') - +flags.DEFINE_integer('host_state_interval', 120, + 'Interval in seconds for querying the host status') LOG = logging.getLogger('nova.compute.manager') -- cgit From 31820248e8d886d37add963f0709f3658fd6087d Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 13 May 2011 10:22:04 -0400 Subject: Update the compute manager so that it breaks out of a loop if set_admin_password is not implemented by the driver. --- nova/compute/manager.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 556b3b3b9..149147460 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -426,6 +426,12 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.audit(_("Instance %s: Root password set"), instance_ref["name"]) break + except NotImplementedError: + # NOTE(dprince): if the driver doesn't implement + # set_admin_password we break to avoid a loop + LOG.warn(_('set_admin_password is not implemented ' + 'by this driver.')) + break except Exception, e: # Catch all here because this could be anything. LOG.exception(e) -- cgit From a8941345c97cccb9171e3d1e172f607dc8d17e6a Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 13 May 2011 09:47:08 -0500 Subject: Make set_admin_password non-blocking to API. --- nova/api/openstack/servers.py | 6 ++++-- nova/compute/api.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 547310613..aaff57b4a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -14,6 +14,7 @@ # under the License. import base64 +import eventlet import traceback from webob import exc @@ -175,8 +176,9 @@ class Controller(common.OpenstackController): builder = self._get_view_builder(req) server = builder.build(inst, is_detail=True) server['server']['adminPass'] = password - self.compute_api.set_admin_password(context, server['server']['id'], - password) + # We don't want this to block + eventlet.spawn(self.compute_api.set_admin_password( + context, server['server']['id'], password)) return server def _deserialize_create(self, request): diff --git a/nova/compute/api.py b/nova/compute/api.py index 63884be97..1c28b6a89 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -484,7 +484,7 @@ class API(base.Base): def _find_host(self, context, instance_id): """Find the host associated with an instance.""" - for attempts in xrange(10): + for attempts in xrange(30): instance = self.get(context, instance_id) host = instance["host"] if host: -- cgit From b4357fef25a0f6a402979d15c1ecbf4abb8643a8 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 13 May 2011 10:04:37 -0500 Subject: Make host timeout configurable. --- nova/compute/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 1c28b6a89..29347bb4c 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -42,6 +42,8 @@ LOG = logging.getLogger('nova.compute.api') FLAGS = flags.FLAGS flags.DECLARE('vncproxy_topic', 'nova.vnc') +flags.DEFINE_integer('find_host_timeout', 30, + 'Timeout after NN seconds when looking for a host.') def generate_default_hostname(instance_id): @@ -484,7 +486,7 @@ class API(base.Base): def _find_host(self, context, instance_id): """Find the host associated with an instance.""" - for attempts in xrange(30): + for attempts in xrange(FLAGS.find_host_timeout): instance = self.get(context, instance_id) host = instance["host"] if host: -- cgit From 60dcc66db2a08bd4acfe2f3fc3e66cc382e7d008 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 13 May 2011 10:26:50 -0500 Subject: Review feedback. --- nova/api/openstack/servers.py | 6 ++---- nova/compute/api.py | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index aaff57b4a..547310613 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -14,7 +14,6 @@ # under the License. import base64 -import eventlet import traceback from webob import exc @@ -176,9 +175,8 @@ class Controller(common.OpenstackController): builder = self._get_view_builder(req) server = builder.build(inst, is_detail=True) server['server']['adminPass'] = password - # We don't want this to block - eventlet.spawn(self.compute_api.set_admin_password( - context, server['server']['id'], password)) + self.compute_api.set_admin_password(context, server['server']['id'], + password) return server def _deserialize_create(self, request): diff --git a/nova/compute/api.py b/nova/compute/api.py index 29347bb4c..a12f8d515 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -19,6 +19,7 @@ """Handles all requests relating to instances (guest vms).""" import datetime +import eventlet import re import time @@ -495,6 +496,15 @@ class API(base.Base): raise exception.Error(_("Unable to find host for Instance %s") % instance_id) + def _set_admin_password(self, context, instance_id, password): + """Set the root/admin password for the given instance.""" + host = self._find_host(context, instance_id) + + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "set_admin_password", + "args": {"instance_id": instance_id, "new_pass": password}}) + def snapshot(self, context, instance_id, name): """Snapshot the given instance. @@ -648,12 +658,8 @@ class API(base.Base): def set_admin_password(self, context, instance_id, password=None): """Set the root/admin password for the given instance.""" - host = self._find_host(context, instance_id) - - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "set_admin_password", - "args": {"instance_id": instance_id, "new_pass": password}}) + eventlet.spawn_n(self._set_admin_password(context, instance_id, + password)) def inject_file(self, context, instance_id): """Write a file to the given instance.""" -- cgit From 3f247a628c954d5d4d97def6e6a2f889ab7ec7e3 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 13 May 2011 16:47:18 +0000 Subject: pep8 fixes --- nova/tests/xenapi/stubs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index f3d3d0ceb..4833ccb07 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -116,6 +116,7 @@ def stubout_loopingcall_start(stubs): self.f(*self.args, **self.kw) stubs.Set(utils.LoopingCall, 'start', fake_start) + def stubout_loopingcall_delay(stubs): def fake_start(self, interval, now=True): self._running = True @@ -125,6 +126,7 @@ def stubout_loopingcall_delay(stubs): assert self._running == False stubs.Set(utils.LoopingCall, 'start', fake_start) + class FakeSessionForVMTests(fake.SessionBase): """ Stubs out a XenAPISession for VM tests """ def __init__(self, uri): -- cgit From b098428155b36551cfd84d4b2faf87a104d58f27 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Sat, 14 May 2011 22:47:12 -0500 Subject: renamed test cases to use a consistent naming convention as used in nova/tests/api/openstack/test_images.py --- nova/tests/api/openstack/test_servers.py | 50 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 89edece42..308271167 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -183,7 +183,7 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict['server']['id'], 1) self.assertEqual(res_dict['server']['name'], 'server1') - def test_get_server_by_id_v11(self): + def test_get_server_by_id_v1_1(self): req = webob.Request.blank('/v1.1/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -246,7 +246,7 @@ class ServersTest(test.TestCase): self.assertEqual(len(addresses["private"]), 1) self.assertEqual(addresses["private"][0], private) - def test_get_server_addresses_V10(self): + def test_get_server_addresses_v1_0(self): private = '192.168.0.3' public = ['1.2.3.4'] new_return_server = return_server_with_addresses(private, public) @@ -257,7 +257,7 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict, { 'addresses': {'public': public, 'private': [private]}}) - def test_get_server_addresses_xml_V10(self): + def test_get_server_addresses_xml_v1_0(self): private_expected = "192.168.0.3" public_expected = ["1.2.3.4"] new_return_server = return_server_with_addresses(private_expected, @@ -276,7 +276,7 @@ class ServersTest(test.TestCase): (ip,) = private.getElementsByTagName('ip') self.assertEquals(ip.getAttribute('addr'), private_expected) - def test_get_server_addresses_public_V10(self): + def test_get_server_addresses_public_v1_0(self): private = "192.168.0.3" public = ["1.2.3.4"] new_return_server = return_server_with_addresses(private, public) @@ -286,7 +286,7 @@ class ServersTest(test.TestCase): res_dict = json.loads(res.body) self.assertEqual(res_dict, {'public': public}) - def test_get_server_addresses_private_V10(self): + def test_get_server_addresses_private_v1_0(self): private = "192.168.0.3" public = ["1.2.3.4"] new_return_server = return_server_with_addresses(private, public) @@ -296,7 +296,7 @@ class ServersTest(test.TestCase): res_dict = json.loads(res.body) self.assertEqual(res_dict, {'private': [private]}) - def test_get_server_addresses_public_xml_V10(self): + def test_get_server_addresses_public_xml_v1_0(self): private = "192.168.0.3" public = ["1.2.3.4"] new_return_server = return_server_with_addresses(private, public) @@ -310,7 +310,7 @@ class ServersTest(test.TestCase): (ip,) = public_node.getElementsByTagName('ip') self.assertEquals(ip.getAttribute('addr'), public[0]) - def test_get_server_addresses_private_xml_V10(self): + def test_get_server_addresses_private_xml_v1_0(self): private = "192.168.0.3" public = ["1.2.3.4"] new_return_server = return_server_with_addresses(private, public) @@ -324,7 +324,7 @@ class ServersTest(test.TestCase): (ip,) = private_node.getElementsByTagName('ip') self.assertEquals(ip.getAttribute('addr'), private) - def test_get_server_by_id_with_addresses_v11(self): + def test_get_server_by_id_with_addresses_v1_1(self): private = "192.168.0.3" public = ["1.2.3.4"] new_return_server = return_server_with_addresses(private, public) @@ -354,7 +354,7 @@ class ServersTest(test.TestCase): self.assertEqual(s.get('imageId', None), None) i += 1 - def test_get_server_list_v11(self): + def test_get_server_list_v1_1(self): req = webob.Request.blank('/v1.1/servers') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -576,7 +576,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_create_instance_v11(self): + def test_create_instance_v1_1(self): self._setup_for_create_instance() imageRef = 'http://localhost/v1.1/images/2' @@ -609,7 +609,7 @@ class ServersTest(test.TestCase): self.assertEqual(imageRef, server['imageRef']) self.assertEqual(res.status_int, 200) - def test_create_instance_v11_bad_href(self): + def test_create_instance_v1_1_bad_href(self): self._setup_for_create_instance() imageRef = 'http://localhost/v1.1/images/asdf' @@ -625,7 +625,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_create_instance_v11_local_href(self): + def test_create_instance_v1_1_local_href(self): self._setup_for_create_instance() imageRef = 'http://localhost/v1.1/images/2' @@ -652,7 +652,7 @@ class ServersTest(test.TestCase): self.assertEqual(imageRef, server['imageRef']) self.assertEqual(res.status_int, 200) - def test_create_instance_with_admin_pass_v10(self): + def test_create_instance_with_admin_pass_v1_0(self): self._setup_for_create_instance() body = { @@ -673,7 +673,7 @@ class ServersTest(test.TestCase): self.assertNotEqual(res['server']['adminPass'], body['server']['adminPass']) - def test_create_instance_with_admin_pass_v11(self): + def test_create_instance_with_admin_pass_v1_1(self): self._setup_for_create_instance() imageRef = 'http://localhost/v1.1/images/2' @@ -695,7 +695,7 @@ class ServersTest(test.TestCase): server = json.loads(res.body)['server'] self.assertEqual(server['adminPass'], body['server']['adminPass']) - def test_create_instance_with_empty_admin_pass_v11(self): + def test_create_instance_with_empty_admin_pass_v1_1(self): self._setup_for_create_instance() imageRef = 'http://localhost/v1.1/images/2' @@ -758,7 +758,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_update_server_v10(self): + def test_update_server_v1_0(self): inst_dict = dict(name='server_test', adminPass='bacon') self.body = json.dumps(dict(server=inst_dict)) @@ -781,7 +781,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 204) - def test_update_server_adminPass_ignored_v11(self): + def test_update_server_adminPass_ignored_v1_1(self): inst_dict = dict(name='server_test', adminPass='bacon') self.body = json.dumps(dict(server=inst_dict)) @@ -822,7 +822,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 501) - def test_server_backup_schedule_deprecated_v11(self): + def test_server_backup_schedule_deprecated_v1_1(self): req = webob.Request.blank('/v1.1/servers/1/backup_schedule') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -1113,7 +1113,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_accepted_minimum_v11(self): + def test_server_rebuild_accepted_minimum_v1_1(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -1128,7 +1128,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) - def test_server_rebuild_rejected_when_building_v11(self): + def test_server_rebuild_rejected_when_building_v1_1(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -1147,7 +1147,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 409) - def test_server_rebuild_accepted_with_metadata_v11(self): + def test_server_rebuild_accepted_with_metadata_v1_1(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -1165,7 +1165,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) - def test_server_rebuild_accepted_with_bad_metadata_v11(self): + def test_server_rebuild_accepted_with_bad_metadata_v1_1(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -1181,7 +1181,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_bad_entity_v11(self): + def test_server_rebuild_bad_entity_v1_1(self): body = { "rebuild": { "imageId": 2, @@ -1196,7 +1196,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_bad_personality_v11(self): + def test_server_rebuild_bad_personality_v1_1(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -1215,7 +1215,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_personality_v11(self): + def test_server_rebuild_personality_v1_1(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", -- cgit From 9350dc2cee8d18e7b68921f5135504b68a25f95d Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Sat, 14 May 2011 23:00:56 -0500 Subject: fixed a few C0103 errors in test_servers.py --- nova/tests/api/openstack/test_servers.py | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 308271167..eebc7f754 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -579,13 +579,13 @@ class ServersTest(test.TestCase): def test_create_instance_v1_1(self): self._setup_for_create_instance() - imageRef = 'http://localhost/v1.1/images/2' - flavorRef = 'http://localhost/v1.1/flavors/3' + image_ref = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/v1.1/flavors/3' body = { 'server': { 'name': 'server_test', - 'imageRef': imageRef, - 'flavorRef': flavorRef, + 'imageRef': image_ref, + 'flavorRef': flavor_ref, 'metadata': { 'hello': 'world', 'open': 'stack', @@ -605,17 +605,17 @@ class ServersTest(test.TestCase): self.assertEqual(16, len(server['adminPass'])) self.assertEqual('server_test', server['name']) self.assertEqual(1, server['id']) - self.assertEqual(flavorRef, server['flavorRef']) - self.assertEqual(imageRef, server['imageRef']) + self.assertEqual(flavor_ref, server['flavorRef']) + self.assertEqual(image_ref, server['imageRef']) self.assertEqual(res.status_int, 200) def test_create_instance_v1_1_bad_href(self): self._setup_for_create_instance() - imageRef = 'http://localhost/v1.1/images/asdf' - flavorRef = 'http://localhost/v1.1/flavors/3' + image_ref = 'http://localhost/v1.1/images/asdf' + flavor_ref = 'http://localhost/v1.1/flavors/3' body = dict(server=dict( - name='server_test', imageRef=imageRef, flavorRef=flavorRef, + name='server_test', imageRef=image_ref, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) req = webob.Request.blank('/v1.1/servers') @@ -628,14 +628,14 @@ class ServersTest(test.TestCase): def test_create_instance_v1_1_local_href(self): self._setup_for_create_instance() - imageRef = 'http://localhost/v1.1/images/2' - imageRefLocal = '2' - flavorRef = 'http://localhost/v1.1/flavors/3' + image_ref = 'http://localhost/v1.1/images/2' + image_ref_local = '2' + flavor_ref = 'http://localhost/v1.1/flavors/3' body = { 'server': { 'name': 'server_test', - 'imageRef': imageRefLocal, - 'flavorRef': flavorRef, + 'imageRef': image_ref_local, + 'flavorRef': flavor_ref, }, } @@ -648,8 +648,8 @@ class ServersTest(test.TestCase): server = json.loads(res.body)['server'] self.assertEqual(1, server['id']) - self.assertEqual(flavorRef, server['flavorRef']) - self.assertEqual(imageRef, server['imageRef']) + self.assertEqual(flavor_ref, server['flavorRef']) + self.assertEqual(image_ref, server['imageRef']) self.assertEqual(res.status_int, 200) def test_create_instance_with_admin_pass_v1_0(self): @@ -676,13 +676,13 @@ class ServersTest(test.TestCase): def test_create_instance_with_admin_pass_v1_1(self): self._setup_for_create_instance() - imageRef = 'http://localhost/v1.1/images/2' - flavorRef = 'http://localhost/v1.1/flavors/3' + image_ref = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/v1.1/flavors/3' body = { 'server': { 'name': 'server_test', - 'imageRef': imageRef, - 'flavorRef': flavorRef, + 'imageRef': image_ref, + 'flavorRef': flavor_ref, 'adminPass': 'testpass', }, } @@ -698,13 +698,13 @@ class ServersTest(test.TestCase): def test_create_instance_with_empty_admin_pass_v1_1(self): self._setup_for_create_instance() - imageRef = 'http://localhost/v1.1/images/2' - flavorRef = 'http://localhost/v1.1/flavors/3' + image_ref = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/v1.1/flavors/3' body = { 'server': { 'name': 'server_test', - 'imageRef': imageRef, - 'flavorRef': flavorRef, + 'imageRef': image_ref, + 'flavorRef': flavor_ref, 'adminPass': '', }, } -- cgit From 262dec736fa6ef54a1101a0a17671ff2a19cbd95 Mon Sep 17 00:00:00 2001 From: Nirmal Ranganathan Date: Mon, 16 May 2011 11:12:16 -0500 Subject: Added the imageRef and flavorRef attributes in the xml deserialization --- nova/api/openstack/servers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3cf78e32c..1289c1e0a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -746,8 +746,9 @@ class ServerCreateRequestXMLDeserializer(object): """Marshal the server attribute of a parsed request""" server = {} server_node = self._find_first_child_named(node, 'server') - for attr in ["name", "imageId", "flavorId"]: - server[attr] = server_node.getAttribute(attr) + for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]: + if server_node.getAttribute(attr): + server[attr] = server_node.getAttribute(attr) metadata = self._extract_metadata(server_node) if metadata is not None: server["metadata"] = metadata -- cgit From e7f699706089919274055fc5c57c276f36d7a301 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 16 May 2011 14:02:56 -0400 Subject: Removed obsolete method and test --- nova/api/ec2/cloud.py | 3 --- nova/tests/test_cloud.py | 35 ----------------------------------- 2 files changed, 38 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9aa4e7778..092b80fa2 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -366,9 +366,6 @@ class CloudController(object): g['ipPermissions'] += [r] return g - def _get_instance(instance_id): - raise NotImplementedError() - def _revoke_rule_args_to_dict(self, context, to_port=None, from_port=None, ip_protocol=None, cidr_ip=None, user_id=None, source_security_group_name=None, diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index c45bdd12c..7835ded28 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -338,41 +338,6 @@ class CloudTestCase(test.TestCase): self._create_key('test') self.cloud.delete_key_pair(self.context, 'test') - def test_run_instances(self): - if FLAGS.connection_type == 'fake': - LOG.debug(_("Can't test instances without a real virtual env.")) - return - image_id = FLAGS.default_image - instance_type = FLAGS.default_instance_type - max_count = 1 - kwargs = {'image_id': image_id, - 'instance_type': instance_type, - 'max_count': max_count} - rv = self.cloud.run_instances(self.context, **kwargs) - # TODO: check for proper response - instance_id = rv['reservationSet'][0].keys()[0] - instance = rv['reservationSet'][0][instance_id][0] - LOG.debug(_("Need to watch instance %s until it's running..."), - instance['instance_id']) - while True: - greenthread.sleep(1) - info = self.cloud._get_instance(instance['instance_id']) - LOG.debug(info['state']) - if info['state'] == power_state.RUNNING: - break - self.assert_(rv) - - if FLAGS.connection_type != 'fake': - time.sleep(45) # Should use boto for polling here - for reservations in rv['reservationSet']: - # for res_id in reservations.keys(): - # LOG.debug(reservations[res_id]) - # for instance in reservations[res_id]: - for instance in reservations[reservations.keys()[0]]: - instance_id = instance['instance_id'] - LOG.debug(_("Terminating instance %s"), instance_id) - rv = self.compute.terminate_instance(instance_id) - def test_terminate_instances(self): inst1 = db.instance_create(self.context, {'reservation_id': 'a', 'image_id': 1, -- cgit From 15ef81b2138afa4fd22e0926fcadf3acfb31f2c5 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Mon, 16 May 2011 18:55:46 +0000 Subject: Use new 3-argument API --- nova/tests/network/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/tests/network/base.py b/nova/tests/network/base.py index 5236b1dfe..b06271c99 100644 --- a/nova/tests/network/base.py +++ b/nova/tests/network/base.py @@ -125,7 +125,8 @@ class NetworkTestCase(test.TestCase): self.assertEqual(instance_ref['id'], instance_ref2['id']) self.assertEqual(address_v6, ipv6.to_global(network_ref['cidr_v6'], - instance_ref['mac_address'])) + instance_ref['mac_address'], + 'test')) self._deallocate_address(0, address) db.instance_destroy(context.get_admin_context(), instance_ref['id']) -- cgit From 428dc895a3495a4800e57162cd7db8d79013a414 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Mon, 16 May 2011 19:33:18 +0000 Subject: PEP8 cleanups --- nova/ipv6/api.py | 5 +++-- nova/tests/test_ipv6.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/ipv6/api.py b/nova/ipv6/api.py index cdda2c253..d24cea066 100644 --- a/nova/ipv6/api.py +++ b/nova/ipv6/api.py @@ -28,12 +28,13 @@ def reset_backend(): global IMPL IMPL = utils.LazyPluggable(FLAGS['ipv6_backend'], rfc2462='nova.ipv6.rfc2462', - account_identifier= - 'nova.ipv6.account_identifier') + account_identifier='nova.ipv6.account_identifier') + def to_global(prefix, mac, project_id): return IMPL.to_global(prefix, mac, project_id) + def to_mac(ipv6_address): return IMPL.to_mac(ipv6_address) diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py index 45b64cba8..11dc2ec98 100644 --- a/nova/tests/test_ipv6.py +++ b/nova/tests/test_ipv6.py @@ -27,6 +27,7 @@ FLAGS = flags.FLAGS import sys + class IPv6RFC2462TestCase(test.TestCase): """Unit tests for IPv6 rfc2462 backend operations.""" def setUp(self): -- cgit From 3d1cef9e56d7fac8a1b89861b7443e4ca660e4a8 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Mon, 16 May 2011 20:06:49 +0000 Subject: Reduce indentation to avoid PEP8 failures --- nova/ipv6/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/ipv6/api.py b/nova/ipv6/api.py index d24cea066..da003645a 100644 --- a/nova/ipv6/api.py +++ b/nova/ipv6/api.py @@ -27,8 +27,8 @@ flags.DEFINE_string('ipv6_backend', def reset_backend(): global IMPL IMPL = utils.LazyPluggable(FLAGS['ipv6_backend'], - rfc2462='nova.ipv6.rfc2462', - account_identifier='nova.ipv6.account_identifier') + rfc2462='nova.ipv6.rfc2462', + account_identifier='nova.ipv6.account_identifier') def to_global(prefix, mac, project_id): -- cgit From ea847e600249f1e3b65e04cfaa67014508c26e95 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 16 May 2011 15:16:34 -0500 Subject: Merge prop changes --- nova/flags.py | 2 +- nova/notifier/api.py | 20 ++++++++++++-------- nova/notifier/log_notifier.py | 19 +++++++++---------- nova/notifier/no_op_notifier.py | 9 +++------ nova/notifier/rabbit_notifier.py | 19 ++++++++----------- nova/tests/test_notifier.py | 22 +++++++++++----------- 6 files changed, 44 insertions(+), 47 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index a1f7f71c8..32cb6efa8 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -370,7 +370,7 @@ DEFINE_string('node_availability_zone', 'nova', 'availability zone of this node') DEFINE_string('notification_driver', - 'nova.notifier.no_op_notifier.NoopNotifier', + 'nova.notifier.no_op_notifier', 'Default driver for sending notifications') DEFINE_list('memcached_servers', None, 'Memcached servers or None for in process cache.') diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 4fcfa84ff..5b9b8ea29 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -37,21 +37,25 @@ class BadPriorityException(Exception): pass -def notify(event_name, publisher_id, event_type, priority, message): +def notify(publisher_id, event_type, priority, message): """ Sends a notification using the specified driver - Message format is as follows: + Notify parameters: - message_id - a UUID representing the id for this notification publisher_id - the source worker_type.host of the message - timestamp - the GMT timestamp the notification was sent at event_type - the literal type of event (ex. Instance Creation) priority - patterned after the enumeration of Python logging levels in the set (DEBUG, WARN, INFO, ERROR, CRITICAL) message - A python dictionary of attributes - The message payload will be constructed as a dictionary of the above + Outgoing message format includes the above parameters, and appends the + following: + + message_id - a UUID representing the id for this notification + timestamp - the GMT timestamp the notification was sent at + + The composite message will be constructed as a dictionary of the above attributes, which will then be sent via the transport mechanism defined by the driver. @@ -62,17 +66,17 @@ def notify(event_name, publisher_id, event_type, priority, message): 'timestamp': datetime.datetime.utcnow(), 'priority': 'WARN', 'event_type': 'compute.create_instance', - 'message': {'instance_id': 12, ... }} + 'payload': {'instance_id': 12, ... }} """ if priority not in log_levels: raise BadPriorityException( _('%s not in valid priorities' % priority)) - driver = utils.import_class(FLAGS.notification_driver)() + driver = utils.import_object(FLAGS.notification_driver) msg = dict(message_id=str(uuid.uuid4()), publisher_id=publisher_id, event_type=event_type, priority=priority, - message=message, + payload=message, timestamp=str(datetime.datetime.utcnow())) driver.notify(msg) diff --git a/nova/notifier/log_notifier.py b/nova/notifier/log_notifier.py index f072a6125..a3df31721 100644 --- a/nova/notifier/log_notifier.py +++ b/nova/notifier/log_notifier.py @@ -21,14 +21,13 @@ from nova import log as logging FLAGS = flags.FLAGS -class LogNotifier(object): - """Log notifications using nova's default logging system""" +def notify(message): + """Notifies the recipient of the desired event given the model. + Log notifications using nova's default logging system""" - def notify(self, message): - """Notifies the recipient of the desired event given the model""" - priority = message.get('priority', - FLAGS.default_notification_level) - priority = priority.lower() - logger = logging.getLogger( - 'nova.notification.%s' % message['event_type']) - getattr(logger, priority)(json.dumps(message)) + priority = message.get('priority', + FLAGS.default_notification_level) + priority = priority.lower() + logger = logging.getLogger( + 'nova.notification.%s' % message['event_type']) + getattr(logger, priority)(json.dumps(message)) diff --git a/nova/notifier/no_op_notifier.py b/nova/notifier/no_op_notifier.py index f5e745f1f..029710505 100644 --- a/nova/notifier/no_op_notifier.py +++ b/nova/notifier/no_op_notifier.py @@ -14,9 +14,6 @@ # under the License. -class NoopNotifier(object): - """A notifier that doesn't actually do anything. Simply a placeholder""" - - def notify(self, message): - """Notifies the recipient of the desired event given the model""" - pass +def notify(message): + """Notifies the recipient of the desired event given the model""" + pass diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index 7e2ee5f0b..acab79658 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -25,14 +25,11 @@ flags.DEFINE_string('notification_topic', 'notifications', 'RabbitMQ topic used for Nova notifications') -class RabbitNotifier(object): - """Sends notifications to a specific RabbitMQ server and topic""" - - def notify(self, message): - """Sends a notification to the RabbitMQ""" - context = nova.context.get_admin_context() - priority = message.get('priority', - FLAGS.default_notification_level) - priority = priority.lower() - topic = '%s.%s' % (FLAGS.notification_topic, priority) - rpc.cast(context, topic, message) +def notify(message): + """Sends a notification to the RabbitMQ""" + context = nova.context.get_admin_context() + priority = message.get('priority', + FLAGS.default_notification_level) + priority = priority.lower() + topic = '%s.%s' % (FLAGS.notification_topic, priority) + rpc.cast(context, topic, message) diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 82c4d3f5a..b6b0fcc68 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -43,12 +43,12 @@ class NotifierTestCase(test.TestCase): def mock_notify(cls, *args): self.notify_called = True - self.stubs.Set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', + self.stubs.Set(nova.notifier.no_op_notifier, 'notify', mock_notify) class Mock(object): pass - notify('event_name', 'publisher_id', 'event_type', + notify('publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3)) self.assertEqual(self.notify_called, True) @@ -56,24 +56,24 @@ class NotifierTestCase(test.TestCase): """A test to ensure changing the message format is prohibitively annoying""" - def message_assert(cls, message): + def message_assert(message): fields = [('publisher_id', 'publisher_id'), ('event_type', 'event_type'), ('priority', 'WARN'), - ('message', dict(a=3))] + ('payload', dict(a=3))] for k, v in fields: self.assertEqual(message[k], v) self.assertTrue(len(message['message_id']) > 0) self.assertTrue(len(message['timestamp']) > 0) - self.stubs.Set(nova.notifier.no_op_notifier.NoopNotifier, 'notify', + self.stubs.Set(nova.notifier.no_op_notifier, 'notify', message_assert) - notify('event_name', 'publisher_id', 'event_type', + notify('publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3)) def test_send_rabbit_notification(self): self.stubs.Set(nova.flags.FLAGS, 'notification_driver', - 'nova.notifier.rabbit_notifier.RabbitNotifier') + 'nova.notifier.rabbit_notifier') self.mock_cast = False def mock_cast(cls, *args): @@ -83,7 +83,7 @@ class NotifierTestCase(test.TestCase): pass self.stubs.Set(nova.rpc, 'cast', mock_cast) - notify('event_name', 'publisher_id', 'event_type', + notify('publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3)) self.assertEqual(self.mock_cast, True) @@ -97,12 +97,12 @@ class NotifierTestCase(test.TestCase): self.stubs.Set(nova.rpc, 'cast', mock_cast) self.assertRaises(nova.notifier.api.BadPriorityException, - notify, 'event_name', 'publisher_id', + notify, 'publisher_id', 'event_type', 'not a priority', dict(a=3)) def test_rabbit_priority_queue(self): self.stubs.Set(nova.flags.FLAGS, 'notification_driver', - 'nova.notifier.rabbit_notifier.RabbitNotifier') + 'nova.notifier.rabbit_notifier') self.stubs.Set(nova.flags.FLAGS, 'notification_topic', 'testnotify') @@ -112,6 +112,6 @@ class NotifierTestCase(test.TestCase): self.test_topic = topic self.stubs.Set(nova.rpc, 'cast', mock_cast) - notify('event_name', 'publisher_id', + notify('publisher_id', 'event_type', 'DEBUG', dict(a=3)) self.assertEqual(self.test_topic, 'testnotify.debug') -- cgit From 9fb47870df17e66a2294af7f52eb2dc5845405c0 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 16 May 2011 15:45:40 -0500 Subject: Conceded :-D --- nova/notifier/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 5b9b8ea29..a2231055a 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -37,7 +37,7 @@ class BadPriorityException(Exception): pass -def notify(publisher_id, event_type, priority, message): +def notify(publisher_id, event_type, priority, payload): """ Sends a notification using the specified driver @@ -47,7 +47,7 @@ def notify(publisher_id, event_type, priority, message): event_type - the literal type of event (ex. Instance Creation) priority - patterned after the enumeration of Python logging levels in the set (DEBUG, WARN, INFO, ERROR, CRITICAL) - message - A python dictionary of attributes + payload - A python dictionary of attributes Outgoing message format includes the above parameters, and appends the following: @@ -77,6 +77,6 @@ def notify(publisher_id, event_type, priority, message): publisher_id=publisher_id, event_type=event_type, priority=priority, - payload=message, + payload=payload, timestamp=str(datetime.datetime.utcnow())) driver.notify(msg) -- cgit From b00d39083901731a4345c0a0b13ce98f1dfaaf01 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Mon, 16 May 2011 17:58:07 -0400 Subject: add logging to migration and fix migration version --- .../016_make_instance_type_id_an_integer.py | 61 -------------------- .../017_make_instance_type_id_an_integer.py | 67 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 61 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/017_make_instance_type_id_an_integer.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py deleted file mode 100644 index e2d03ebf2..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_instance_type_id_an_integer.py +++ /dev/null @@ -1,61 +0,0 @@ -from sqlalchemy import Column, Integer, MetaData, String, Table - -meta = MetaData() - - -def upgrade(migrate_engine): - meta.bind = migrate_engine - instances = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - types = {} - for instance in migrate_engine.execute(instances.select()): - try: - types[instance.id] = int(instance.instance_type_id) - except (ValueError, TypeError): - types[instance.id] = None - - integer_column = Column('instance_type_id_int', Integer(), nullable=True) - string_column = instances.c.instance_type_id - - integer_column.create(instances) - for instance_id, instance_type_id in types.iteritems(): - update = instances.update().\ - where(instances.c.id == instance_id).\ - values(instance_type_id_int=instance_type_id) - migrate_engine.execute(update) - - string_column.alter(name='instance_type_id_str') - integer_column.alter(name='instance_type_id') - string_column.drop() - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - instances = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - integer_column = instances.c.instance_type_id - string_column = Column('instance_type_id_str', - String(length=255, convert_unicode=False, - assert_unicode=None, unicode_error=None, - _warn_on_bytestring=False), - nullable=True) - - types = {} - for instance in migrate_engine.execute(instances.select()): - if instance.instance_type_id is None: - types[instance.id] = None - else: - types[instance.id] = str(instance.instance_type_id) - - string_column.create(instances) - for instance_id, instance_type_id in types.iteritems(): - update = instances.update().\ - where(instances.c.id == instance_id).\ - values(instance_type_id_str=instance_type_id) - migrate_engine.execute(update) - - integer_column.alter(name='instance_type_id_int') - string_column.alter(name='instance_type_id') - integer_column.drop() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/017_make_instance_type_id_an_integer.py b/nova/db/sqlalchemy/migrate_repo/versions/017_make_instance_type_id_an_integer.py new file mode 100644 index 000000000..8da9108ce --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/017_make_instance_type_id_an_integer.py @@ -0,0 +1,67 @@ +from sqlalchemy import Column, Integer, MetaData, String, Table +from nova import log as logging + +meta = MetaData() + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + types = {} + for instance in migrate_engine.execute(instances.select()): + if instance.instance_type_id is None: + types[instance.id] = None + try: + types[instance.id] = int(instance.instance_type_id) + except ValueError: + logging.warn("Instance %s did not have instance_type_id " + "converted to an integer because its value is %s" % + (instance.id, instance.instance_type_id)) + types[instance.id] = None + + integer_column = Column('instance_type_id_int', Integer(), nullable=True) + string_column = instances.c.instance_type_id + + integer_column.create(instances) + for instance_id, instance_type_id in types.iteritems(): + update = instances.update().\ + where(instances.c.id == instance_id).\ + values(instance_type_id_int=instance_type_id) + migrate_engine.execute(update) + + string_column.alter(name='instance_type_id_str') + integer_column.alter(name='instance_type_id') + string_column.drop() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + integer_column = instances.c.instance_type_id + string_column = Column('instance_type_id_str', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + + types = {} + for instance in migrate_engine.execute(instances.select()): + if instance.instance_type_id is None: + types[instance.id] = None + else: + types[instance.id] = str(instance.instance_type_id) + + string_column.create(instances) + for instance_id, instance_type_id in types.iteritems(): + update = instances.update().\ + where(instances.c.id == instance_id).\ + values(instance_type_id_str=instance_type_id) + migrate_engine.execute(update) + + integer_column.alter(name='instance_type_id_int') + string_column.alter(name='instance_type_id') + integer_column.drop() -- cgit From d9eb72b3e76bd019b817936caa40c897416394b0 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 16 May 2011 17:36:42 -0500 Subject: Added missing metadata join to instance_get calls. --- nova/db/sqlalchemy/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 3681f30db..b53e81053 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -873,6 +873,7 @@ def instance_get_all(context): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('metadata')).\ options(joinedload('instance_type')).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -885,6 +886,7 @@ def instance_get_all_by_user(context, user_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('metadata')).\ options(joinedload('instance_type')).\ filter_by(deleted=can_read_deleted(context)).\ filter_by(user_id=user_id).\ -- cgit From 4dfe3a6b9ae44654b50ea8d65ac18a7a10f5abe3 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Tue, 17 May 2011 12:33:58 -0400 Subject: don't throw type errors on NoneType int conversions --- .../migrate_repo/versions/017_make_instance_type_id_an_integer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/017_make_instance_type_id_an_integer.py b/nova/db/sqlalchemy/migrate_repo/versions/017_make_instance_type_id_an_integer.py index 8da9108ce..cda890c94 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/017_make_instance_type_id_an_integer.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/017_make_instance_type_id_an_integer.py @@ -13,6 +13,7 @@ def upgrade(migrate_engine): for instance in migrate_engine.execute(instances.select()): if instance.instance_type_id is None: types[instance.id] = None + continue try: types[instance.id] = int(instance.instance_type_id) except ValueError: -- cgit From 11a36377f81f6f4c6c20e5802aa91e472772fbc9 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 17 May 2011 11:31:09 -0700 Subject: make token use typo that is in database. Also fix now -> utcnow and stop using . syntax for dealing with tokens --- nova/api/openstack/auth.py | 21 ++++++++++----------- nova/tests/api/openstack/test_auth.py | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 311e6bde9..7ccd46f54 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -17,7 +17,6 @@ import datetime import hashlib -import json import time import webob.exc @@ -25,11 +24,9 @@ import webob.dec from nova import auth from nova import context -from nova import db from nova import exception from nova import flags from nova import log as logging -from nova import manager from nova import utils from nova import wsgi from nova.api.openstack import faults @@ -102,11 +99,13 @@ class AuthMiddleware(wsgi.Middleware): token, user = self._authorize_user(username, key, req) if user and token: res = webob.Response() - res.headers['X-Auth-Token'] = token.token_hash + res.headers['X-Auth-Token'] = token['token_hash'] + # NOTE(vish): the apparrent typo in manageent is actually how it + # is in the db res.headers['X-Server-Management-Url'] = \ - token.server_management_url - res.headers['X-Storage-Url'] = token.storage_url - res.headers['X-CDN-Management-Url'] = token.cdn_management_url + token['server_manageent_url'] + res.headers['X-Storage-Url'] = token['storage_url'] + res.headers['X-CDN-Management-Url'] = token['cdn_management_url'] res.content_type = 'text/plain' res.status = '204' LOG.debug(_("Successfully authenticated '%s'") % username) @@ -130,11 +129,11 @@ class AuthMiddleware(wsgi.Middleware): except exception.NotFound: return None if token: - delta = datetime.datetime.now() - token.created_at + delta = datetime.datetime.utcnow() - token['created_at'] if delta.days >= 2: - self.db.auth_token_destroy(ctxt, token.token_hash) + self.db.auth_token_destroy(ctxt, token['token_hash']) else: - return self.auth.get_user(token.user_id) + return self.auth.get_user(token['user_id']) return None def _authorize_user(self, username, key, req): @@ -159,7 +158,7 @@ class AuthMiddleware(wsgi.Middleware): token_dict['token_hash'] = token_hash token_dict['cdn_management_url'] = '' os_url = req.url - token_dict['server_management_url'] = os_url + token_dict['server_manageent_url'] = os_url token_dict['storage_url'] = '' token_dict['user_id'] = user.id token = self.db.auth_token_create(ctxt, token_dict) diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py index 8f189c744..a35bdfef3 100644 --- a/nova/tests/api/openstack/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -150,7 +150,7 @@ class TestFunctional(test.TestCase): tok = db.auth_token_create(ctx, dict( token_hash='test_token_hash', cdn_management_url='', - server_management_url='', + server_manageent_url='', storage_url='', user_id='user1', )) -- cgit From 91e96cea27c91190f6205defa1f5a3641a0e0f56 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 17 May 2011 12:12:48 -0700 Subject: add migration for proper name --- nova/api/openstack/auth.py | 6 +-- .../versions/017_rename_server_management_url.py | 60 ++++++++++++++++++++++ nova/db/sqlalchemy/models.py | 2 +- nova/tests/api/openstack/test_auth.py | 2 +- 4 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/017_rename_server_management_url.py diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 7ccd46f54..6c6ee22a2 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -100,10 +100,8 @@ class AuthMiddleware(wsgi.Middleware): if user and token: res = webob.Response() res.headers['X-Auth-Token'] = token['token_hash'] - # NOTE(vish): the apparrent typo in manageent is actually how it - # is in the db res.headers['X-Server-Management-Url'] = \ - token['server_manageent_url'] + token['server_management_url'] res.headers['X-Storage-Url'] = token['storage_url'] res.headers['X-CDN-Management-Url'] = token['cdn_management_url'] res.content_type = 'text/plain' @@ -158,7 +156,7 @@ class AuthMiddleware(wsgi.Middleware): token_dict['token_hash'] = token_hash token_dict['cdn_management_url'] = '' os_url = req.url - token_dict['server_manageent_url'] = os_url + token_dict['server_management_url'] = os_url token_dict['storage_url'] = '' token_dict['user_id'] = user.id token = self.db.auth_token_create(ctxt, token_dict) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/017_rename_server_management_url.py b/nova/db/sqlalchemy/migrate_repo/versions/017_rename_server_management_url.py new file mode 100644 index 000000000..a169afb40 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/017_rename_server_management_url.py @@ -0,0 +1,60 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# +# 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 sqlalchemy import Column, Integer, MetaData, String, Table +#from nova import log as logging + +meta = MetaData() + +c_manageent = Column('server_manageent_url', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + +c_management = Column('server_management_url', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + + tokens = Table('auth_tokens', meta, autoload=True, + autoload_with=migrate_engine) + + tokens.create_column(c_management) + migrate_engine.execute(tokens.update() + .values(server_management_url=tokens.c.server_manageent_url)) + + tokens.c.server_manageent_url.drop() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + tokens = Table('auth_tokens', meta, autoload=True, + autoload_with=migrate_engine) + + tokens.create_column(c_manageent) + migrate_engine.execute(tokens.update() + .values(server_manageent_url=tokens.c.server_management_url)) + + tokens.c.server_management_url.drop() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 0b46d5a05..d9243a52e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -495,7 +495,7 @@ class AuthToken(BASE, NovaBase): __tablename__ = 'auth_tokens' token_hash = Column(String(255), primary_key=True) user_id = Column(String(255)) - server_manageent_url = Column(String(255)) + server_management_url = Column(String(255)) storage_url = Column(String(255)) cdn_management_url = Column(String(255)) diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py index a35bdfef3..8f189c744 100644 --- a/nova/tests/api/openstack/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -150,7 +150,7 @@ class TestFunctional(test.TestCase): tok = db.auth_token_create(ctx, dict( token_hash='test_token_hash', cdn_management_url='', - server_manageent_url='', + server_management_url='', storage_url='', user_id='user1', )) -- cgit From 2f23012b79d422b32832396147d308cd062b8d39 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 17 May 2011 12:30:39 -0700 Subject: fix test --- nova/tests/api/openstack/fakes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 8b0729c35..bf51239e6 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -228,6 +228,9 @@ class FakeToken(object): # FIXME(sirp): let's not use id here id = 0 + def __getitem__(self, key): + return getattr(self, key) + def __init__(self, **kwargs): FakeToken.id += 1 self.id = FakeToken.id -- cgit From 3506ae02196b0d44e36f915914b98abdc46db37b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 17 May 2011 13:49:51 -0700 Subject: move migration 017 to 018 --- .../versions/017_rename_server_management_url.py | 60 ---------------------- .../versions/018_rename_server_management_url.py | 60 ++++++++++++++++++++++ 2 files changed, 60 insertions(+), 60 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/017_rename_server_management_url.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/018_rename_server_management_url.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/017_rename_server_management_url.py b/nova/db/sqlalchemy/migrate_repo/versions/017_rename_server_management_url.py deleted file mode 100644 index a169afb40..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/017_rename_server_management_url.py +++ /dev/null @@ -1,60 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. -# -# 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 sqlalchemy import Column, Integer, MetaData, String, Table -#from nova import log as logging - -meta = MetaData() - -c_manageent = Column('server_manageent_url', - String(length=255, convert_unicode=False, - assert_unicode=None, unicode_error=None, - _warn_on_bytestring=False), - nullable=True) - -c_management = Column('server_management_url', - String(length=255, convert_unicode=False, - assert_unicode=None, unicode_error=None, - _warn_on_bytestring=False), - nullable=True) - - -def upgrade(migrate_engine): - # Upgrade operations go here. Don't create your own engine; - # bind migrate_engine to your metadata - meta.bind = migrate_engine - - tokens = Table('auth_tokens', meta, autoload=True, - autoload_with=migrate_engine) - - tokens.create_column(c_management) - migrate_engine.execute(tokens.update() - .values(server_management_url=tokens.c.server_manageent_url)) - - tokens.c.server_manageent_url.drop() - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - - tokens = Table('auth_tokens', meta, autoload=True, - autoload_with=migrate_engine) - - tokens.create_column(c_manageent) - migrate_engine.execute(tokens.update() - .values(server_manageent_url=tokens.c.server_management_url)) - - tokens.c.server_management_url.drop() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/018_rename_server_management_url.py b/nova/db/sqlalchemy/migrate_repo/versions/018_rename_server_management_url.py new file mode 100644 index 000000000..a169afb40 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/018_rename_server_management_url.py @@ -0,0 +1,60 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# +# 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 sqlalchemy import Column, Integer, MetaData, String, Table +#from nova import log as logging + +meta = MetaData() + +c_manageent = Column('server_manageent_url', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + +c_management = Column('server_management_url', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + + tokens = Table('auth_tokens', meta, autoload=True, + autoload_with=migrate_engine) + + tokens.create_column(c_management) + migrate_engine.execute(tokens.update() + .values(server_management_url=tokens.c.server_manageent_url)) + + tokens.c.server_manageent_url.drop() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + tokens = Table('auth_tokens', meta, autoload=True, + autoload_with=migrate_engine) + + tokens.create_column(c_manageent) + migrate_engine.execute(tokens.update() + .values(server_manageent_url=tokens.c.server_management_url)) + + tokens.c.server_management_url.drop() -- cgit From 4d025ef1d2b2b97c13d710cb5080b78e246215bc Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 18 May 2011 11:27:39 -0500 Subject: Added missing xenhost plugin. --- nova/virt/xenapi_conn.py | 8 +- .../xenserver/xenapi/etc/xapi.d/plugins/xenhost | 183 +++++++++++++++++++++ 2 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index eb572f295..6d828e109 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -169,15 +169,15 @@ class XenAPIConnection(driver.ComputeDriver): def __init__(self, url, user, pw): super(XenAPIConnection, self).__init__() - session = XenAPISession(url, user, pw) - self._vmops = VMOps(session) - self._volumeops = VolumeOps(session) + self._session = XenAPISession(url, user, pw) + self._vmops = VMOps(self._session) + self._volumeops = VolumeOps(self._session) self._host_state = None @property def HostState(self): if not self._host_state: - self._host_state = HostState(self.session) + self._host_state = HostState(self._session) return self._host_state def init_host(self, host): diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost new file mode 100644 index 000000000..a8428e841 --- /dev/null +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost @@ -0,0 +1,183 @@ +#!/usr/bin/env python + +# Copyright 2011 OpenStack LLC. +# Copyright 2011 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. + +# +# XenAPI plugin for reading/writing information to xenstore +# + +try: + import json +except ImportError: + import simplejson as json +import os +import random +import re +import subprocess +import tempfile +import time + +import XenAPIPlugin + +from pluginlib_nova import * +configure_logging("xenhost") + +host_data_pattern = re.compile(r"\s*(\S+) \([^\)]+\) *: ?(.*)") + + +def jsonify(fnc): + def wrapper(*args, **kwargs): + return json.dumps(fnc(*args, **kwargs)) + return wrapper + + +class TimeoutError(StandardError): + pass + + +def _run_command(cmd): + """Abstracts out the basics of issuing system commands. If the command + returns anything in stderr, a PluginError is raised with that information. + Otherwise, the output from stdout is returned. + """ + pipe = subprocess.PIPE + proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, + stderr=pipe, close_fds=True) + proc.wait() + err = proc.stderr.read() + if err: + raise pluginlib.PluginError(err) + return proc.stdout.read() + + +@jsonify +def host_data(self, arg_dict): + """Runs the commands on the xenstore host to return the current status + information. + """ + cmd = "xe host-list | grep uuid" + resp = _run_command(cmd) + host_uuid = resp.split(":")[-1].strip() + cmd = "xe host-param-list uuid=%s" % host_uuid + resp = _run_command(cmd) + parsed_data = parse_response(resp) + # We have the raw dict of values. Extract those that we need, + # and convert the data types as needed. + ret_dict = cleanup(parsed_data) + return ret_dict + + +def parse_response(resp): + data = {} + for ln in resp.splitlines(): + if not ln: + continue + mtch = host_data_pattern.match(ln.strip()) + try: + k, v = mtch.groups() + data[k] = v + except AttributeError: + # Not a valid line; skip it + continue + return data + + +def cleanup(dct): + """Take the raw KV pairs returned and translate them into the + appropriate types, discarding any we don't need. + """ + def safe_int(val): + """Integer values will either be string versions of numbers, + or empty strings. Convert the latter to nulls. + """ + try: + return int(val) + except ValueError: + return None + + def strip_kv(ln): + return [val.strip() for val in ln.split(":", 1)] + + out = {} + +# sbs = dct.get("supported-bootloaders", "") +# out["host_supported-bootloaders"] = sbs.split("; ") +# out["host_suspend-image-sr-uuid"] = dct.get("suspend-image-sr-uuid", "") +# out["host_crash-dump-sr-uuid"] = dct.get("crash-dump-sr-uuid", "") +# out["host_local-cache-sr"] = dct.get("local-cache-sr", "") + out["host_memory"] = omm = {} + omm["total"] = safe_int(dct.get("memory-total", "")) + omm["overhead"] = safe_int(dct.get("memory-overhead", "")) + omm["free"] = safe_int(dct.get("memory-free", "")) + omm["free-computed"] = safe_int( + dct.get("memory-free-computed", "")) + +# out["host_API-version"] = avv = {} +# avv["vendor"] = dct.get("API-version-vendor", "") +# avv["major"] = safe_int(dct.get("API-version-major", "")) +# avv["minor"] = safe_int(dct.get("API-version-minor", "")) + + out["host_uuid"] = dct.get("uuid", None) + out["host_name-label"] = dct.get("name-label", "") + out["host_name-description"] = dct.get("name-description", "") +# out["host_host-metrics-live"] = dct.get( +# "host-metrics-live", "false") == "true" + out["host_hostname"] = dct.get("hostname", "") + out["host_ip_address"] = dct.get("address", "") + oc = dct.get("other-config", "") + out["host_other-config"] = ocd = {} + if oc: + for oc_fld in oc.split("; "): + ock, ocv = strip_kv(oc_fld) + ocd[ock] = ocv +# out["host_capabilities"] = dct.get("capabilities", "").split("; ") +# out["host_allowed-operations"] = dct.get( +# "allowed-operations", "").split("; ") +# lsrv = dct.get("license-server", "") +# out["host_license-server"] = ols = {} +# if lsrv: +# for lspart in lsrv.split("; "): +# lsk, lsv = lspart.split(": ") +# if lsk == "port": +# ols[lsk] = safe_int(lsv) +# else: +# ols[lsk] = lsv +# sv = dct.get("software-version", "") +# out["host_software-version"] = osv = {} +# if sv: +# for svln in sv.split("; "): +# svk, svv = strip_kv(svln) +# osv[svk] = svv + cpuinf = dct.get("cpu_info", "") + out["host_cpu_info"] = ocp = {} + if cpuinf: + for cpln in cpuinf.split("; "): + cpk, cpv = strip_kv(cpln) + if cpk in ("cpu_count", "family", "model", "stepping"): + ocp[cpk] = safe_int(cpv) + else: + ocp[cpk] = cpv +# out["host_edition"] = dct.get("edition", "") +# out["host_external-auth-service-name"] = dct.get( +# "external-auth-service-name", "") + return out + + +if __name__ == "__main__": + XenAPIPlugin.dispatch( + {"host_data": host_data}) -- cgit From 62713a9485f9441a70526bb5245695338215c7af Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 18 May 2011 12:55:17 -0500 Subject: Spacing changes --- nova/notifier/api.py | 1 + nova/notifier/log_notifier.py | 1 + nova/notifier/rabbit_notifier.py | 1 + 3 files changed, 3 insertions(+) diff --git a/nova/notifier/api.py b/nova/notifier/api.py index a2231055a..a3e7a039e 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -19,6 +19,7 @@ import uuid from nova import flags from nova import utils + FLAGS = flags.FLAGS flags.DEFINE_string('default_notification_level', 'INFO', diff --git a/nova/notifier/log_notifier.py b/nova/notifier/log_notifier.py index a3df31721..25dfc693b 100644 --- a/nova/notifier/log_notifier.py +++ b/nova/notifier/log_notifier.py @@ -18,6 +18,7 @@ import json from nova import flags from nova import log as logging + FLAGS = flags.FLAGS diff --git a/nova/notifier/rabbit_notifier.py b/nova/notifier/rabbit_notifier.py index acab79658..d46670b58 100644 --- a/nova/notifier/rabbit_notifier.py +++ b/nova/notifier/rabbit_notifier.py @@ -19,6 +19,7 @@ import nova.context from nova import flags from nova import rpc + FLAGS = flags.FLAGS flags.DEFINE_string('notification_topic', 'notifications', -- cgit From 7b8f6ac41a9998dfd7ae48e7bb122cb8ce7ef35b Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 18 May 2011 21:25:35 +0000 Subject: Sort list of controllers/methods before printing --- bin/stack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/stack b/bin/stack index d84a82e27..a1c6d1348 100755 --- a/bin/stack +++ b/bin/stack @@ -65,7 +65,7 @@ def format_help(d): indent = MAX_INDENT - 6 out = [] - for k, v in d.iteritems(): + for k, v in sorted(d.iteritems()): if (len(k) + 6) > MAX_INDENT: out.extend([' %s' % k]) initial_indent = ' ' * (indent + 6) -- cgit