From a81aae612f409bc767af3013eeccb71226831fc2 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 17 Aug 2011 11:19:34 -0400 Subject: Add modules for task and vm states. --- nova/compute/task_state.py | 28 ++++++++++++++++++++++++++++ nova/compute/vm_state.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 nova/compute/task_state.py create mode 100644 nova/compute/vm_state.py diff --git a/nova/compute/task_state.py b/nova/compute/task_state.py new file mode 100644 index 000000000..b4dc9af51 --- /dev/null +++ b/nova/compute/task_state.py @@ -0,0 +1,28 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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. + +"""Possible task states for instances""" + +BUILD_BLOCK_DEVICE_MAPPING='block_device_mapping' +NETWORKING='networking' + +PASSWORD='password' + +RESIZE_PREP='resize_prep' +RESIZE_MIGRATING='resize_migrating' +RESIZE_MIGRATED='resize_migrated' +RESIZE_FINISH='resize_finish' diff --git a/nova/compute/vm_state.py b/nova/compute/vm_state.py new file mode 100644 index 000000000..e81cba1f0 --- /dev/null +++ b/nova/compute/vm_state.py @@ -0,0 +1,35 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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. + +"""Possible vm states for instances""" + +BUILD='build' +REBUILD='rebuild' +REBOOT='reboot' +DELETE='delete' +STOP='stop' +START='start' +RESIZE='resize' +VERIFY_RESIZE='verify_resize' +PAUSE='pause' +UNPAUSE='unpause' + +SUSPEND='suspend' +RESUME='resume' + +RESCUE='rescue' +UNRESCUE='unrescue' -- cgit From 5270b0a565ec26d2f7de3a7d95be86433d8c6bd2 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 17 Aug 2011 11:46:28 -0400 Subject: Split set state into vm, task, and power state functions. --- nova/db/sqlalchemy/api.py | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 57a4370d8..07207b8ee 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1439,15 +1439,43 @@ def instance_get_floating_address(context, instance_id): @require_admin_context -def instance_set_state(context, instance_id, state, description=None): - # TODO(devcamcar): Move this out of models and into driver - from nova.compute import power_state - if not description: - description = power_state.name(state) - db.instance_update(context, - instance_id, - {'state': state, - 'state_description': description}) +def instance_set_power_state(context, instance_id, power_state): + session = get_session() + partial = session.query(models.Instance) + + if utils.is_uuid_like(instance_id): + result = partial.filter_by(uuid=instance_id) + else: + result = partial.filter_by(id=instance_id) + + result.update({'power_state': power_state}) + + +@require_admin_context +def instance_set_vm_state(context, instance_id, vm_state): + # vm_state = running, halted, suspended, paused + session = get_session() + partial = session.query(models.Instance) + + if utils.is_uuid_like(instance_id): + result = partial.filter_by(uuid=instance_id) + else: + result = partial.filter_by(id=instance_id) + + result.update({'vm_state': vm_state}) + + +def instance_set_task_state(context, instance_id, task_state): + # task_state = running, halted, suspended, paused + session = get_session() + partial = session.query(models.Instance) + + if utils.is_uuid_like(instance_id): + result = partial.filter_by(uuid=instance_id) + else: + result = partial.filter_by(id=instance_id) + + result.update({'task_state': task_state}) @require_context -- cgit From 6515b115de8cd026ea88aab796d4364ccc2ac4f0 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 17 Aug 2011 11:51:39 -0400 Subject: Pep8 fixes. --- 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 07207b8ee..e7d02cb5d 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1442,7 +1442,7 @@ def instance_get_floating_address(context, instance_id): def instance_set_power_state(context, instance_id, power_state): session = get_session() partial = session.query(models.Instance) - + if utils.is_uuid_like(instance_id): result = partial.filter_by(uuid=instance_id) else: @@ -1456,7 +1456,7 @@ def instance_set_vm_state(context, instance_id, vm_state): # vm_state = running, halted, suspended, paused session = get_session() partial = session.query(models.Instance) - + if utils.is_uuid_like(instance_id): result = partial.filter_by(uuid=instance_id) else: @@ -1469,7 +1469,7 @@ def instance_set_task_state(context, instance_id, task_state): # task_state = running, halted, suspended, paused session = get_session() partial = session.query(models.Instance) - + if utils.is_uuid_like(instance_id): result = partial.filter_by(uuid=instance_id) else: -- cgit From bd2e98c064b7c1e9c866f3013e13af7883e11e05 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 17 Aug 2011 13:30:47 -0400 Subject: Initial instance states migration. --- .../versions/037_update_instance_states.py | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py new file mode 100644 index 000000000..0bbe39def --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py @@ -0,0 +1,57 @@ +# 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 MetaData, Table + +meta = MetaData() + +c_task_state = Column('task_state', + 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 + + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_state = instances.c.state + c_state.alter(name='power_state') + + c_vm_state = instances.c.state_description + c_vm_state.alter(name='vm_state') + + instances.create_column(c_task_state) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_state = instances.c.power_state + c_state.alter(name='state') + + c_vm_state = instances.c.vm_state + c_vm_state.alter(name='state_description') + + instances.drop_column('task_state') -- cgit From 1d1d027554d6be355bd9b52b2d87081d06f05045 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 17 Aug 2011 16:23:40 -0400 Subject: Updated compute manager/API to use vm/task states. Updated vm/task states to cover a few more cases I encountered. --- nova/compute/api.py | 57 ++++-- nova/compute/manager.py | 441 ++++++++++++++++++++++++--------------------- nova/compute/task_state.py | 17 +- nova/compute/vm_state.py | 8 +- 4 files changed, 296 insertions(+), 227 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index e909e9959..ec760853e 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -36,6 +36,7 @@ from nova import utils from nova import volume from nova.compute import instance_types from nova.compute import power_state +from nova.compute import vm_state from nova.compute.utils import terminate_volumes from nova.scheduler import api as scheduler_api from nova.db import base @@ -74,10 +75,13 @@ def generate_default_hostname(instance): def _is_able_to_shutdown(instance, instance_id): - states = {'terminating': "Instance %s is already being terminated", - 'migrating': "Instance %s is being migrated", - 'stopping': "Instance %s is being stopped"} - msg = states.get(instance['state_description']) + states = { + vm_state.DELETE: "Instance %s is already being terminated", + vm_state.MIGRATE: "Instance %s is being migrated", + vm_state.RESIZE: "Instance %s is being resized", + vm_state.STOP: "Instance %s is being stopped", + } + msg = states.get(instance['vm_state']) if msg: LOG.warning(_(msg), instance_id) return False @@ -231,8 +235,8 @@ class API(base.Base): 'image_ref': image_href, 'kernel_id': kernel_id or '', 'ramdisk_id': ramdisk_id or '', - 'state': 0, - 'state_description': 'scheduling', + 'power_state': power_state.NOSTATE, + 'vm_state': vm_state.BUILD, 'user_id': context.user_id, 'project_id': context.project_id, 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), @@ -648,9 +652,8 @@ class API(base.Base): return self.update(context, - instance['id'], - state_description='terminating', - state=0, + instance_id, + vm_state=vm_state.DELETE, terminated_at=utils.utcnow()) host = instance['host'] @@ -671,9 +674,8 @@ class API(base.Base): return self.update(context, - instance['id'], - state_description='stopping', - state=power_state.NOSTATE, + instance_id, + vm_state=vm_state.STOP, terminated_at=utils.utcnow()) host = instance['host'] @@ -685,12 +687,15 @@ class API(base.Base): """Start an instance.""" LOG.debug(_("Going to try to start %s"), instance_id) instance = self._get_instance(context, instance_id, 'starting') - if instance['state_description'] != 'stopped': - _state_description = instance['state_description'] + vm_state = instance["vm_state"] + + if vm_state != vm_state.STOP: LOG.warning(_("Instance %(instance_id)s is not " - "stopped(%(_state_description)s)") % locals()) + "stopped. (%(vm_state)s)") % locals()) return + self.update(context, instance_id, vm_state=vm_state.ACTIVE) + # TODO(yamahata): injected_files isn't supported right now. # It is used only for osapi. not for ec2 api. # availability_zone isn't used by run_instance. @@ -918,6 +923,7 @@ class API(base.Base): @scheduler_api.reroute_compute("reboot") def reboot(self, context, instance_id): """Reboot the given instance.""" + self.update(context, instance_id, vm_state=vm_state.REBOOT) self._cast_compute_message('reboot_instance', context, instance_id) @scheduler_api.reroute_compute("rebuild") @@ -925,8 +931,12 @@ class API(base.Base): metadata=None, files_to_inject=None): """Rebuild the given instance with the provided metadata.""" instance = db.api.instance_get(context, instance_id) + invalid_rebuild_states = [ + vm_state.BUILD, + vm_state.REBUILD, + ] - if instance["state"] == power_state.BUILDING: + if instance["vm_state"] in invalid_rebuild_states: msg = _("Instance already building") raise exception.BuildInProgress(msg) @@ -946,6 +956,8 @@ class API(base.Base): "injected_files": files_to_inject, } + self.update(context, instance_id, vm_state=vm_state.REBUILD) + self._cast_compute_message('rebuild_instance', context, instance_id, @@ -963,6 +975,8 @@ class API(base.Base): raise exception.MigrationNotFoundByStatus(instance_id=instance_id, status='finished') + self.update(context, instance_id, vm_state=vm_state.ACTIVE) + params = {'migration_id': migration_ref['id']} self._cast_compute_message('revert_resize', context, instance_ref['uuid'], @@ -983,6 +997,9 @@ class API(base.Base): if not migration_ref: raise exception.MigrationNotFoundByStatus(instance_id=instance_id, status='finished') + + self.update(context, instance_id, vm_state=vm_state.ACTIVE) + params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, instance_ref['uuid'], @@ -1028,6 +1045,8 @@ class API(base.Base): if (current_memory_mb == new_memory_mb) and flavor_id: raise exception.CannotResizeToSameSize() + self.update(context, instance_id, vm_state=vm_state.RESIZE) + instance_ref = self._get_instance(context, instance_id, 'resize') self._cast_scheduler_message(context, {"method": "prep_resize", @@ -1061,11 +1080,13 @@ class API(base.Base): @scheduler_api.reroute_compute("pause") def pause(self, context, instance_id): """Pause the given instance.""" + self.update(context, instance_id, vm_state=vm_state.PAUSE) self._cast_compute_message('pause_instance', context, instance_id) @scheduler_api.reroute_compute("unpause") def unpause(self, context, instance_id): """Unpause the given instance.""" + self.update(context, instance_id, vm_state=vm_state.ACTIVE) self._cast_compute_message('unpause_instance', context, instance_id) def set_host_enabled(self, context, host, enabled): @@ -1092,21 +1113,25 @@ class API(base.Base): @scheduler_api.reroute_compute("suspend") def suspend(self, context, instance_id): """Suspend the given instance.""" + self.update(context, instance_id, vm_state=vm_state.SUSPEND) self._cast_compute_message('suspend_instance', context, instance_id) @scheduler_api.reroute_compute("resume") def resume(self, context, instance_id): """Resume the given instance.""" + self.update(context, instance_id, vm_state=vm_state.ACTIVE) self._cast_compute_message('resume_instance', context, instance_id) @scheduler_api.reroute_compute("rescue") def rescue(self, context, instance_id): """Rescue the given instance.""" + self.update(context, instance_id, vm_state=vm_state.RESCUE) self._cast_compute_message('rescue_instance', context, instance_id) @scheduler_api.reroute_compute("unrescue") def unrescue(self, context, instance_id): """Unrescue the given instance.""" + self.update(context, instance_id, vm_state=vm_state.ACTIVE) self._cast_compute_message('unrescue_instance', context, instance_id) @scheduler_api.reroute_compute("set_admin_password") diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 3299268f2..34c6bc1ea 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -56,6 +56,8 @@ from nova import rpc from nova import utils from nova import volume from nova.compute import power_state +from nova.compute import task_state +from nova.compute import vm_state from nova.notifier import api as notifier from nova.compute.utils import terminate_volumes from nova.virt import driver @@ -146,6 +148,10 @@ class ComputeManager(manager.SchedulerDependentManager): super(ComputeManager, self).__init__(service_name="compute", *args, **kwargs) + def _instance_update(self, context, instance_id, **kwargs): + """Update an instance in the database using kwargs as value.""" + return self.db.instance_update(context, instance_id, kwargs) + def init_host(self): """Initialization for a standalone compute service.""" self.driver.init_host(host=self.host) @@ -153,8 +159,8 @@ class ComputeManager(manager.SchedulerDependentManager): instances = self.db.instance_get_all_by_host(context, self.host) for instance in instances: inst_name = instance['name'] - db_state = instance['state'] - drv_state = self._update_state(context, instance['id']) + db_state = instance['power_state'] + drv_state = self._get_power_state(context, instance) expect_running = db_state == power_state.RUNNING \ and drv_state != db_state @@ -177,34 +183,13 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.warning(_('Hypervisor driver does not ' 'support firewall rules')) - 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) - - if state is None: - try: - LOG.debug(_('Checking state of %s'), instance_ref['name']) - info = self.driver.get_info(instance_ref['name']) - except exception.NotFound: - info = None - - if info is not None: - state = info['state'] - else: - state = power_state.FAILED - - self.db.instance_set_state(context, instance_id, state) - return state - - def _update_launched_at(self, context, instance_id, launched_at=None): - """Update the launched_at parameter of the given instance.""" - data = {'launched_at': launched_at or utils.utcnow()} - self.db.instance_update(context, instance_id, data) - - def _update_image_ref(self, context, instance_id, image_ref): - """Update the image_id for the given instance.""" - data = {'image_ref': image_ref} - self.db.instance_update(context, instance_id, data) + def _get_power_state(self, context, instance): + """Retrieve the power state for the given instance.""" + LOG.debug(_('Checking state of %s'), instance['name']) + try: + return self.driver.get_info(instance['name'])["state"] + except exception.NotFound: + return power_state.FAILED def get_console_topic(self, context, **kwargs): """Retrieves the console host for a project on this host. @@ -388,13 +373,10 @@ class ComputeManager(manager.SchedulerDependentManager): # NOTE(vish): used by virt but not in database updates['injected_files'] = kwargs.get('injected_files', []) updates['admin_pass'] = kwargs.get('admin_password', None) - instance = self.db.instance_update(context, - instance_id, - updates) - self.db.instance_set_state(context, - instance_id, - power_state.NOSTATE, - 'networking') + updates['vm_state'] = vm_state.BUILD + updates['task_state'] = task_state.NETWORKING + + instance = self.db.instance_update(context, instance_id, updates) is_vpn = instance['image_ref'] == str(FLAGS.vpn_image_id) try: @@ -413,6 +395,11 @@ class ComputeManager(manager.SchedulerDependentManager): # all vif creation and network injection, maybe this is correct network_info = [] + self._instance_update(context, + instance_id, + vm_state=vm_state.BUILD, + task_state=task_state.BLOCK_DEVICE_MAPPING) + (swap, ephemerals, block_device_mapping) = self._setup_block_device_mapping( context, instance_id) @@ -422,9 +409,11 @@ class ComputeManager(manager.SchedulerDependentManager): 'ephemerals': ephemerals, 'block_device_mapping': block_device_mapping} - # TODO(vish) check to make sure the availability zone matches - self._update_state(context, instance_id, power_state.BUILDING) + self._instance_update(context, + instance_id, + task_state=task_state.SPAWN) + # TODO(vish) check to make sure the availability zone matches try: self.driver.spawn(context, instance, network_info, block_device_info) @@ -433,13 +422,21 @@ class ComputeManager(manager.SchedulerDependentManager): "virtualization enabled in the BIOS? Details: " "%(ex)s") % locals() LOG.exception(msg) + return + + current_power_state = self._get_power_state(context, instance) + self._instance_update(context, + instance_id, + power_state=current_power_state, + vm_state=vm_state.ACTIVE, + task_state=None, + launched_at=utils.utcnow()) - self._update_launched_at(context, instance_id) - self._update_state(context, instance_id) usage_info = utils.usage_from_instance(instance) notifier.notify('compute.%s' % self.host, 'compute.instance.create', notifier.INFO, usage_info) + except exception.InstanceNotFound: # FIXME(wwolf): We are just ignoring InstanceNotFound # exceptions here in case the instance was immediately @@ -523,11 +520,22 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_("Rebuilding instance %s"), instance_id, context=context) - self._update_state(context, instance_id, power_state.BUILDING) + current_power_state = self._get_power_state(context, instance_ref) + self._instance_update(context, + instance_id, + power_state=current_power_state, + vm_state=vm_state.REBUILD, + task_state=task_state.REBUILDING) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.destroy(instance_ref, network_info) + + self._instance_update(context, + instance_id, + power_state=current_power_state, + vm_state=vm_state.REBUILD, + task_state=task_state.SPAWN) + image_ref = kwargs.get('image_ref') instance_ref.image_ref = image_ref instance_ref.injected_files = kwargs.get('injected_files', []) @@ -536,9 +544,15 @@ class ComputeManager(manager.SchedulerDependentManager): bd_mapping = self._setup_block_device_mapping(context, instance_id) self.driver.spawn(context, instance_ref, network_info, bd_mapping) - self._update_image_ref(context, instance_id, image_ref) - self._update_launched_at(context, instance_id) - self._update_state(context, instance_id) + current_power_state = self._get_power_state(context, instance_ref) + self._instance_update(context, + instance_id, + power_state=current_power_state, + vm_state=vm_state.ACTIVE, + task_state=None, + image_ref=image_ref, + launched_at=utils.utcnow()) + usage_info = utils.usage_from_instance(instance_ref, image_ref=image_ref) notifier.notify('compute.%s' % self.host, @@ -550,26 +564,34 @@ class ComputeManager(manager.SchedulerDependentManager): @checks_instance_lock def reboot_instance(self, context, instance_id): """Reboot an instance on this host.""" + LOG.audit(_("Rebooting instance %s"), instance_id, context=context) context = context.elevated() - self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_("Rebooting instance %s"), instance_id, context=context) - if instance_ref['state'] != power_state.RUNNING: - state = instance_ref['state'] + current_power_state = self._get_power_state(context, instance_ref) + self._instance_update(context, + instance_id, + power_state=current_power_state, + vm_state=vm_state.REBOOT, + task_state=task_state.REBOOTING) + + if instance_ref['power_state'] != power_state.RUNNING: + state = instance_ref['power_state'] running = power_state.RUNNING LOG.warn(_('trying to reboot a non-running ' 'instance: %(instance_id)s (state: %(state)s ' 'expected: %(running)s)') % locals(), context=context) - self.db.instance_set_state(context, - instance_id, - power_state.NOSTATE, - 'rebooting') network_info = self._get_instance_nw_info(context, instance_ref) self.driver.reboot(instance_ref, network_info) - self._update_state(context, instance_id) + + current_power_state = self._get_power_state(context, instance_ref) + self._instance_update(context, + instance_id, + power_state=current_power_state, + vm_state=vm_state.ACTIVE, + task_state=None) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) def snapshot_instance(self, context, instance_id, image_id, @@ -585,37 +607,41 @@ class ComputeManager(manager.SchedulerDependentManager): :param rotation: int representing how many backups to keep around; None if rotation shouldn't be used (as in the case of snapshots) """ + if image_type != "snapshot" and image_type != "backup": + raise Exception(_('Image type not recognized %s') % image_type) + context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - #NOTE(sirp): update_state currently only refreshes the state field - # if we add is_snapshotting, we will need this refreshed too, - # potentially? - self._update_state(context, instance_id) + current_power_state = self._get_power_state(context, instance_ref) + self._instance_update(context, + instance_id, + power_state=current_power_state, + vm_state=vm_state.ACTIVE, + task_state=image_type) LOG.audit(_('instance %s: snapshotting'), instance_id, context=context) - if instance_ref['state'] != power_state.RUNNING: - state = instance_ref['state'] + + if instance_ref['power_state'] != power_state.RUNNING: + state = instance_ref['power_state'] running = power_state.RUNNING LOG.warn(_('trying to snapshot a non-running ' 'instance: %(instance_id)s (state: %(state)s ' 'expected: %(running)s)') % locals()) self.driver.snapshot(context, instance_ref, image_id) + self._instance_update(context, instance_id, task_state=None) + + if image_type == 'snapshot' and rotation: + raise exception.ImageRotationNotAllowed() + + elif image_type == 'backup' and rotation: + instance_uuid = instance_ref['uuid'] + self.rotate_backups(context, instance_uuid, backup_type, rotation) - if image_type == 'snapshot': - if rotation: - raise exception.ImageRotationNotAllowed() elif image_type == 'backup': - if rotation: - instance_uuid = instance_ref['uuid'] - self.rotate_backups(context, instance_uuid, backup_type, - rotation) - else: - raise exception.RotationRequiredForBackup() - else: - raise Exception(_('Image type not recognized %s') % image_type) + raise exception.RotationRequiredForBackup() def rotate_backups(self, context, instance_uuid, backup_type, rotation): """Delete excess backups associated to an instance. @@ -751,40 +777,51 @@ class ComputeManager(manager.SchedulerDependentManager): @checks_instance_lock def rescue_instance(self, context, instance_id): """Rescue an instance on this host.""" + LOG.audit(_('instance %s: rescuing'), instance_id, context=context) context = context.elevated() + + self._instance_update(context, + instance_id, + vm_state=vm_state.RESCUE, + task_state=task_state.RESCUING) + instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_('instance %s: rescuing'), instance_id, context=context) - self.db.instance_set_state(context, - instance_id, - power_state.NOSTATE, - 'rescuing') - _update_state = lambda result: self._update_state_callback( - self, context, instance_id, result) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.rescue(context, instance_ref, _update_state, network_info) - self._update_state(context, instance_id) + + # NOTE(blamar): None of the virt drivers use the 'callback' param + self.driver.rescue(context, instance_ref, None, network_info) + + current_power_state = self._get_power_state(context, instance_ref) + self._instance_update(context, + instance_id, + vm_state=vm_state.RESCUE, + task_state=task_state.RESCUED, + power_state=current_power_state) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock def unrescue_instance(self, context, instance_id): """Rescue an instance on this host.""" + LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) context = context.elevated() + + self._instance_update(context, + instance_id, + vm_state=vm_state.ACTIVE, + task_state=task_state.UNRESCUING) + instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) - self.db.instance_set_state(context, - instance_id, - power_state.NOSTATE, - 'unrescuing') - _update_state = lambda result: self._update_state_callback( - self, context, instance_id, result) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.unrescue(instance_ref, _update_state, network_info) - self._update_state(context, instance_id) - @staticmethod - def _update_state_callback(self, context, instance_id, result): - """Update instance state when async task completes.""" - self._update_state(context, instance_id) + # NOTE(blamar): None of the virt drivers use the 'callback' param + self.driver.unrescue(instance_ref, None, network_info) + + current_power_state = self._get_power_state(context, instance_ref) + self._instance_update(context, + instance_id, + vm_state=vm_state.ACTIVE, + task_state=None, + power_state=current_power_state) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock @@ -843,11 +880,12 @@ class ComputeManager(manager.SchedulerDependentManager): # Just roll back the record. There's no need to resize down since # the 'old' VM already has the preferred attributes - self.db.instance_update(context, instance_ref['uuid'], - dict(memory_mb=instance_type['memory_mb'], - vcpus=instance_type['vcpus'], - local_gb=instance_type['local_gb'], - instance_type_id=instance_type['id'])) + self._instance_update(context, + instance_ref["uuid"], + memory_mb=instance_type['memory_mb'], + vcpus=instance_type['vcpus'], + local_gb=instance_type['local_gb'], + instance_type_id=instance_type['id']) self.driver.revert_migration(instance_ref) self.db.migration_update(context, migration_id, @@ -1000,35 +1038,45 @@ class ComputeManager(manager.SchedulerDependentManager): @checks_instance_lock def pause_instance(self, context, instance_id): """Pause an instance on this host.""" + LOG.audit(_('instance %s: pausing'), instance_id, context=context) context = context.elevated() + + self._instance_update(context, + instance_id, + vm_state=vm_state.PAUSE, + task_state=task_state.PAUSING) + instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_('instance %s: pausing'), instance_id, context=context) - self.db.instance_set_state(context, - instance_id, - power_state.NOSTATE, - 'pausing') - self.driver.pause(instance_ref, - lambda result: self._update_state_callback(self, - context, - instance_id, - result)) + self.driver.pause(instance_ref, lambda result: None) + + current_power_state = self._get_power_state(context, instance_ref) + self._instance_update(context, + instance_id, + power_state=current_power_state, + vm_state=vm_state.PAUSE, + task_state=None) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock def unpause_instance(self, context, instance_id): """Unpause a paused instance on this host.""" + LOG.audit(_('instance %s: unpausing'), instance_id, context=context) context = context.elevated() + + self._instance_update(context, + instance_id, + vm_state=vm_state.ACTIVE, + task_state=task_state.UNPAUSING) + instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_('instance %s: unpausing'), instance_id, context=context) - self.db.instance_set_state(context, - instance_id, - power_state.NOSTATE, - 'unpausing') - self.driver.unpause(instance_ref, - lambda result: self._update_state_callback(self, - context, - instance_id, - result)) + self.driver.unpause(instance_ref, lambda result: None) + + current_power_state = self._get_power_state(context, instance_ref) + self._instance_update(context, + instance_id, + power_state=current_power_state, + vm_state=vm_state.ACTIVE, + task_state=None) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) def host_power_action(self, context, host=None, action=None): @@ -1053,33 +1101,45 @@ class ComputeManager(manager.SchedulerDependentManager): @checks_instance_lock def suspend_instance(self, context, instance_id): """Suspend the given instance.""" + LOG.audit(_('instance %s: suspending'), instance_id, context=context) context = context.elevated() + + self._instance_update(context, + instance_id, + vm_state=vm_state.SUSPEND, + task_state=task_state.SUSPENDING) + instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_('instance %s: suspending'), instance_id, context=context) - self.db.instance_set_state(context, instance_id, - power_state.NOSTATE, - 'suspending') - self.driver.suspend(instance_ref, - lambda result: self._update_state_callback(self, - context, - instance_id, - result)) + self.driver.suspend(instance_ref, lambda result: None) + + current_power_state = self._get_power_state(context, instance_ref) + self._instance_update(context, + instance_id, + power_state=current_power_state, + vm_state=vm_state.SUSPEND, + task_state=None) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock def resume_instance(self, context, instance_id): """Resume the given suspended instance.""" + LOG.audit(_('instance %s: resuming'), instance_id, context=context) context = context.elevated() + + self._instance_update(context, + instance_id, + vm_state=vm_state.ACTIVE, + task_state=task_state.RESUMING) + instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_('instance %s: resuming'), instance_id, context=context) - self.db.instance_set_state(context, instance_id, - power_state.NOSTATE, - 'resuming') - self.driver.resume(instance_ref, - lambda result: self._update_state_callback(self, - context, - instance_id, - result)) + self.driver.resume(instance_ref, lambda result: None) + + current_power_state = self._get_power_state(context, instance_ref) + self._instance_update(context, + instance_id, + power_state=current_power_state, + vm_state=vm_state.ACTIVE, + task_state=None) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) def lock_instance(self, context, instance_id): @@ -1489,11 +1549,14 @@ class ComputeManager(manager.SchedulerDependentManager): 'block_migration': block_migration}}) # Restore instance state - self.db.instance_update(ctxt, - instance_ref['id'], - {'state_description': 'running', - 'state': power_state.RUNNING, - 'host': dest}) + current_power_state = self._get_power_state(ctxt, instance_ref) + self._instance_update(ctxt, + instance_ref["id"], + host=dest, + power_state=current_power_state, + vm_state=vm_state.ACTIVE, + task_state=None) + # Restore volume state for volume_ref in instance_ref['volumes']: volume_id = volume_ref['id'] @@ -1539,11 +1602,11 @@ class ComputeManager(manager.SchedulerDependentManager): This param specifies destination host. """ host = instance_ref['host'] - self.db.instance_update(context, - instance_ref['id'], - {'state_description': 'running', - 'state': power_state.RUNNING, - 'host': host}) + self._instance_update(context, + instance_ref['id'], + host=host, + vm_state=vm_state.ACTIVE, + task_state=None) for volume_ref in instance_ref['volumes']: volume_id = volume_ref['id'] @@ -1591,10 +1654,9 @@ class ComputeManager(manager.SchedulerDependentManager): error_list.append(ex) try: - self._poll_instance_states(context) + self._sync_power_states(context) except Exception as ex: - LOG.warning(_("Error during instance poll: %s"), - unicode(ex)) + LOG.warning(_("Error during power_state sync: %s"), unicode(ex)) error_list.append(ex) return error_list @@ -1609,68 +1671,39 @@ class ComputeManager(manager.SchedulerDependentManager): 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) + def _sync_power_states(self, context): + """Align power states between the database and the hypervisor. - # Keep a list of VMs not in the DB, cross them off as we find them - vms_not_found_in_db = list(vm_instances.keys()) + The hypervisor is authoritative for the power_state data, so we + simply loop over all known instances for this host and update the + power_state according to the hypervisor. If the instance is not found + then it will be set to power_state.NOSTATE, because it doesn't exist + on the hypervisor. + """ + vm_instances = self.driver.list_instances_detail() db_instances = self.db.instance_get_all_by_host(context, self.host) + num_vm_instances = len(vm_instances) + num_db_instances = len(db_instances) + + if num_vm_instances != num_db_instances: + LOG.info(_("Found %(num_db_instances)s in the database and " + "%(num_vm_instances)s on the hypervisor.") % locals()) + for db_instance in db_instances: - name = db_instance['name'] - db_state = db_instance['state'] + name = db_instance["name"] + db_power_state = db_instance['power_state'] vm_instance = vm_instances.get(name) 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.BUILDING: - # 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 - # trying to correct that problem. - # We could have a separate task to correct this error. - # TODO(justinsb): What happens during a live migration? - LOG.info(_("Found instance '%(name)s' in DB but no VM. " - "State=%(db_state)s, so assuming spawn is in " - "progress.") % locals()) - vm_state = db_state - else: - LOG.info(_("Found instance '%(name)s' in DB but no VM. " - "State=%(db_state)s, so setting state to " - "shutoff.") % locals()) - vm_state = power_state.SHUTOFF - if db_instance['state_description'] == 'stopping': - self.db.instance_stop(context, db_instance['id']) - continue + vm_power_state = power_state.NOSTATE else: - vm_state = vm_instance.state - vms_not_found_in_db.remove(name) - - if (db_instance['state_description'] in ['migrating', 'stopping']): - # A situation which db record exists, but no instance" - # sometimes occurs while live-migration at src compute, - # this case should be ignored. - LOG.debug(_("Ignoring %(name)s, as it's currently being " - "migrated.") % locals()) - continue - - if vm_state != db_state: - LOG.info(_("DB/VM state mismatch. Changing state from " - "'%(db_state)s' to '%(vm_state)s'") % locals()) - self._update_state(context, db_instance['id'], vm_state) + vm_power_state = vm_instance["power_state"] - # NOTE(justinsb): We no longer auto-remove SHUTOFF instances - # It's quite hard to get them back when we do. - - # Are there VMs not in the DB? - for vm_not_found_in_db in vms_not_found_in_db: - name = vm_not_found_in_db + if vm_power_state == db_power_state: + continue - # We only care about instances that compute *should* know about - if name.startswith("instance-"): - # TODO(justinsb): What to do here? Adopt it? Shut it down? - LOG.warning(_("Found VM not in DB: '%(name)s'. Ignoring") - % locals()) + self._instance_update(context, + db_instance["id"], + power_state=vm_power_state) diff --git a/nova/compute/task_state.py b/nova/compute/task_state.py index b4dc9af51..55466c783 100644 --- a/nova/compute/task_state.py +++ b/nova/compute/task_state.py @@ -17,12 +17,27 @@ """Possible task states for instances""" -BUILD_BLOCK_DEVICE_MAPPING='block_device_mapping' +BLOCK_DEVICE_MAPPING='block_device_mapping' NETWORKING='networking' +SPAWN='spawn' +SNAPSHOT='snapshot' +BACKUP='backup' PASSWORD='password' RESIZE_PREP='resize_prep' RESIZE_MIGRATING='resize_migrating' RESIZE_MIGRATED='resize_migrated' RESIZE_FINISH='resize_finish' + +REBUILDING='rebuilding' + +REBOOTING='rebooting' +PAUSING='pausing' +UNPAUSING='unpausing' +SUSPENDING='suspending' +RESUMING='resuming' + +RESCUING='rescuing' +RESCUED='rescued' +UNRESCUING='unrescuing' diff --git a/nova/compute/vm_state.py b/nova/compute/vm_state.py index e81cba1f0..a1bca6ef4 100644 --- a/nova/compute/vm_state.py +++ b/nova/compute/vm_state.py @@ -17,19 +17,15 @@ """Possible vm states for instances""" +ACTIVE='active' BUILD='build' REBUILD='rebuild' REBOOT='reboot' DELETE='delete' STOP='stop' -START='start' +MIGRATE='migrate' RESIZE='resize' VERIFY_RESIZE='verify_resize' PAUSE='pause' -UNPAUSE='unpause' - SUSPEND='suspend' -RESUME='resume' - RESCUE='rescue' -UNRESCUE='unrescue' -- cgit From cab13dbfd652d1fcf9443e796e50f7eb374fc3fc Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 18 Aug 2011 12:34:01 -0400 Subject: Updated a number of items to pave the way for new states. --- nova/compute/manager.py | 31 +++++++++++----------- nova/db/sqlalchemy/api.py | 4 +-- .../versions/037_update_instance_states.py | 2 +- nova/db/sqlalchemy/models.py | 16 +++-------- nova/scheduler/driver.py | 11 ++++---- nova/tests/scheduler/test_scheduler.py | 13 ++++++--- nova/tests/test_compute.py | 29 ++++++++++---------- 7 files changed, 51 insertions(+), 55 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 34c6bc1ea..cb19a19cc 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -241,11 +241,6 @@ class ComputeManager(manager.SchedulerDependentManager): def _setup_block_device_mapping(self, context, instance_id): """setup volumes for block device mapping""" - self.db.instance_set_state(context, - instance_id, - power_state.NOSTATE, - 'block_device_mapping') - volume_api = volume.API() block_device_mapping = [] swap = None @@ -472,8 +467,7 @@ class ComputeManager(manager.SchedulerDependentManager): for volume in volumes: self._detach_volume(context, instance_id, volume['id'], False) - if (instance['state'] == power_state.SHUTOFF and - instance['state_description'] != 'stopped'): + if instance['power_state'] == power_state.SHUTOFF: self.db.instance_destroy(context, instance_id) raise exception.Error(_('trying to destroy already destroyed' ' instance: %s') % instance_id) @@ -532,16 +526,22 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - power_state=current_power_state, vm_state=vm_state.REBUILD, - task_state=task_state.SPAWN) + task_state=task_state.BLOCK_DEVICE_MAPPING) + + bd_mapping = self._setup_block_device_mapping(context, instance_id) image_ref = kwargs.get('image_ref') instance_ref.image_ref = image_ref instance_ref.injected_files = kwargs.get('injected_files', []) network_info = self.network_api.get_instance_nw_info(context, - instance_ref) - bd_mapping = self._setup_block_device_mapping(context, instance_id) + instance_ref) + + self._instance_update(context, + instance_id, + vm_state=vm_state.REBUILD, + task_state=task_state.SPAWN) + self.driver.spawn(context, instance_ref, network_info, bd_mapping) current_power_state = self._get_power_state(context, instance_ref) @@ -709,7 +709,7 @@ class ComputeManager(manager.SchedulerDependentManager): for i in xrange(max_tries): instance_ref = self.db.instance_get(context, instance_id) instance_id = instance_ref["id"] - instance_state = instance_ref["state"] + instance_state = instance_ref["power_state"] expected_state = power_state.RUNNING if instance_state != expected_state: @@ -744,7 +744,7 @@ class ComputeManager(manager.SchedulerDependentManager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) instance_id = instance_ref['id'] - instance_state = instance_ref['state'] + instance_state = instance_ref['power_state'] expected_state = power_state.RUNNING if instance_state != expected_state: LOG.warn(_('trying to inject a file into a non-running ' @@ -762,7 +762,7 @@ class ComputeManager(manager.SchedulerDependentManager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) instance_id = instance_ref['id'] - instance_state = instance_ref['state'] + instance_state = instance_ref['power_state'] expected_state = power_state.RUNNING if instance_state != expected_state: LOG.warn(_('trying to update agent on a non-running ' @@ -1092,7 +1092,7 @@ class ComputeManager(manager.SchedulerDependentManager): def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for an instance on this host.""" instance_ref = self.db.instance_get(context, instance_id) - if instance_ref["state"] == power_state.RUNNING: + if instance_ref["power_state"] == power_state.RUNNING: LOG.audit(_("instance %s: retrieving diagnostics"), instance_id, context=context) return self.driver.get_diagnostics(instance_ref) @@ -1682,6 +1682,7 @@ class ComputeManager(manager.SchedulerDependentManager): """ vm_instances = self.driver.list_instances_detail() + vm_instances = dict((vm.name, vm) for vm in vm_instances) db_instances = self.db.instance_get_all_by_host(context, self.host) num_vm_instances = len(vm_instances) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e7d02cb5d..67736dea2 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1079,8 +1079,8 @@ def instance_stop(context, instance_id): session.query(models.Instance).\ filter_by(id=instance_id).\ update({'host': None, - 'state': power_state.SHUTOFF, - 'state_description': 'stopped', + 'vm_state': vm_state.STOP, + 'task_state': None, 'updated_at': literal_column('updated_at')}) session.query(models.SecurityGroupInstanceAssociation).\ filter_by(instance_id=instance_id).\ diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py index 0bbe39def..07efbf90f 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import MetaData, Table +from sqlalchemy import MetaData, Table, Column, String meta = MetaData() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f2a4680b0..d2987cacc 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -193,8 +193,9 @@ class Instance(BASE, NovaBase): key_name = Column(String(255)) key_data = Column(Text) - state = Column(Integer) - state_description = Column(String(255)) + power_state = Column(Integer) + vm_state = Column(String(255)) + task_state = Column(String(255)) memory_mb = Column(Integer) vcpus = Column(Integer) @@ -232,17 +233,6 @@ class Instance(BASE, NovaBase): root_device_name = Column(String(255)) - # TODO(vish): see Ewan's email about state improvements, probably - # should be in a driver base class or some such - # vmstate_state = running, halted, suspended, paused - # power_state = what we have - # task_state = transitory and may trigger power state transition - - #@validates('state') - #def validate_state(self, key, state): - # assert(state in ['nostate', 'running', 'blocked', 'paused', - # 'shutdown', 'shutoff', 'crashed']) - class InstanceActions(BASE, NovaBase): """Represents a guest VM's actions and results""" diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index f28353f05..b788b996f 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -30,6 +30,8 @@ from nova import log as logging from nova import rpc from nova import utils from nova.compute import power_state +from nova.compute import task_state +from nova.compute import vm_state from nova.api.ec2 import ec2utils @@ -104,10 +106,8 @@ class Scheduler(object): dest, block_migration) # Changing instance_state. - db.instance_set_state(context, - instance_id, - power_state.PAUSED, - 'migrating') + values = {"vm_state": vm_state.MIGRATE} + db.instance_update(context, instance_id, values) # Changing volume state for volume_ref in instance_ref['volumes']: @@ -129,8 +129,7 @@ class Scheduler(object): """ # Checking instance is running. - if (power_state.RUNNING != instance_ref['state'] or \ - 'running' != instance_ref['state_description']): + if instance_ref['power_state'] != power_state.RUNNING: instance_id = ec2utils.id_to_ec2_id(instance_ref['id']) raise exception.InstanceNotRunning(instance_id=instance_id) diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index 158df2a27..1b5e131c9 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -40,6 +40,7 @@ from nova.scheduler import driver from nova.scheduler import manager from nova.scheduler import multi from nova.compute import power_state +from nova.compute import vm_state FLAGS = flags.FLAGS @@ -94,6 +95,9 @@ class SchedulerTestCase(test.TestCase): inst['vcpus'] = kwargs.get('vcpus', 1) inst['memory_mb'] = kwargs.get('memory_mb', 10) inst['local_gb'] = kwargs.get('local_gb', 20) + inst['vm_state'] = kwargs.get('vm_state', vm_state.ACTIVE) + inst['power_state'] = kwargs.get('power_state', power_state.RUNNING) + inst['task_state'] = kwargs.get('task_state', None) return db.instance_create(ctxt, inst) def test_fallback(self): @@ -271,8 +275,9 @@ class SimpleDriverTestCase(test.TestCase): inst['memory_mb'] = kwargs.get('memory_mb', 20) inst['local_gb'] = kwargs.get('local_gb', 30) inst['launched_on'] = kwargs.get('launghed_on', 'dummy') - inst['state_description'] = kwargs.get('state_description', 'running') - inst['state'] = kwargs.get('state', power_state.RUNNING) + inst['vm_state'] = kwargs.get('vm_state', vm_state.ACTIVE) + inst['task_state'] = kwargs.get('task_state', None) + inst['power_state'] = kwargs.get('power_state', power_state.RUNNING) return db.instance_create(self.context, inst)['id'] def _create_volume(self): @@ -664,14 +669,14 @@ class SimpleDriverTestCase(test.TestCase): block_migration=False) i_ref = db.instance_get(self.context, instance_id) - self.assertTrue(i_ref['state_description'] == 'migrating') + self.assertTrue(i_ref['vm_state'] == vm_state.MIGRATE) db.instance_destroy(self.context, instance_id) db.volume_destroy(self.context, v_ref['id']) def test_live_migration_src_check_instance_not_running(self): """The instance given by instance_id is not running.""" - instance_id = self._create_instance(state_description='migrating') + instance_id = self._create_instance(power_state=power_state.NOSTATE) i_ref = db.instance_get(self.context, instance_id) try: diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index e2fa3b140..f310eaff6 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -23,6 +23,7 @@ from nova import compute from nova.compute import instance_types from nova.compute import manager as compute_manager from nova.compute import power_state +from nova.compute import vm_state from nova import context from nova import db from nova.db.sqlalchemy import models @@ -747,8 +748,8 @@ class ComputeTestCase(test.TestCase): 'block_migration': False, 'disk': None}}).\ AndRaise(rpc.RemoteError('', '', '')) - dbmock.instance_update(c, i_ref['id'], {'state_description': 'running', - 'state': power_state.RUNNING, + dbmock.instance_update(c, i_ref['id'], {'vm_state': vm_state.ACTIVE, + 'task_state': None, 'host': i_ref['host']}) for v in i_ref['volumes']: dbmock.volume_update(c, v['id'], {'status': 'in-use'}) @@ -779,8 +780,8 @@ class ComputeTestCase(test.TestCase): 'block_migration': False, 'disk': None}}).\ AndRaise(rpc.RemoteError('', '', '')) - dbmock.instance_update(c, i_ref['id'], {'state_description': 'running', - 'state': power_state.RUNNING, + dbmock.instance_update(c, i_ref['id'], {'vm_state': vm_state.ACTIVE, + 'task_state': None, 'host': i_ref['host']}) self.compute.db = dbmock @@ -825,8 +826,8 @@ class ComputeTestCase(test.TestCase): c = context.get_admin_context() instance_id = self._create_instance() i_ref = db.instance_get(c, instance_id) - db.instance_update(c, i_ref['id'], {'state_description': 'migrating', - 'state': power_state.PAUSED}) + db.instance_update(c, i_ref['id'], {'vm_state': vm_state.MIGRATE, + 'power_state': power_state.PAUSED}) v_ref = db.volume_create(c, {'size': 1, 'instance_id': instance_id}) fix_addr = db.fixed_ip_create(c, {'address': '1.1.1.1', 'instance_id': instance_id}) @@ -887,7 +888,7 @@ class ComputeTestCase(test.TestCase): instances = db.instance_get_all(context.get_admin_context()) LOG.info(_("After force-killing instances: %s"), instances) self.assertEqual(len(instances), 1) - self.assertEqual(power_state.SHUTOFF, instances[0]['state']) + self.assertEqual(power_state.NOSTATE, instances[0]['power_state']) def test_get_all_by_name_regexp(self): """Test searching instances by name (display_name)""" @@ -1307,25 +1308,25 @@ class ComputeTestCase(test.TestCase): """Test searching instances by state""" c = context.get_admin_context() - instance_id1 = self._create_instance({'state': power_state.SHUTDOWN}) + instance_id1 = self._create_instance({'power_state': power_state.SHUTDOWN}) instance_id2 = self._create_instance({ 'id': 2, - 'state': power_state.RUNNING}) + 'power_state': power_state.RUNNING}) instance_id3 = self._create_instance({ 'id': 10, - 'state': power_state.RUNNING}) + 'power_state': power_state.RUNNING}) instances = self.compute_api.get_all(c, - search_opts={'state': power_state.SUSPENDED}) + search_opts={'power_state': power_state.SUSPENDED}) self.assertEqual(len(instances), 0) instances = self.compute_api.get_all(c, - search_opts={'state': power_state.SHUTDOWN}) + search_opts={'power_state': power_state.SHUTDOWN}) self.assertEqual(len(instances), 1) self.assertEqual(instances[0].id, instance_id1) instances = self.compute_api.get_all(c, - search_opts={'state': power_state.RUNNING}) + search_opts={'power_state': power_state.RUNNING}) self.assertEqual(len(instances), 2) instance_ids = [instance.id for instance in instances] self.assertTrue(instance_id2 in instance_ids) @@ -1333,7 +1334,7 @@ class ComputeTestCase(test.TestCase): # Test passing a list as search arg instances = self.compute_api.get_all(c, - search_opts={'state': [power_state.SHUTDOWN, + search_opts={'power_state': [power_state.SHUTDOWN, power_state.RUNNING]}) self.assertEqual(len(instances), 3) -- cgit From 805c1cec609b39ee5a0ba1517bf2f1d41e0c4fa9 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Thu, 18 Aug 2011 15:05:35 -0700 Subject: allow specification of key pair/security group info via metadata extract metadata about keypair / security group configuration from server metadata sent on create. This allows users to use these extensions with their existing api implementations. Also remove the code that choose the first key pair in the tenant - since it seems to have been used during the development of os api --- nova/api/openstack/create_instance_helper.py | 21 ++++++++------------- nova/api/openstack/servers.py | 1 - 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index b4a08dac0..031b06921 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -73,20 +73,15 @@ class CreateInstanceHelper(object): if not 'server' in body: raise exc.HTTPUnprocessableEntity() - server_dict = body['server'] context = req.environ['nova.context'] + server_dict = body['server'] + metadata = server_dict.get('metadata', {}) password = self.controller._get_server_admin_password(server_dict) - key_name = None - key_data = None - # TODO(vish): Key pair access should move into a common library - # instead of being accessed directly from the db. - key_pairs = db.key_pair_get_all_by_user(context.elevated(), - context.user_id) - if key_pairs: - key_pair = key_pairs[0] - key_name = key_pair['name'] - key_data = key_pair['public_key'] + # NOTE(ja): extract key_name and security_group from metadata + # to use in os extensions for firewall & keypairs + key_name = metadata.get('key_name') + security_group = metadata.get('security_group') image_href = self.controller._image_ref_from_req_data(body) # If the image href was generated by nova api, strip image_href @@ -155,8 +150,8 @@ class CreateInstanceHelper(object): display_name=name, display_description=name, key_name=key_name, - key_data=key_data, - metadata=server_dict.get('metadata', {}), + security_group=security_group, + metadata=metadata, injected_files=injected_files, admin_password=password, zone_blob=zone_blob, diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 335ecad86..2cf4e3eda 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -146,7 +146,6 @@ class Controller(object): def create(self, req, body): """ Creates a new server for a given user """ extra_values = None - result = None extra_values, instances = self.helper.create_instance( req, body, self.compute_api.create) -- cgit From bbe414cba5d389b553fb3122a3a7dbc9c6d898f2 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Thu, 18 Aug 2011 16:26:32 -0700 Subject: follow same pattern as userdata (not metadata apporach) --- nova/api/openstack/create_instance_helper.py | 27 +++++++++++++-------------- nova/api/openstack/views/servers.py | 3 +++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index e64a076c8..c2e9e76ac 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -75,13 +75,15 @@ class CreateInstanceHelper(object): context = req.environ['nova.context'] server_dict = body['server'] - metadata = server_dict.get('metadata', {}) password = self.controller._get_server_admin_password(server_dict) - # NOTE(ja): extract key_name and security_group from metadata - # to use in os extensions for firewall & keypairs - key_name = metadata.get('key_name') - security_group = metadata.get('security_group') + if not 'name' in server_dict: + msg = _("Server name is not defined") + raise exc.HTTPBadRequest(explanation=msg) + + name = server_dict['name'] + self._validate_server_name(name) + name = name.strip() image_href = self.controller._image_ref_from_req_data(body) # If the image href was generated by nova api, strip image_href @@ -112,16 +114,13 @@ class CreateInstanceHelper(object): msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) - if not 'name' in server_dict: - msg = _("Server name is not defined") - raise exc.HTTPBadRequest(explanation=msg) - zone_blob = server_dict.get('blob') - user_data = server_dict.get('user_data') availability_zone = server_dict.get('availability_zone') - name = server_dict['name'] - self._validate_server_name(name) - name = name.strip() + + # optional openstack extensions: + key_name = server_dict.get('key_name') + security_group = server_dict.get('security_group') + user_data = server_dict.get('user_data') reservation_id = server_dict.get('reservation_id') min_count = server_dict.get('min_count') @@ -152,7 +151,7 @@ class CreateInstanceHelper(object): display_description=name, key_name=key_name, security_group=security_group, - metadata=metadata, + metadata=server_dict.get('metadata', {}), injected_files=injected_files, admin_password=password, zone_blob=zone_blob, diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index edc328129..ac2de0c57 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -78,6 +78,9 @@ class ViewBuilder(object): metadata[item['key']] = str(item['value']) inst_dict['metadata'] = metadata + inst_dict['key_name'] = inst.get('key_name') + inst_dict['security_group'] = inst.get('security_group') + inst_dict['hostId'] = '' if inst.get('host'): inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest() -- cgit From f4f0ce95946962be73cfc509b24fd000fc931198 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Thu, 18 Aug 2011 16:46:16 -0700 Subject: tests pass --- nova/api/openstack/views/servers.py | 4 ++-- nova/tests/api/openstack/test_servers.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index ac2de0c57..285228b30 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -78,8 +78,8 @@ class ViewBuilder(object): metadata[item['key']] = str(item['value']) inst_dict['metadata'] = metadata - inst_dict['key_name'] = inst.get('key_name') - inst_dict['security_group'] = inst.get('security_group') + inst_dict['key_name'] = inst.get('key_name', '') + inst_dict['security_group'] = inst.get('security_group', '') inst_dict['hostId'] = '' if inst.get('host'): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index a510d7d97..e374abb6b 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -335,6 +335,8 @@ class ServersTest(test.TestCase): "name": "server1", "status": "BUILD", "hostId": '', + "key_name": '', + "security_group": '', "image": { "id": "10", "links": [ @@ -497,6 +499,8 @@ class ServersTest(test.TestCase): "name": "server1", "status": "ACTIVE", "hostId": '', + "key_name": '', + "security_group": '', "image": { "id": "10", "links": [ @@ -588,6 +592,8 @@ class ServersTest(test.TestCase): "name": "server1", "status": "ACTIVE", "hostId": '', + "key_name": '', + "security_group": '', "image": { "id": "10", "links": [ @@ -3094,6 +3100,8 @@ class ServersViewBuilderV11Test(test.TestCase): "name": "test_server", "status": "BUILD", "hostId": '', + "key_name": '', + "security_group": '', "image": { "id": "5", "links": [ @@ -3145,6 +3153,8 @@ class ServersViewBuilderV11Test(test.TestCase): "name": "test_server", "status": "ACTIVE", "hostId": '', + "key_name": '', + "security_group": '', "image": { "id": "5", "links": [ @@ -3200,6 +3210,8 @@ class ServersViewBuilderV11Test(test.TestCase): "name": "test_server", "status": "BUILD", "hostId": '', + "key_name": '', + "security_group": '', "image": { "id": "5", "links": [ @@ -3265,6 +3277,8 @@ class ServerXMLSerializationTest(test.TestCase): "name": "test_server", "status": "BUILD", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', + "key_name": '', + "security_group": '', "image": { "id": "5", "links": [ -- cgit From 203309693fc2dd648b9d4b211686228557728c89 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 18 Aug 2011 19:05:40 -0700 Subject: remove security groups, improve exception handling, add tests --- nova/api/openstack/create_instance_helper.py | 5 ++-- nova/api/openstack/views/servers.py | 1 - nova/tests/api/openstack/test_servers.py | 42 +++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index c2e9e76ac..84d8c0c39 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -119,7 +119,6 @@ class CreateInstanceHelper(object): # optional openstack extensions: key_name = server_dict.get('key_name') - security_group = server_dict.get('security_group') user_data = server_dict.get('user_data') reservation_id = server_dict.get('reservation_id') @@ -150,7 +149,6 @@ class CreateInstanceHelper(object): display_name=name, display_description=name, key_name=key_name, - security_group=security_group, metadata=server_dict.get('metadata', {}), injected_files=injected_files, admin_password=password, @@ -168,6 +166,9 @@ class CreateInstanceHelper(object): except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) + except exception.KeypairNotFound as error: + msg = _("Invalid key_name provided.") + raise exc.HTTPBadRequest(explanation=msg) # Let the caller deal with unhandled exceptions. def _handle_quota_error(self, error): diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 285228b30..c0df5abe6 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -79,7 +79,6 @@ class ViewBuilder(object): inst_dict['metadata'] = metadata inst_dict['key_name'] = inst.get('key_name', '') - inst_dict['security_group'] = inst.get('security_group', '') inst_dict['hostId'] = '' if inst.get('host'): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index e374abb6b..139820c09 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -336,7 +336,6 @@ class ServersTest(test.TestCase): "status": "BUILD", "hostId": '', "key_name": '', - "security_group": '', "image": { "id": "10", "links": [ @@ -500,7 +499,6 @@ class ServersTest(test.TestCase): "status": "ACTIVE", "hostId": '', "key_name": '', - "security_group": '', "image": { "id": "10", "links": [ @@ -593,7 +591,6 @@ class ServersTest(test.TestCase): "status": "ACTIVE", "hostId": '', "key_name": '', - "security_group": '', "image": { "id": "10", "links": [ @@ -1643,6 +1640,41 @@ class ServersTest(test.TestCase): self.assertEqual(expected_flavor, server['flavor']) self.assertEqual(expected_image, server['image']) + def test_create_instance_v1_1_invalid_key_name(self): + self._setup_for_create_instance() + + image_href = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/flavors/3' + body = dict(server=dict( + name='server_test', imageRef=image_href, flavorRef=flavor_ref, + key_name='nonexistentkey')) + 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()) + self.assertEqual(res.status_int, 400) + + def test_create_instance_v1_1_valid_key_name(self): + self._setup_for_create_instance() + + def key_pair_get(context, user_id, key_name): + return dict(name='mykey', public_key='public_key') + + self.stubs.Set(nova.db, 'key_pair_get', key_pair_get) + + image_href = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/flavors/3' + body = dict(server=dict( + name='server_test', imageRef=image_href, flavorRef=flavor_ref, + key_name='mykey')) + 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()) + self.assertEqual(res.status_int, 202) + def test_create_instance_v1_1_invalid_flavor_href(self): self._setup_for_create_instance() @@ -3101,7 +3133,6 @@ class ServersViewBuilderV11Test(test.TestCase): "status": "BUILD", "hostId": '', "key_name": '', - "security_group": '', "image": { "id": "5", "links": [ @@ -3154,7 +3185,6 @@ class ServersViewBuilderV11Test(test.TestCase): "status": "ACTIVE", "hostId": '', "key_name": '', - "security_group": '', "image": { "id": "5", "links": [ @@ -3211,7 +3241,6 @@ class ServersViewBuilderV11Test(test.TestCase): "status": "BUILD", "hostId": '', "key_name": '', - "security_group": '', "image": { "id": "5", "links": [ @@ -3278,7 +3307,6 @@ class ServerXMLSerializationTest(test.TestCase): "status": "BUILD", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', "key_name": '', - "security_group": '', "image": { "id": "5", "links": [ -- cgit From 5ef94944514c3f81e31cc60d3d63b903859dca45 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 18 Aug 2011 22:15:13 -0700 Subject: add user_id and description. without user_id, there is no way for a tenant to tell which user created the server. description should be added for ec2 parity. --- nova/api/openstack/create_instance_helper.py | 3 ++- nova/api/openstack/views/servers.py | 2 ++ nova/tests/api/openstack/test_servers.py | 22 +++++++++++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 978741682..96f817d38 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -154,7 +154,8 @@ class CreateInstanceHelper(object): kernel_id=kernel_id, ramdisk_id=ramdisk_id, display_name=name, - display_description=name, + display_description=\ + server_dict.get('description', ''), key_name=key_name, key_data=key_data, metadata=server_dict.get('metadata', {}), diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index edc328129..c5f1e6021 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -64,6 +64,8 @@ class ViewBuilder(object): inst_dict = { 'id': inst['id'], 'name': inst['display_name'], + 'user_id': inst['user_id'], + 'description': inst['display_description'], 'status': common.status_from_power_state(inst.get('state'))} ctxt = nova.context.get_admin_context() diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 437620854..7ca58b24d 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -194,7 +194,7 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, "terminated_at": utils.utcnow(), "availability_zone": "", "display_name": server_name, - "display_description": "", + "display_description": "fakedescription", "locked": False, "metadata": metadata, "uuid": uuid, @@ -329,10 +329,12 @@ class ServersTest(test.TestCase): "server": { "id": 1, "uuid": FAKE_UUID, + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "server1", + "description": "fakedescription", "status": "BUILD", "hostId": '', "image": { @@ -491,10 +493,12 @@ class ServersTest(test.TestCase): "server": { "id": 1, "uuid": FAKE_UUID, + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "server1", + "description": "fakedescription", "status": "ACTIVE", "hostId": '', "image": { @@ -582,10 +586,12 @@ class ServersTest(test.TestCase): "server": { "id": 1, "uuid": FAKE_UUID, + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "server1", + "description": "fakedescription", "status": "ACTIVE", "hostId": '', "image": { @@ -1380,6 +1386,8 @@ class ServersTest(test.TestCase): 'uuid': FAKE_UUID, 'instance_type': dict(inst_type), 'image_ref': image_ref, + 'display_description': 'fakedescription', + 'user_id': 'fake', "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0), } @@ -2719,6 +2727,8 @@ class TestServerInstanceCreation(test.TestCase): else: self.injected_files = None return [{'id': '1234', 'display_name': 'fakeinstance', + 'user_id': 'fake', + 'display_description': 'fakedescription', 'uuid': FAKE_UUID}] def set_admin_password(self, *args, **kwargs): @@ -3010,7 +3020,7 @@ class ServersViewBuilderV11Test(test.TestCase): "created_at": created_at, "updated_at": updated_at, "admin_pass": "", - "user_id": "", + "user_id": "fake", "project_id": "", "image_ref": "5", "kernel_id": "", @@ -3036,7 +3046,7 @@ class ServersViewBuilderV11Test(test.TestCase): "terminated_at": utils.utcnow(), "availability_zone": "", "display_name": "test_server", - "display_description": "", + "display_description": "fakedescription", "locked": False, "metadata": [], #"address": , @@ -3088,10 +3098,12 @@ class ServersViewBuilderV11Test(test.TestCase): "server": { "id": 1, "uuid": self.instance['uuid'], + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "test_server", + "description": "fakedescription", "status": "BUILD", "hostId": '', "image": { @@ -3139,10 +3151,12 @@ class ServersViewBuilderV11Test(test.TestCase): "server": { "id": 1, "uuid": self.instance['uuid'], + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "test_server", + "description": "fakedescription", "status": "ACTIVE", "hostId": '', "image": { @@ -3194,10 +3208,12 @@ class ServersViewBuilderV11Test(test.TestCase): "server": { "id": 1, "uuid": self.instance['uuid'], + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "test_server", + "description": "fakedescription", "status": "BUILD", "hostId": '', "image": { -- cgit From 1ab7b11a439cef8b48621355467d5fb460d9b2e2 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Thu, 18 Aug 2011 22:52:45 -0700 Subject: add key_name/data support to server stub --- nova/tests/api/openstack/test_servers.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 139820c09..605b3fee3 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -145,7 +145,7 @@ def instance_addresses(context, instance_id): def stub_instance(id, user_id='fake', project_id='fake', private_address=None, public_addresses=None, host=None, power_state=0, reservation_id="", uuid=FAKE_UUID, image_ref="10", - flavor_id="1", interfaces=None, name=None): + flavor_id="1", interfaces=None, name=None, key_name=''): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -160,6 +160,11 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, if host is not None: host = str(host) + if key_name: + key_data = 'FAKE' + else: + key_data = '' + # ReservationID isn't sent back, hack it in there. server_name = name or "server%s" % id if reservation_id != "": @@ -176,8 +181,8 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, "kernel_id": "", "ramdisk_id": "", "launch_index": 0, - "key_name": "", - "key_data": "", + "key_name": key_name, + "key_data": key_data, "state": power_state, "state_description": "", "memory_mb": 0, -- cgit From 9b96b24ec93864731b6fc5031d2eceb22398be24 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 19 Aug 2011 09:30:52 -0400 Subject: Bump migration number. --- .../versions/037_update_instance_states.py | 57 ---------------------- .../versions/039_update_instance_states.py | 57 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 57 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/039_update_instance_states.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py deleted file mode 100644 index 07efbf90f..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/037_update_instance_states.py +++ /dev/null @@ -1,57 +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 MetaData, Table, Column, String - -meta = MetaData() - -c_task_state = Column('task_state', - 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 - - instances = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - c_state = instances.c.state - c_state.alter(name='power_state') - - c_vm_state = instances.c.state_description - c_vm_state.alter(name='vm_state') - - instances.create_column(c_task_state) - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - - instances = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - c_state = instances.c.power_state - c_state.alter(name='state') - - c_vm_state = instances.c.vm_state - c_vm_state.alter(name='state_description') - - instances.drop_column('task_state') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/039_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/039_update_instance_states.py new file mode 100644 index 000000000..07efbf90f --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/039_update_instance_states.py @@ -0,0 +1,57 @@ +# 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 MetaData, Table, Column, String + +meta = MetaData() + +c_task_state = Column('task_state', + 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 + + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_state = instances.c.state + c_state.alter(name='power_state') + + c_vm_state = instances.c.state_description + c_vm_state.alter(name='vm_state') + + instances.create_column(c_task_state) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_state = instances.c.power_state + c_state.alter(name='state') + + c_vm_state = instances.c.vm_state + c_vm_state.alter(name='state_description') + + instances.drop_column('task_state') -- cgit From 34ef09beb3bf00fd9eb16b8517c520af24641e8c Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 19 Aug 2011 10:10:51 -0700 Subject: add tenant_id to api. without tenant_id, admins can't tell which servers belong to which tenants when retrieving lists --- nova/api/openstack/servers.py | 5 +++++ nova/api/openstack/views/servers.py | 1 + nova/tests/api/openstack/test_servers.py | 29 ++++++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 41e63ec3c..57ed5f45e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -107,6 +107,11 @@ class Controller(object): LOG.error(reason) raise exception.InvalidInput(reason=reason) + # translate tenant_id filter to internal project_id + if 'tenant_id' in search_opts: + search_opts['project_id'] = search_opts['tenant_id'] + del search_opts['tenant_id'] + # By default, compute's get_all() will return deleted instances. # If an admin hasn't specified a 'deleted' search option, we need # to filter out deleted instances by setting the filter ourselves. diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index c5f1e6021..37f48b3b2 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -65,6 +65,7 @@ class ViewBuilder(object): 'id': inst['id'], 'name': inst['display_name'], 'user_id': inst['user_id'], + 'tenant_id': inst['project_id'], 'description': inst['display_description'], 'status': common.status_from_power_state(inst.get('state'))} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 7ca58b24d..480d6a370 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -330,6 +330,7 @@ class ServersTest(test.TestCase): "id": 1, "uuid": FAKE_UUID, "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, @@ -494,6 +495,7 @@ class ServersTest(test.TestCase): "id": 1, "uuid": FAKE_UUID, "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, @@ -587,6 +589,7 @@ class ServersTest(test.TestCase): "id": 1, "uuid": FAKE_UUID, "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, @@ -1152,6 +1155,25 @@ class ServersTest(test.TestCase): self.assertEqual(len(servers), 1) self.assertEqual(servers[0]['id'], 100) + def test_tenant_id_filter_converts_to_project_id_for_admin(self): + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + self.assertEqual(search_opts['project_id'], 'faketenant') + self.assertFalse(search_opts.get('tenant_id')) + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + self.flags(allow_admin_api=True) + + req = webob.Request.blank('/v1.1/servers?tenant_id=faketenant') + # Use admin context + context = nova.context.RequestContext('testuser', 'testproject', + is_admin=True) + res = req.get_response(fakes.wsgi_app(fake_auth_context=context)) + res_dict = json.loads(res.body) + # Failure in fake_get_all returns non 200 status code + self.assertEqual(res.status_int, 200) + def test_get_servers_allows_flavor_v1_1(self): def fake_get_all(compute_self, context, search_opts=None): self.assertNotEqual(search_opts, None) @@ -1388,6 +1410,7 @@ class ServersTest(test.TestCase): 'image_ref': image_ref, 'display_description': 'fakedescription', 'user_id': 'fake', + 'project_id': 'fake', "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0), } @@ -2728,6 +2751,7 @@ class TestServerInstanceCreation(test.TestCase): self.injected_files = None return [{'id': '1234', 'display_name': 'fakeinstance', 'user_id': 'fake', + 'project_id': 'fake', 'display_description': 'fakedescription', 'uuid': FAKE_UUID}] @@ -3021,7 +3045,7 @@ class ServersViewBuilderV11Test(test.TestCase): "updated_at": updated_at, "admin_pass": "", "user_id": "fake", - "project_id": "", + "project_id": "fake", "image_ref": "5", "kernel_id": "", "ramdisk_id": "", @@ -3099,6 +3123,7 @@ class ServersViewBuilderV11Test(test.TestCase): "id": 1, "uuid": self.instance['uuid'], "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, @@ -3152,6 +3177,7 @@ class ServersViewBuilderV11Test(test.TestCase): "id": 1, "uuid": self.instance['uuid'], "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, @@ -3209,6 +3235,7 @@ class ServersViewBuilderV11Test(test.TestCase): "id": 1, "uuid": self.instance['uuid'], "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, -- cgit From 3bd386cdba53f6a54a29e510c0f9eecf9b9ea7d9 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 19 Aug 2011 15:13:40 -0400 Subject: vm_state --> vm_states --- nova/api/ec2/cloud.py | 24 ++++++++-------- nova/compute/api.py | 46 +++++++++++++++---------------- nova/compute/manager.py | 50 +++++++++++++++++----------------- nova/compute/vm_state.py | 31 --------------------- nova/compute/vm_states.py | 31 +++++++++++++++++++++ nova/scheduler/driver.py | 4 +-- nova/tests/scheduler/test_scheduler.py | 2 +- nova/tests/test_compute.py | 8 +++--- 8 files changed, 98 insertions(+), 98 deletions(-) delete mode 100644 nova/compute/vm_state.py create mode 100644 nova/compute/vm_states.py diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9aebf92e3..4b69cc272 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -47,6 +47,7 @@ from nova import utils from nova import volume from nova.api.ec2 import ec2utils from nova.compute import instance_types +from nova.compute import vm_states from nova.image import s3 @@ -1039,11 +1040,10 @@ class CloudController(object): def _format_attr_instance_initiated_shutdown_behavior(instance, result): - state_description = instance['state_description'] - state_to_value = {'stopping': 'stop', - 'stopped': 'stop', - 'terminating': 'terminate'} - value = state_to_value.get(state_description) + vm_state = instance['vm_state'] + state_to_value = {vm_states.STOP: 'stop', + vm_states.DELETE: 'terminate'} + value = state_to_value.get(vm_state) if value: result['instanceInitiatedShutdownBehavior'] = value @@ -1198,8 +1198,8 @@ class CloudController(object): self._format_kernel_id(instance, i, 'kernelId') self._format_ramdisk_id(instance, i, 'ramdiskId') i['instanceState'] = { - 'code': instance['state'], - 'name': instance['state_description']} + 'code': instance['power_state'], + 'name': instance['vm_state']} #FIXME fixed_addr = None floating_addr = None if instance['fixed_ips']: @@ -1618,22 +1618,22 @@ class CloudController(object): # stop the instance if necessary restart_instance = False if not no_reboot: - state_description = instance['state_description'] + vm_state = instance['vm_state'] # if the instance is in subtle state, refuse to proceed. - if state_description not in ('running', 'stopping', 'stopped'): + if vm_state not in (vm_states.ACTIVE, vm_states.STOP): raise exception.InstanceNotRunning(instance_id=ec2_instance_id) - if state_description == 'running': + if vm_state == vm_states.ACTIVE: restart_instance = True self.compute_api.stop(context, instance_id=instance_id) # wait instance for really stopped start_time = time.time() - while state_description != 'stopped': + while vm_state != vm_states.STOP: time.sleep(1) instance = self.compute_api.get(context, instance_id) - state_description = instance['state_description'] + vm_state = instance['vm_state'] # NOTE(yamahata): timeout and error. 1 hour for now for safety. # Is it too short/long? # Or is there any better way? diff --git a/nova/compute/api.py b/nova/compute/api.py index 77397f90e..8634ad27a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -36,7 +36,7 @@ from nova import utils from nova import volume from nova.compute import instance_types from nova.compute import power_state -from nova.compute import vm_state +from nova.compute import vm_states from nova.compute.utils import terminate_volumes from nova.scheduler import api as scheduler_api from nova.db import base @@ -76,10 +76,10 @@ def generate_default_hostname(instance): def _is_able_to_shutdown(instance, instance_id): states = { - vm_state.DELETE: "Instance %s is already being terminated", - vm_state.MIGRATE: "Instance %s is being migrated", - vm_state.RESIZE: "Instance %s is being resized", - vm_state.STOP: "Instance %s is being stopped", + vm_states.DELETE: "Instance %s is already being terminated", + vm_states.MIGRATE: "Instance %s is being migrated", + vm_states.RESIZE: "Instance %s is being resized", + vm_states.STOP: "Instance %s is being stopped", } msg = states.get(instance['vm_state']) if msg: @@ -236,7 +236,7 @@ class API(base.Base): 'kernel_id': kernel_id or '', 'ramdisk_id': ramdisk_id or '', 'power_state': power_state.NOSTATE, - 'vm_state': vm_state.BUILD, + 'vm_state': vm_states.BUILD, 'user_id': context.user_id, 'project_id': context.project_id, 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), @@ -653,7 +653,7 @@ class API(base.Base): self.update(context, instance_id, - vm_state=vm_state.DELETE, + vm_state=vm_states.DELETE, terminated_at=utils.utcnow()) host = instance['host'] @@ -675,7 +675,7 @@ class API(base.Base): self.update(context, instance_id, - vm_state=vm_state.STOP, + vm_state=vm_states.STOP, terminated_at=utils.utcnow()) host = instance['host'] @@ -689,12 +689,12 @@ class API(base.Base): instance = self._get_instance(context, instance_id, 'starting') vm_state = instance["vm_state"] - if vm_state != vm_state.STOP: + if vm_state != vm_states.STOP: LOG.warning(_("Instance %(instance_id)s is not " "stopped. (%(vm_state)s)") % locals()) return - self.update(context, instance_id, vm_state=vm_state.ACTIVE) + self.update(context, instance_id, vm_state=vm_states.ACTIVE) # TODO(yamahata): injected_files isn't supported right now. # It is used only for osapi. not for ec2 api. @@ -923,7 +923,7 @@ class API(base.Base): @scheduler_api.reroute_compute("reboot") def reboot(self, context, instance_id): """Reboot the given instance.""" - self.update(context, instance_id, vm_state=vm_state.REBOOT) + self.update(context, instance_id, vm_state=vm_states.REBOOT) self._cast_compute_message('reboot_instance', context, instance_id) @scheduler_api.reroute_compute("rebuild") @@ -932,8 +932,8 @@ class API(base.Base): """Rebuild the given instance with the provided metadata.""" instance = db.api.instance_get(context, instance_id) invalid_rebuild_states = [ - vm_state.BUILD, - vm_state.REBUILD, + vm_states.BUILD, + vm_states.REBUILD, ] if instance["vm_state"] in invalid_rebuild_states: @@ -956,7 +956,7 @@ class API(base.Base): "injected_files": files_to_inject, } - self.update(context, instance_id, vm_state=vm_state.REBUILD) + self.update(context, instance_id, vm_state=vm_states.REBUILD) self._cast_compute_message('rebuild_instance', context, @@ -975,7 +975,7 @@ class API(base.Base): raise exception.MigrationNotFoundByStatus(instance_id=instance_id, status='finished') - self.update(context, instance_id, vm_state=vm_state.ACTIVE) + self.update(context, instance_id, vm_state=vm_states.ACTIVE) params = {'migration_id': migration_ref['id']} self._cast_compute_message('revert_resize', context, @@ -998,7 +998,7 @@ class API(base.Base): raise exception.MigrationNotFoundByStatus(instance_id=instance_id, status='finished') - self.update(context, instance_id, vm_state=vm_state.ACTIVE) + self.update(context, instance_id, vm_state=vm_states.ACTIVE) params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, @@ -1045,7 +1045,7 @@ class API(base.Base): if (current_memory_mb == new_memory_mb) and flavor_id: raise exception.CannotResizeToSameSize() - self.update(context, instance_id, vm_state=vm_state.RESIZE) + self.update(context, instance_id, vm_state=vm_states.RESIZE) instance_ref = self._get_instance(context, instance_id, 'resize') self._cast_scheduler_message(context, @@ -1080,13 +1080,13 @@ class API(base.Base): @scheduler_api.reroute_compute("pause") def pause(self, context, instance_id): """Pause the given instance.""" - self.update(context, instance_id, vm_state=vm_state.PAUSE) + self.update(context, instance_id, vm_state=vm_states.PAUSE) self._cast_compute_message('pause_instance', context, instance_id) @scheduler_api.reroute_compute("unpause") def unpause(self, context, instance_id): """Unpause the given instance.""" - self.update(context, instance_id, vm_state=vm_state.ACTIVE) + self.update(context, instance_id, vm_state=vm_states.ACTIVE) self._cast_compute_message('unpause_instance', context, instance_id) def _call_compute_message_for_host(self, action, context, host, params): @@ -1119,25 +1119,25 @@ class API(base.Base): @scheduler_api.reroute_compute("suspend") def suspend(self, context, instance_id): """Suspend the given instance.""" - self.update(context, instance_id, vm_state=vm_state.SUSPEND) + self.update(context, instance_id, vm_state=vm_states.SUSPEND) self._cast_compute_message('suspend_instance', context, instance_id) @scheduler_api.reroute_compute("resume") def resume(self, context, instance_id): """Resume the given instance.""" - self.update(context, instance_id, vm_state=vm_state.ACTIVE) + self.update(context, instance_id, vm_state=vm_states.ACTIVE) self._cast_compute_message('resume_instance', context, instance_id) @scheduler_api.reroute_compute("rescue") def rescue(self, context, instance_id): """Rescue the given instance.""" - self.update(context, instance_id, vm_state=vm_state.RESCUE) + self.update(context, instance_id, vm_state=vm_states.RESCUE) self._cast_compute_message('rescue_instance', context, instance_id) @scheduler_api.reroute_compute("unrescue") def unrescue(self, context, instance_id): """Unrescue the given instance.""" - self.update(context, instance_id, vm_state=vm_state.ACTIVE) + self.update(context, instance_id, vm_state=vm_states.ACTIVE) self._cast_compute_message('unrescue_instance', context, instance_id) @scheduler_api.reroute_compute("set_admin_password") diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0d7f3ad71..4be5bdd69 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -57,7 +57,7 @@ from nova import utils from nova import volume from nova.compute import power_state from nova.compute import task_state -from nova.compute import vm_state +from nova.compute import vm_states from nova.notifier import api as notifier from nova.compute.utils import terminate_volumes from nova.virt import driver @@ -372,7 +372,7 @@ class ComputeManager(manager.SchedulerDependentManager): updates = {} updates['host'] = self.host updates['launched_on'] = self.host - updates['vm_state'] = vm_state.BUILD + updates['vm_state'] = vm_states.BUILD updates['task_state'] = task_state.NETWORKING instance = self.db.instance_update(context, instance_id, updates) instance['injected_files'] = kwargs.get('injected_files', []) @@ -397,7 +397,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - vm_state=vm_state.BUILD, + vm_state=vm_states.BUILD, task_state=task_state.BLOCK_DEVICE_MAPPING) (swap, ephemerals, @@ -428,7 +428,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=None, launched_at=utils.utcnow()) @@ -523,7 +523,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_state.REBUILD, + vm_state=vm_states.REBUILD, task_state=task_state.REBUILDING) network_info = self._get_instance_nw_info(context, instance_ref) @@ -531,7 +531,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - vm_state=vm_state.REBUILD, + vm_state=vm_states.REBUILD, task_state=task_state.BLOCK_DEVICE_MAPPING) bd_mapping = self._setup_block_device_mapping(context, instance_id) @@ -544,7 +544,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - vm_state=vm_state.REBUILD, + vm_state=vm_states.REBUILD, task_state=task_state.SPAWN) self.driver.spawn(context, instance_ref, network_info, bd_mapping) @@ -553,7 +553,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=None, image_ref=image_ref, launched_at=utils.utcnow()) @@ -577,7 +577,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_state.REBOOT, + vm_state=vm_states.REBOOT, task_state=task_state.REBOOTING) if instance_ref['power_state'] != power_state.RUNNING: @@ -595,7 +595,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=None) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @@ -622,7 +622,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=image_type) LOG.audit(_('instance %s: snapshotting'), instance_id, @@ -787,7 +787,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - vm_state=vm_state.RESCUE, + vm_state=vm_states.RESCUE, task_state=task_state.RESCUING) instance_ref = self.db.instance_get(context, instance_id) @@ -799,7 +799,7 @@ class ComputeManager(manager.SchedulerDependentManager): current_power_state = self._get_power_state(context, instance_ref) self._instance_update(context, instance_id, - vm_state=vm_state.RESCUE, + vm_state=vm_states.RESCUE, task_state=task_state.RESCUED, power_state=current_power_state) @@ -812,7 +812,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=task_state.UNRESCUING) instance_ref = self.db.instance_get(context, instance_id) @@ -824,7 +824,7 @@ class ComputeManager(manager.SchedulerDependentManager): current_power_state = self._get_power_state(context, instance_ref) self._instance_update(context, instance_id, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=None, power_state=current_power_state) @@ -1048,7 +1048,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - vm_state=vm_state.PAUSE, + vm_state=vm_states.PAUSE, task_state=task_state.PAUSING) instance_ref = self.db.instance_get(context, instance_id) @@ -1058,7 +1058,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_state.PAUSE, + vm_state=vm_states.PAUSE, task_state=None) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @@ -1070,7 +1070,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=task_state.UNPAUSING) instance_ref = self.db.instance_get(context, instance_id) @@ -1080,7 +1080,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=None) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @@ -1111,7 +1111,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - vm_state=vm_state.SUSPEND, + vm_state=vm_states.SUSPEND, task_state=task_state.SUSPENDING) instance_ref = self.db.instance_get(context, instance_id) @@ -1121,7 +1121,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_state.SUSPEND, + vm_state=vm_states.SUSPEND, task_state=None) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @@ -1133,7 +1133,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=task_state.RESUMING) instance_ref = self.db.instance_get(context, instance_id) @@ -1143,7 +1143,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=None) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @@ -1560,7 +1560,7 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref["id"], host=dest, power_state=current_power_state, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=None) # Restore volume state @@ -1611,7 +1611,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_ref['id'], host=host, - vm_state=vm_state.ACTIVE, + vm_state=vm_states.ACTIVE, task_state=None) for volume_ref in instance_ref['volumes']: diff --git a/nova/compute/vm_state.py b/nova/compute/vm_state.py deleted file mode 100644 index a1bca6ef4..000000000 --- a/nova/compute/vm_state.py +++ /dev/null @@ -1,31 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 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. - -"""Possible vm states for instances""" - -ACTIVE='active' -BUILD='build' -REBUILD='rebuild' -REBOOT='reboot' -DELETE='delete' -STOP='stop' -MIGRATE='migrate' -RESIZE='resize' -VERIFY_RESIZE='verify_resize' -PAUSE='pause' -SUSPEND='suspend' -RESCUE='rescue' diff --git a/nova/compute/vm_states.py b/nova/compute/vm_states.py new file mode 100644 index 000000000..a1bca6ef4 --- /dev/null +++ b/nova/compute/vm_states.py @@ -0,0 +1,31 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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. + +"""Possible vm states for instances""" + +ACTIVE='active' +BUILD='build' +REBUILD='rebuild' +REBOOT='reboot' +DELETE='delete' +STOP='stop' +MIGRATE='migrate' +RESIZE='resize' +VERIFY_RESIZE='verify_resize' +PAUSE='pause' +SUSPEND='suspend' +RESCUE='rescue' diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index b788b996f..8f9be879b 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -31,7 +31,7 @@ from nova import rpc from nova import utils from nova.compute import power_state from nova.compute import task_state -from nova.compute import vm_state +from nova.compute import vm_states from nova.api.ec2 import ec2utils @@ -106,7 +106,7 @@ class Scheduler(object): dest, block_migration) # Changing instance_state. - values = {"vm_state": vm_state.MIGRATE} + values = {"vm_state": vm_states.MIGRATE} db.instance_update(context, instance_id, values) # Changing volume state diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index 1b5e131c9..629019eaf 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -40,7 +40,7 @@ from nova.scheduler import driver from nova.scheduler import manager from nova.scheduler import multi from nova.compute import power_state -from nova.compute import vm_state +from nova.compute import vm_states FLAGS = flags.FLAGS diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 188398924..ca1bbc69f 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -23,7 +23,7 @@ from nova import compute from nova.compute import instance_types from nova.compute import manager as compute_manager from nova.compute import power_state -from nova.compute import vm_state +from nova.compute import vm_states from nova import context from nova import db from nova.db.sqlalchemy import models @@ -748,7 +748,7 @@ class ComputeTestCase(test.TestCase): 'block_migration': False, 'disk': None}}).\ AndRaise(rpc.RemoteError('', '', '')) - dbmock.instance_update(c, i_ref['id'], {'vm_state': vm_state.ACTIVE, + dbmock.instance_update(c, i_ref['id'], {'vm_state': vm_states.ACTIVE, 'task_state': None, 'host': i_ref['host']}) for v in i_ref['volumes']: @@ -780,7 +780,7 @@ class ComputeTestCase(test.TestCase): 'block_migration': False, 'disk': None}}).\ AndRaise(rpc.RemoteError('', '', '')) - dbmock.instance_update(c, i_ref['id'], {'vm_state': vm_state.ACTIVE, + dbmock.instance_update(c, i_ref['id'], {'vm_state': vm_states.ACTIVE, 'task_state': None, 'host': i_ref['host']}) @@ -826,7 +826,7 @@ class ComputeTestCase(test.TestCase): c = context.get_admin_context() instance_id = self._create_instance() i_ref = db.instance_get(c, instance_id) - db.instance_update(c, i_ref['id'], {'vm_state': vm_state.MIGRATE, + db.instance_update(c, i_ref['id'], {'vm_state': vm_states.MIGRATE, 'power_state': power_state.PAUSED}) v_ref = db.volume_create(c, {'size': 1, 'instance_id': instance_id}) fix_addr = db.fixed_ip_create(c, {'address': '1.1.1.1', -- cgit From f8ec1a5b9002f4a4cda5d7156c6006aac0035c33 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 16:12:33 -0500 Subject: initial committ --- nova/tests/test_compute.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 4f5d36f14..925ac733b 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -35,6 +35,7 @@ from nova import rpc from nova import test from nova import utils from nova.notifier import test_notifier +from nova.tests import fake_network_info LOG = logging.getLogger('nova.tests.compute') FLAGS = flags.FLAGS @@ -133,6 +134,9 @@ class ComputeTestCase(test.TestCase): def test_create_instance_defaults_display_name(self): """Verify that an instance cannot be created without a display_name.""" + import pretty_print as pp + pp(fake_get_instance_nw_info(self.stubs, 1, 2)) + self.assertEqual(True, False) cases = [dict(), dict(display_name=None)] for instance in cases: ref = self.compute_api.create(self.context, -- cgit From 773f64af47e38f10b20399fcfaa43cc52eb1d2e6 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 16:41:02 -0500 Subject: added fake network info --- nova/tests/fake_network_info.py | 107 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 nova/tests/fake_network_info.py diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py new file mode 100644 index 000000000..f5bc0368c --- /dev/null +++ b/nova/tests/fake_network_info.py @@ -0,0 +1,107 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Rackspace +# 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 db +from nova import test +from nova.network import manager as network_manager + + +HOST = "testhost" + + +class FakeModel(dict): + """Represent a model from the db""" + def __init__(self, *args, **kwargs): + self.update(kwargs) + + def __getattr__(self, name): + return self[name] + + +def fake_network(n): + return {'id': n, + 'label': 'test%d' % n, + 'injected': False, + 'multi_host': False, + 'cidr': '192.168.%d.0/24' % n, + 'cidr_v6': '2001:db8:0:%x::/64' % n, + 'gateway_v6': '2001:db8:0:%x::1' % n, + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': 'fa%d' % n, + 'bridge_interface': 'fake_br%d' % n, + 'gateway': '192.168.%d.1' % n, + 'broadcast': '192.168.%d.255' % n, + 'dns1': '192.168.%d.3' % n, + 'dns2': '192.168.%d.4' % n, + 'vlan': None, + 'host': None, + 'project_id': 'fake_project', + 'vpn_public_address': '192.168.%d.2' % n} + + +def fixed_ips(num_networks, num_ips): + for network in xrange(num_networks): + for ip in xrange(num_ips): + yield {'id': network * ip, + 'network_id': network, + 'address': '192.168.%d.100' % network, + 'instance_id': 0, + 'allocated': False, + # and since network_id and vif_id happen to be equivalent + 'virtual_interface_id': network, + 'floating_ips': [FakeModel(**floating_ip)]} + + +flavor = {'id': 0, + 'rxtx_cap': 3} + + +floating_ip = {'id': 0, + 'address': '10.10.10.10', + 'fixed_ip_id': 0, + 'project_id': None, + 'auto_assigned': False} + + +def vifs(n): + for x in xrange(n): + yield {'id': x, + 'address': 'DE:AD:BE:EF:00:%2x' % x, + 'uuid': '00000000-0000-0000-0000-00000000000000%2d' % x, + 'network_id': x, + 'network': FakeModel(**fake_network(x)), + 'instance_id': 0} + + +def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): + network = network_manager.FlatManager(host=HOST) + + def fixed_ips_fake(*args, **kwargs): + return fixed_ips() + + def virtual_interfaces_fake(*args, **kwargs): + return [vif for vif in vifs(n)] + + def instance_type_fake(*args, **kwargs): + return flavor + + stubs.Set(db, 'fixed_ip_get_by_instance', fixed_ips_fake) + stubs.Set(db, 'virtual_interface_get_by_instance', virtual_interface_fake) + stubs.Set(db, 'instance_type_get', instance_type_fake) + + nw_info = self.network.get_instance_nw_info(None, 0, 0, None) -- cgit From 608a6c3e719fffe9af6f8f7cc6d18824d0c28c36 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 16:42:52 -0500 Subject: typo --- nova/tests/fake_network_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index f5bc0368c..991599987 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -101,7 +101,7 @@ def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): return flavor stubs.Set(db, 'fixed_ip_get_by_instance', fixed_ips_fake) - stubs.Set(db, 'virtual_interface_get_by_instance', virtual_interface_fake) + stubs.Set(db, 'virtual_interface_get_by_instance', virtual_interfaces_fake) stubs.Set(db, 'instance_type_get', instance_type_fake) nw_info = self.network.get_instance_nw_info(None, 0, 0, None) -- cgit From c4d73b638dee9dc4c1b2883affc0c00cdfc5ecb6 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 16:44:23 -0500 Subject: typo --- nova/tests/fake_network_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 991599987..61b35a9d2 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -104,4 +104,4 @@ def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): stubs.Set(db, 'virtual_interface_get_by_instance', virtual_interfaces_fake) stubs.Set(db, 'instance_type_get', instance_type_fake) - nw_info = self.network.get_instance_nw_info(None, 0, 0, None) + nw_info = network_manager.get_instance_nw_info(None, 0, 0, None) -- cgit From ca83ca57646ba76908e9b5e600208fe9afde78a4 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 16:47:10 -0500 Subject: typo --- nova/tests/fake_network_info.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 61b35a9d2..1fb3584dd 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -18,6 +18,7 @@ from nova import db from nova import test from nova.network import manager as network_manager +from network_ HOST = "testhost" @@ -104,4 +105,4 @@ def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): stubs.Set(db, 'virtual_interface_get_by_instance', virtual_interfaces_fake) stubs.Set(db, 'instance_type_get', instance_type_fake) - nw_info = network_manager.get_instance_nw_info(None, 0, 0, None) + nw_info = network.get_instance_nw_info(None, 0, 0, None) -- cgit From 23e163199e3b3208d7ba16049b7a93cbd59a8eaf Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 16:48:01 -0500 Subject: typo --- nova/tests/fake_network_info.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 1fb3584dd..87f6b9c56 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -18,7 +18,6 @@ from nova import db from nova import test from nova.network import manager as network_manager -from network_ HOST = "testhost" -- cgit From 7e6451b722b1e77ccab702482d5d5ad516056825 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 16:58:39 -0500 Subject: typo --- nova/tests/fake_network_info.py | 1 + nova/tests/test_compute.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 87f6b9c56..3360675d6 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -90,6 +90,7 @@ def vifs(n): def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): network = network_manager.FlatManager(host=HOST) + network.db = db def fixed_ips_fake(*args, **kwargs): return fixed_ips() diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 925ac733b..dd65d81cc 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -134,8 +134,7 @@ class ComputeTestCase(test.TestCase): def test_create_instance_defaults_display_name(self): """Verify that an instance cannot be created without a display_name.""" - import pretty_print as pp - pp(fake_get_instance_nw_info(self.stubs, 1, 2)) + print fake_network_info.fake_get_instance_nw_info(self.stubs, 1, 2) self.assertEqual(True, False) cases = [dict(), dict(display_name=None)] for instance in cases: -- cgit From 429d28df87e887ef297453f3dc186c1a99ba0a7a Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 17:00:13 -0500 Subject: typo --- nova/tests/fake_network_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 3360675d6..d920ab1a2 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -93,7 +93,7 @@ def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): network.db = db def fixed_ips_fake(*args, **kwargs): - return fixed_ips() + return fixed_ips(n, ips_per_vif) def virtual_interfaces_fake(*args, **kwargs): return [vif for vif in vifs(n)] -- cgit From 6ab9559b5f1f398cdaac4eca7bfcfcda859d8bc8 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 17:02:21 -0500 Subject: fixed formatting string --- nova/tests/fake_network_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index d920ab1a2..e0ff71c2c 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -81,7 +81,7 @@ floating_ip = {'id': 0, def vifs(n): for x in xrange(n): yield {'id': x, - 'address': 'DE:AD:BE:EF:00:%2x' % x, + 'address': 'DE:AD:BE:EF:00:%02x' % x, 'uuid': '00000000-0000-0000-0000-00000000000000%2d' % x, 'network_id': x, 'network': FakeModel(**fake_network(x)), -- cgit From 95851baea252d801cbb704e869f78162ea995ceb Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 17:05:29 -0500 Subject: added return --- nova/tests/fake_network_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index e0ff71c2c..53785635c 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -105,4 +105,4 @@ def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): stubs.Set(db, 'virtual_interface_get_by_instance', virtual_interfaces_fake) stubs.Set(db, 'instance_type_get', instance_type_fake) - nw_info = network.get_instance_nw_info(None, 0, 0, None) + return network.get_instance_nw_info(None, 0, 0, None) -- cgit From 44af602dbc6f02de44e2b844a8d53a87680b7a94 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 17:18:57 -0500 Subject: who cares --- nova/tests/test_compute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index dd65d81cc..fcb86e322 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -134,7 +134,7 @@ class ComputeTestCase(test.TestCase): def test_create_instance_defaults_display_name(self): """Verify that an instance cannot be created without a display_name.""" - print fake_network_info.fake_get_instance_nw_info(self.stubs, 1, 2) + print fake_network_info.fake_get_instance_nw_info(self.stubs, 2, 1) self.assertEqual(True, False) cases = [dict(), dict(display_name=None)] for instance in cases: -- cgit From c4e77b67a74cb0828bb9a7ccbedcaa1baeb6188d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 19 Aug 2011 18:34:34 -0400 Subject: Lots of modifications surrounding the OSAPI to remove any mention of dealing with power states and exclusively using vm_states and task_state modules. Currently there are still a number of tests failing, but this is a stopping place for today. --- nova/api/openstack/common.py | 52 ++++++------ nova/api/openstack/servers.py | 12 ++- nova/api/openstack/views/servers.py | 5 +- nova/compute/vm_states.py | 2 + nova/tests/api/openstack/test_server_actions.py | 28 ++++--- nova/tests/api/openstack/test_servers.py | 102 +++++++++++++++--------- nova/tests/integrated/test_servers.py | 27 ++++--- nova/tests/vmwareapi/db_fakes.py | 5 +- 8 files changed, 138 insertions(+), 95 deletions(-) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index d9eb832f2..eae0fd916 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -27,7 +27,8 @@ from nova import flags from nova import log as logging from nova import quota from nova.api.openstack import wsgi -from nova.compute import power_state as compute_power_state +from nova.compute import vm_states +from nova.compute import task_state LOG = logging.getLogger('nova.api.openstack.common') @@ -38,36 +39,35 @@ XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1' -_STATUS_MAP = { - None: 'BUILD', - compute_power_state.NOSTATE: 'BUILD', - compute_power_state.RUNNING: 'ACTIVE', - compute_power_state.BLOCKED: 'ACTIVE', - compute_power_state.SUSPENDED: 'SUSPENDED', - compute_power_state.PAUSED: 'PAUSED', - compute_power_state.SHUTDOWN: 'SHUTDOWN', - compute_power_state.SHUTOFF: 'SHUTOFF', - compute_power_state.CRASHED: 'ERROR', - compute_power_state.FAILED: 'ERROR', - compute_power_state.BUILDING: 'BUILD', +_STATE_MAP = { + vm_states.ACTIVE: 'ACTIVE', + vm_states.BUILD: 'BUILD', + vm_states.REBUILD: 'REBUILD', + vm_states.REBOOT: 'REBOOT', + vm_states.HARD_REBOOT: 'HARD_REBOOT', + vm_states.STOP: 'STOPPED', + vm_states.MIGRATE: 'MIGRATING', + vm_states.RESIZE: 'RESIZE', + vm_states.VERIFY_RESIZE: 'VERIFY_RESIZE', + vm_states.PAUSE: 'PAUSED', + vm_states.SUSPEND: 'SUSPENDED', + vm_states.RESCUE: 'RESCUE', + vm_states.ERROR: 'ERROR', } -def status_from_power_state(power_state): - """Map the power state to the server status string""" - return _STATUS_MAP[power_state] +def status_from_state(_vm_state, _task_state=None): + """Given vm_state and task_state, return a status string.""" + if _vm_state == vm_states.ACTIVE and _task_state == task_state.PASSWORD: + return "PASSWORD" + return _STATE_MAP.get(_vm_state, "UNKNOWN_STATE") -def power_states_from_status(status): - """Map the server status string to a list of power states""" - power_states = [] - for power_state, status_map in _STATUS_MAP.iteritems(): - # Skip the 'None' state - if power_state is None: - continue - if status.lower() == status_map.lower(): - power_states.append(power_state) - return power_states +def vm_state_from_status(status): + """Map the server status string to a vm state.""" + for state, status_string in _STATE_MAP.iteritems(): + if status.lower() == status_string.lower(): + return state def get_pagination_params(request): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 41e63ec3c..0a451caee 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -95,17 +95,15 @@ class Controller(object): search_opts['recurse_zones'] = utils.bool_from_str( search_opts.get('recurse_zones', False)) - # If search by 'status', we need to convert it to 'state' - # If the status is unknown, bail. - # Leave 'state' in search_opts so compute can pass it on to - # child zones.. + # If search by 'status', we need to convert it to 'vm_state' + # to pass on to child zones. if 'status' in search_opts: status = search_opts['status'] - search_opts['state'] = common.power_states_from_status(status) - if len(search_opts['state']) == 0: + state = common.vm_state_from_status(status) + if state is None: reason = _('Invalid server status: %(status)s') % locals() - LOG.error(reason) raise exception.InvalidInput(reason=reason) + search_opts['vm_state'] = state # By default, compute's get_all() will return deleted instances. # If an admin hasn't specified a 'deleted' search option, we need diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index edc328129..e9a932b0e 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -27,6 +27,7 @@ from nova.api.openstack.views import addresses as addresses_view from nova.api.openstack.views import flavors as flavors_view from nova.api.openstack.views import images as images_view from nova import utils +from nova.compute import vm_states class ViewBuilder(object): @@ -60,11 +61,13 @@ class ViewBuilder(object): def _build_detail(self, inst): """Returns a detailed model of a server.""" + vm_state = inst.get('vm_state', vm_states.BUILD) + task_state = inst.get('task_state') inst_dict = { 'id': inst['id'], 'name': inst['display_name'], - 'status': common.status_from_power_state(inst.get('state'))} + 'status': common.status_from_state(vm_state, task_state)} ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() diff --git a/nova/compute/vm_states.py b/nova/compute/vm_states.py index a1bca6ef4..d3d168001 100644 --- a/nova/compute/vm_states.py +++ b/nova/compute/vm_states.py @@ -21,6 +21,7 @@ ACTIVE='active' BUILD='build' REBUILD='rebuild' REBOOT='reboot' +HARD_REBOOT='hard_reboot' DELETE='delete' STOP='stop' MIGRATE='migrate' @@ -29,3 +30,4 @@ VERIFY_RESIZE='verify_resize' PAUSE='pause' SUSPEND='suspend' RESCUE='rescue' +ERROR='error' diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 80a27e30f..6f8be0f47 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -12,7 +12,8 @@ from nova import utils from nova import flags from nova.api.openstack import create_instance_helper from nova.compute import instance_types -from nova.compute import power_state +from nova.compute import vm_states +from nova.compute import task_state import nova.db.api from nova import test from nova.tests.api.openstack import common @@ -30,17 +31,18 @@ def instance_update(context, instance_id, kwargs): return _get_instance() -def return_server_with_power_state(power_state): +def return_server_with_state(vm_state, task_state=None): def _return_server(context, id): instance = _get_instance() - instance['state'] = power_state + instance['vm_state'] = vm_state + instance['task_state'] = task_state return instance return _return_server -def return_server_with_uuid_and_power_state(power_state): +def return_server_with_uuid_and_state(vm_state, task_state=None): def _return_server(context, id): - return return_server_with_power_state(power_state) + return return_server_with_state(vm_state, task_state) return _return_server @@ -68,8 +70,8 @@ def _get_instance(): "launch_index": 0, "key_name": "", "key_data": "", - "state": 0, - "state_description": "", + "vm_state": vm_states.ACTIVE, + "task_state": None, "memory_mb": 0, "vcpus": 0, "local_gb": 0, @@ -164,11 +166,11 @@ class ServerActionsTest(test.TestCase): }, } - state = power_state.BUILDING - new_return_server = return_server_with_power_state(state) + state = vm_states.BUILD + new_return_server = return_server_with_state(state) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) self.stubs.Set(nova.db, 'instance_get_by_uuid', - return_server_with_uuid_and_power_state(state)) + return_server_with_uuid_and_state(state)) req = webob.Request.blank('/v1.0/servers/1/action') req.method = 'POST' @@ -627,11 +629,11 @@ class ServerActionsTestV11(test.TestCase): }, } - state = power_state.BUILDING - new_return_server = return_server_with_power_state(state) + state = vm_states.BUILD + new_return_server = return_server_with_state(state) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) self.stubs.Set(nova.db, 'instance_get_by_uuid', - return_server_with_uuid_and_power_state(state)) + return_server_with_uuid_and_state(state)) req = webob.Request.blank('/v1.1/servers/1/action') req.method = 'POST' diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 437620854..b500c514e 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -34,7 +34,8 @@ from nova.api.openstack import servers from nova.api.openstack import wsgi import nova.compute.api from nova.compute import instance_types -from nova.compute import power_state +from nova.compute import task_state +from nova.compute import vm_states import nova.db.api import nova.scheduler.api from nova.db.sqlalchemy.models import Instance @@ -86,15 +87,18 @@ def return_server_with_addresses(private, public): return _return_server -def return_server_with_power_state(power_state): +def return_server_with_state(vm_state, task_state=None): def _return_server(context, id): - return stub_instance(id, power_state=power_state) + return stub_instance(id, vm_state=vm_state, task_state=task_state) return _return_server -def return_server_with_uuid_and_power_state(power_state): +def return_server_with_uuid_and_state(vm_state, task_state): def _return_server(context, id): - return stub_instance(id, uuid=FAKE_UUID, power_state=power_state) + return stub_instance(id, + uuid=FAKE_UUID, + vm_state=vm_state, + task_state=task_state) return _return_server @@ -143,7 +147,8 @@ def instance_addresses(context, instance_id): def stub_instance(id, user_id='fake', project_id='fake', private_address=None, - public_addresses=None, host=None, power_state=0, + public_addresses=None, host=None, + vm_state=None, task_state=None, reservation_id="", uuid=FAKE_UUID, image_ref="10", flavor_id="1", interfaces=None, name=None): metadata = [] @@ -178,8 +183,8 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, "launch_index": 0, "key_name": "", "key_data": "", - "state": power_state, - "state_description": "", + "vm_state": vm_state or vm_states.BUILD, + "task_state": task_state, "memory_mb": 0, "vcpus": 0, "local_gb": 0, @@ -481,7 +486,7 @@ class ServersTest(test.TestCase): }, ] new_return_server = return_server_with_attributes( - interfaces=interfaces, power_state=1) + interfaces=interfaces, vm_state=vm_states.ACTIVE) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) req = webob.Request.blank('/v1.1/servers/1') @@ -571,8 +576,8 @@ class ServersTest(test.TestCase): }, ] new_return_server = return_server_with_attributes( - interfaces=interfaces, power_state=1, image_ref=image_ref, - flavor_id=flavor_id) + interfaces=interfaces, vm_state=vm_states.ACTIVE, + image_ref=image_ref, flavor_id=flavor_id) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) req = webob.Request.blank('/v1.1/servers/1') @@ -1169,9 +1174,8 @@ class ServersTest(test.TestCase): def test_get_servers_allows_status_v1_1(self): def fake_get_all(compute_self, context, search_opts=None): self.assertNotEqual(search_opts, None) - self.assertTrue('state' in search_opts) - self.assertEqual(set(search_opts['state']), - set([power_state.RUNNING, power_state.BLOCKED])) + self.assertTrue('vm_state' in search_opts) + self.assertEqual(search_opts['vm_state'], vm_states.ACTIVE) return [stub_instance(100)] self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) @@ -1188,13 +1192,9 @@ class ServersTest(test.TestCase): def test_get_servers_invalid_status_v1_1(self): """Test getting servers by invalid status""" - self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/servers?status=running') res = req.get_response(fakes.wsgi_app()) - # The following assert will fail if either of the asserts in - # fake_get_all() fail self.assertEqual(res.status_int, 400) self.assertTrue(res.body.find('Invalid server status') > -1) @@ -1632,6 +1632,7 @@ class ServersTest(test.TestCase): server = json.loads(res.body)['server'] self.assertEqual(16, len(server['adminPass'])) self.assertEqual(1, server['id']) + self.assertEqual("BUILD", server["status"]) self.assertEqual(0, server['progress']) self.assertEqual('server_test', server['name']) self.assertEqual(expected_flavor, server['flavor']) @@ -2165,23 +2166,52 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 204) self.assertEqual(self.server_delete_called, True) - 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') - 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'], 'SHUTDOWN') - def test_shutoff_status(self): - new_server = return_server_with_power_state(power_state.SHUTOFF) +class TestServerStatus(test.TestCase): + + def _get_with_state(self, vm_state, task_state=None): + new_server = return_server_with_state(vm_state, task_state) 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) - res_dict = json.loads(res.body) - self.assertEqual(res_dict['server']['status'], 'SHUTOFF') + request = webob.Request.blank('/v1.0/servers/1') + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(response.status_int, 200) + return json.loads(response.body) + + def test_active(self): + response = self._get_with_state(vm_states.ACTIVE) + self.assertEqual(response['server']['status'], 'ACTIVE') + + def test_reboot(self): + response = self._get_with_state(vm_states.REBOOT) + self.assertEqual(response['server']['status'], 'REBOOT') + + def test_hard_reboot(self): + response = self._get_with_state(vm_states.HARD_REBOOT) + self.assertEqual(response['server']['status'], 'HARD_REBOOT') + + def test_rebuild(self): + response = self._get_with_state(vm_states.REBUILD) + self.assertEqual(response['server']['status'], 'REBUILD') + + def test_rebuild_error(self): + response = self._get_with_state(vm_states.ERROR) + self.assertEqual(response['server']['status'], 'ERROR') + + def test_resize(self): + response = self._get_with_state(vm_states.RESIZE) + self.assertEqual(response['server']['status'], 'RESIZE') + + def test_verify_resize(self): + response = self._get_with_state(vm_states.VERIFY_RESIZE) + self.assertEqual(response['server']['status'], 'VERIFY_RESIZE') + + def test_password_update(self): + response = self._get_with_state(vm_states.ACTIVE, task_state.PASSWORD) + self.assertEqual(response['server']['status'], 'PASSWORD') + + def test_stopped(self): + response = self._get_with_state(vm_states.STOP) + self.assertEqual(response['server']['status'], 'STOPPED') class TestServerCreateRequestXMLDeserializerV10(unittest.TestCase): @@ -3018,8 +3048,8 @@ class ServersViewBuilderV11Test(test.TestCase): "launch_index": 0, "key_name": "", "key_data": "", - "state": 0, - "state_description": "", + "vm_state": vm_states.BUILD, + "task_state": None, "memory_mb": 0, "vcpus": 0, "local_gb": 0, @@ -3132,7 +3162,7 @@ class ServersViewBuilderV11Test(test.TestCase): def test_build_server_detail_active_status(self): #set the power state of the instance to running - self.instance['state'] = 1 + self.instance['vm_state'] = vm_states.ACTIVE image_bookmark = "http://localhost/images/5" flavor_bookmark = "http://localhost/flavors/1" expected_server = { diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 725f6d529..deeb3d008 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -28,6 +28,17 @@ LOG = logging.getLogger('nova.tests.integrated') class ServersTest(integrated_helpers._IntegratedTestBase): + def _wait_for_creation(self, server): + retries = 0 + while server['status'] == 'BUILD': + time.sleep(1) + server = self.api.get_server(server['id']) + print server + retries = retries + 1 + if retries > 8: + break + return server + def test_get_servers(self): """Simple check that listing servers works.""" servers = self.api.get_servers() @@ -36,7 +47,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): def test_create_and_delete_server(self): """Creates and deletes a server.""" - + self.flags(stub_network=True) # Create server # Build the server data gradually, checking errors along the way @@ -91,19 +102,11 @@ class ServersTest(integrated_helpers._IntegratedTestBase): server_ids = [server['id'] for server in servers] self.assertTrue(created_server_id in server_ids) - # Wait (briefly) for creation - retries = 0 - while found_server['status'] == 'build': - LOG.debug("found server: %s" % found_server) - time.sleep(1) - found_server = self.api.get_server(created_server_id) - retries = retries + 1 - if retries > 5: - break + found_server = self._wait_for_creation(found_server) # It should be available... # TODO(justinsb): Mock doesn't yet do this... - #self.assertEqual('available', found_server['status']) + self.assertEqual('ACTIVE', found_server['status']) servers = self.api.get_servers(detail=True) for server in servers: self.assertTrue("image" in server) @@ -190,6 +193,8 @@ class ServersTest(integrated_helpers._IntegratedTestBase): self.assertTrue(created_server['id']) created_server_id = created_server['id'] + created_server = self._wait_for_creation(created_server) + # rebuild the server with metadata post = {} post['rebuild'] = { diff --git a/nova/tests/vmwareapi/db_fakes.py b/nova/tests/vmwareapi/db_fakes.py index afd672c7a..dd38420ce 100644 --- a/nova/tests/vmwareapi/db_fakes.py +++ b/nova/tests/vmwareapi/db_fakes.py @@ -23,6 +23,8 @@ import time from nova import db from nova import utils +from nova.compute import task_state +from nova.compute import vm_states def stub_out_db_instance_api(stubs): @@ -64,7 +66,8 @@ def stub_out_db_instance_api(stubs): 'image_ref': values['image_ref'], 'kernel_id': values['kernel_id'], 'ramdisk_id': values['ramdisk_id'], - 'state_description': 'scheduling', + 'vm_state': vm_states.BUILD, + 'task_state': task_state.SCHEDULE, 'user_id': values['user_id'], 'project_id': values['project_id'], 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), -- cgit From 32e5341af311faf9838bd5d039b2153549726a71 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 17:36:52 -0500 Subject: updated a maths --- nova/tests/fake_network_info.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 53785635c..e73fe0442 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -57,7 +57,7 @@ def fake_network(n): def fixed_ips(num_networks, num_ips): for network in xrange(num_networks): for ip in xrange(num_ips): - yield {'id': network * ip, + yield {'id': network * num_ips + ip, 'network_id': network, 'address': '192.168.%d.100' % network, 'instance_id': 0, @@ -82,7 +82,7 @@ def vifs(n): for x in xrange(n): yield {'id': x, 'address': 'DE:AD:BE:EF:00:%02x' % x, - 'uuid': '00000000-0000-0000-0000-00000000000000%2d' % x, + 'uuid': '00000000-0000-0000-0000-00000000000000%02d' % x, 'network_id': x, 'network': FakeModel(**fake_network(x)), 'instance_id': 0} @@ -93,7 +93,7 @@ def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): network.db = db def fixed_ips_fake(*args, **kwargs): - return fixed_ips(n, ips_per_vif) + return list(fixed_ips(n, ips_per_vif)) def virtual_interfaces_fake(*args, **kwargs): return [vif for vif in vifs(n)] -- cgit From 154f1fbcc7098aca210514ce9f458fb755b4b50b Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 17:51:14 -0500 Subject: updated a maths --- nova/tests/fake_network_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index e73fe0442..0c33898cb 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -59,7 +59,7 @@ def fixed_ips(num_networks, num_ips): for ip in xrange(num_ips): yield {'id': network * num_ips + ip, 'network_id': network, - 'address': '192.168.%d.100' % network, + 'address': '192.168.%d.1%02d' % (network, ip), 'instance_id': 0, 'allocated': False, # and since network_id and vif_id happen to be equivalent -- cgit From 4d975772a6a488a99bec616f0118dd1ce74e9403 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 19 Aug 2011 18:04:08 -0500 Subject: finished fake network info, removed testing shims --- nova/tests/test_compute.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index fcb86e322..4f5d36f14 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -35,7 +35,6 @@ from nova import rpc from nova import test from nova import utils from nova.notifier import test_notifier -from nova.tests import fake_network_info LOG = logging.getLogger('nova.tests.compute') FLAGS = flags.FLAGS @@ -134,8 +133,6 @@ class ComputeTestCase(test.TestCase): def test_create_instance_defaults_display_name(self): """Verify that an instance cannot be created without a display_name.""" - print fake_network_info.fake_get_instance_nw_info(self.stubs, 2, 1) - self.assertEqual(True, False) cases = [dict(), dict(display_name=None)] for instance in cases: ref = self.compute_api.create(self.context, -- cgit From f82d2d309a0f826522854fe331d1c53b8c6d6879 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 22 Aug 2011 09:54:33 -0400 Subject: Ec2 API updates. --- nova/api/ec2/cloud.py | 25 ++++++++++++++++++++++++- nova/tests/test_cloud.py | 8 ++++---- nova/virt/fake.py | 12 ++++++------ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4b69cc272..b7c7d2e12 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -79,6 +79,29 @@ def _gen_key(context, user_id, key_name): return {'private_key': private_key, 'fingerprint': fingerprint} +# EC2 API: Valid Values: +# pending | running | shutting-down | terminated | stopping | stopped +_STATE_DESCRIPTION_MAP = { + vm_states.ACTIVE: 'running', + vm_states.BUILD: 'pending', + vm_states.REBUILD: 'pending', + vm_states.REBOOT: 'reboot', + vm_states.DELETE: 'terminated', + vm_states.STOP: 'stopped', + vm_states.MIGRATE: 'migrate', + vm_states.RESIZE: 'resize', + vm_states.VERIFY_RESIZE: 'verify_resize', + vm_states.PAUSE: 'pause', + vm_states.SUSPEND: 'suspend', + vm_states.RESCUE: 'rescue' +} + + +def state_description_from_vm_state(vm_state): + """Map the vm state to the server status string""" + return _STATE_DESCRIPTION_MAP[vm_state] + + # TODO(yamahata): hypervisor dependent default device name _DEFAULT_ROOT_DEVICE_NAME = '/dev/sda1' _DEFAULT_MAPPINGS = {'ami': 'sda1', @@ -1199,7 +1222,7 @@ class CloudController(object): self._format_ramdisk_id(instance, i, 'ramdiskId') i['instanceState'] = { 'code': instance['power_state'], - 'name': instance['vm_state']} #FIXME + 'name': state_description_from_vm_state(instance['vm_state'])} fixed_addr = None floating_addr = None if instance['fixed_ips']: diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 0793784f8..cce9514ec 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -1163,7 +1163,7 @@ class CloudTestCase(test.TestCase): self.compute = self.start_service('compute') def _wait_for_state(self, ctxt, instance_id, predicate): - """Wait for an stopping instance to be a given state""" + """Wait for a stopped instance to be a given state""" id = ec2utils.ec2_id_to_id(instance_id) while True: info = self.cloud.compute_api.get(context=ctxt, instance_id=id) @@ -1174,12 +1174,12 @@ class CloudTestCase(test.TestCase): def _wait_for_running(self, instance_id): def is_running(info): - return info['state_description'] == 'running' + return info['vm_state'] == 'running' self._wait_for_state(self.context, instance_id, is_running) def _wait_for_stopped(self, instance_id): def is_stopped(info): - return info['state_description'] == 'stopped' + return info['vm_state'] == 'stopped' self._wait_for_state(self.context, instance_id, is_stopped) def _wait_for_terminate(self, instance_id): @@ -1562,7 +1562,7 @@ class CloudTestCase(test.TestCase): 'id': 0, 'root_device_name': '/dev/sdh', 'security_groups': [{'name': 'fake0'}, {'name': 'fake1'}], - 'state_description': 'stopping', + 'vm_state': 'stopped', 'instance_type': {'name': 'fake_type'}, 'kernel_id': 1, 'ramdisk_id': 2, diff --git a/nova/virt/fake.py b/nova/virt/fake.py index dc0628772..b42f4ca2f 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -42,9 +42,9 @@ def get_connection(_): class FakeInstance(object): - def __init__(self, name, state): + def __init__(self, name, power_state): self.name = name - self.state = state + self.power_state = power_state class FakeConnection(driver.ComputeDriver): @@ -120,7 +120,7 @@ class FakeConnection(driver.ComputeDriver): def _map_to_instance_info(self, instance): instance = utils.check_isinstance(instance, FakeInstance) - info = driver.InstanceInfo(instance.name, instance.state) + info = driver.InstanceInfo(instance.name, instance.power_state) return info def list_instances_detail(self): @@ -150,8 +150,8 @@ class FakeConnection(driver.ComputeDriver): """ name = instance.name - state = power_state.RUNNING - fake_instance = FakeInstance(name, state) + pstate = power_state.RUNNING + fake_instance = FakeInstance(name, pstate) self.instances[name] = fake_instance def snapshot(self, context, instance, name): @@ -325,7 +325,7 @@ class FakeConnection(driver.ComputeDriver): if instance_name not in self.instances: raise exception.InstanceNotFound(instance_id=instance_name) i = self.instances[instance_name] - return {'state': i.state, + return {'state': i.power_state, 'max_mem': 0, 'mem': 0, 'num_cpu': 2, -- cgit From 44aea954e5efa7d94d8333ddbf54dab6464018a0 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 22 Aug 2011 10:01:13 -0400 Subject: Renamed task_state to task_states... --- nova/api/openstack/common.py | 6 ++-- nova/compute/manager.py | 30 ++++++++--------- nova/compute/task_state.py | 43 ------------------------- nova/compute/task_states.py | 43 +++++++++++++++++++++++++ nova/tests/api/openstack/test_server_actions.py | 1 - nova/tests/vmwareapi/db_fakes.py | 4 +-- 6 files changed, 63 insertions(+), 64 deletions(-) delete mode 100644 nova/compute/task_state.py create mode 100644 nova/compute/task_states.py diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index eae0fd916..778c1e514 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -28,7 +28,7 @@ from nova import log as logging from nova import quota from nova.api.openstack import wsgi from nova.compute import vm_states -from nova.compute import task_state +from nova.compute import task_states LOG = logging.getLogger('nova.api.openstack.common') @@ -56,9 +56,9 @@ _STATE_MAP = { } -def status_from_state(_vm_state, _task_state=None): +def status_from_state(_vm_state, task_state=None): """Given vm_state and task_state, return a status string.""" - if _vm_state == vm_states.ACTIVE and _task_state == task_state.PASSWORD: + if _vm_state == vm_states.ACTIVE and task_state == task_states.PASSWORD: return "PASSWORD" return _STATE_MAP.get(_vm_state, "UNKNOWN_STATE") diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 4be5bdd69..5a4f62b76 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -56,7 +56,7 @@ from nova import rpc from nova import utils from nova import volume from nova.compute import power_state -from nova.compute import task_state +from nova.compute import task_states from nova.compute import vm_states from nova.notifier import api as notifier from nova.compute.utils import terminate_volumes @@ -373,7 +373,7 @@ class ComputeManager(manager.SchedulerDependentManager): updates['host'] = self.host updates['launched_on'] = self.host updates['vm_state'] = vm_states.BUILD - updates['task_state'] = task_state.NETWORKING + updates['task_state'] = task_states.NETWORKING instance = self.db.instance_update(context, instance_id, updates) instance['injected_files'] = kwargs.get('injected_files', []) instance['admin_pass'] = kwargs.get('admin_password', None) @@ -398,7 +398,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.BUILD, - task_state=task_state.BLOCK_DEVICE_MAPPING) + task_state=task_states.BLOCK_DEVICE_MAPPING) (swap, ephemerals, block_device_mapping) = self._setup_block_device_mapping( @@ -411,7 +411,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - task_state=task_state.SPAWN) + task_state=task_states.SPAWN) # TODO(vish) check to make sure the availability zone matches try: @@ -524,7 +524,7 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id, power_state=current_power_state, vm_state=vm_states.REBUILD, - task_state=task_state.REBUILDING) + task_state=task_states.REBUILDING) network_info = self._get_instance_nw_info(context, instance_ref) self.driver.destroy(instance_ref, network_info) @@ -532,7 +532,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.REBUILD, - task_state=task_state.BLOCK_DEVICE_MAPPING) + task_state=task_states.BLOCK_DEVICE_MAPPING) bd_mapping = self._setup_block_device_mapping(context, instance_id) @@ -545,7 +545,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.REBUILD, - task_state=task_state.SPAWN) + task_state=task_states.SPAWN) self.driver.spawn(context, instance_ref, network_info, bd_mapping) @@ -578,7 +578,7 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id, power_state=current_power_state, vm_state=vm_states.REBOOT, - task_state=task_state.REBOOTING) + task_state=task_states.REBOOTING) if instance_ref['power_state'] != power_state.RUNNING: state = instance_ref['power_state'] @@ -788,7 +788,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.RESCUE, - task_state=task_state.RESCUING) + task_state=task_states.RESCUING) instance_ref = self.db.instance_get(context, instance_id) network_info = self._get_instance_nw_info(context, instance_ref) @@ -800,7 +800,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.RESCUE, - task_state=task_state.RESCUED, + task_state=task_states.RESCUED, power_state=current_power_state) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @@ -813,7 +813,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.ACTIVE, - task_state=task_state.UNRESCUING) + task_state=task_states.UNRESCUING) instance_ref = self.db.instance_get(context, instance_id) network_info = self._get_instance_nw_info(context, instance_ref) @@ -1049,7 +1049,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.PAUSE, - task_state=task_state.PAUSING) + task_state=task_states.PAUSING) instance_ref = self.db.instance_get(context, instance_id) self.driver.pause(instance_ref, lambda result: None) @@ -1071,7 +1071,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.ACTIVE, - task_state=task_state.UNPAUSING) + task_state=task_states.UNPAUSING) instance_ref = self.db.instance_get(context, instance_id) self.driver.unpause(instance_ref, lambda result: None) @@ -1112,7 +1112,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.SUSPEND, - task_state=task_state.SUSPENDING) + task_state=task_states.SUSPENDING) instance_ref = self.db.instance_get(context, instance_id) self.driver.suspend(instance_ref, lambda result: None) @@ -1134,7 +1134,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.ACTIVE, - task_state=task_state.RESUMING) + task_state=task_states.RESUMING) instance_ref = self.db.instance_get(context, instance_id) self.driver.resume(instance_ref, lambda result: None) diff --git a/nova/compute/task_state.py b/nova/compute/task_state.py deleted file mode 100644 index 55466c783..000000000 --- a/nova/compute/task_state.py +++ /dev/null @@ -1,43 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 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. - -"""Possible task states for instances""" - -BLOCK_DEVICE_MAPPING='block_device_mapping' -NETWORKING='networking' -SPAWN='spawn' - -SNAPSHOT='snapshot' -BACKUP='backup' -PASSWORD='password' - -RESIZE_PREP='resize_prep' -RESIZE_MIGRATING='resize_migrating' -RESIZE_MIGRATED='resize_migrated' -RESIZE_FINISH='resize_finish' - -REBUILDING='rebuilding' - -REBOOTING='rebooting' -PAUSING='pausing' -UNPAUSING='unpausing' -SUSPENDING='suspending' -RESUMING='resuming' - -RESCUING='rescuing' -RESCUED='rescued' -UNRESCUING='unrescuing' diff --git a/nova/compute/task_states.py b/nova/compute/task_states.py new file mode 100644 index 000000000..55466c783 --- /dev/null +++ b/nova/compute/task_states.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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. + +"""Possible task states for instances""" + +BLOCK_DEVICE_MAPPING='block_device_mapping' +NETWORKING='networking' +SPAWN='spawn' + +SNAPSHOT='snapshot' +BACKUP='backup' +PASSWORD='password' + +RESIZE_PREP='resize_prep' +RESIZE_MIGRATING='resize_migrating' +RESIZE_MIGRATED='resize_migrated' +RESIZE_FINISH='resize_finish' + +REBUILDING='rebuilding' + +REBOOTING='rebooting' +PAUSING='pausing' +UNPAUSING='unpausing' +SUSPENDING='suspending' +RESUMING='resuming' + +RESCUING='rescuing' +RESCUED='rescued' +UNRESCUING='unrescuing' diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 6f8be0f47..011f83773 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -13,7 +13,6 @@ from nova import flags from nova.api.openstack import create_instance_helper from nova.compute import instance_types from nova.compute import vm_states -from nova.compute import task_state import nova.db.api from nova import test from nova.tests.api.openstack import common diff --git a/nova/tests/vmwareapi/db_fakes.py b/nova/tests/vmwareapi/db_fakes.py index dd38420ce..b046071c7 100644 --- a/nova/tests/vmwareapi/db_fakes.py +++ b/nova/tests/vmwareapi/db_fakes.py @@ -23,7 +23,7 @@ import time from nova import db from nova import utils -from nova.compute import task_state +from nova.compute import task_states from nova.compute import vm_states @@ -67,7 +67,7 @@ def stub_out_db_instance_api(stubs): 'kernel_id': values['kernel_id'], 'ramdisk_id': values['ramdisk_id'], 'vm_state': vm_states.BUILD, - 'task_state': task_state.SCHEDULE, + 'task_state': task_states.SCHEDULE, 'user_id': values['user_id'], 'project_id': values['project_id'], 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), -- cgit From 0ea797cd8e709d910c428234417fb179bdfd1525 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 22 Aug 2011 10:50:05 -0400 Subject: Update virt/fake to correct power state issue. --- nova/virt/fake.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index b42f4ca2f..c12ee3ab8 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -42,9 +42,9 @@ def get_connection(_): class FakeInstance(object): - def __init__(self, name, power_state): + def __init__(self, name, state): self.name = name - self.power_state = power_state + self.state = state class FakeConnection(driver.ComputeDriver): @@ -150,8 +150,8 @@ class FakeConnection(driver.ComputeDriver): """ name = instance.name - pstate = power_state.RUNNING - fake_instance = FakeInstance(name, pstate) + state = power_state.RUNNING + fake_instance = FakeInstance(name, state) self.instances[name] = fake_instance def snapshot(self, context, instance, name): @@ -325,7 +325,7 @@ class FakeConnection(driver.ComputeDriver): if instance_name not in self.instances: raise exception.InstanceNotFound(instance_id=instance_name) i = self.instances[instance_name] - return {'state': i.power_state, + return {'state': i.state, 'max_mem': 0, 'mem': 0, 'num_cpu': 2, -- cgit From a450c0f3bcc93fe3ec74939e49b109cb02624913 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 22 Aug 2011 11:09:24 -0400 Subject: Update migration number. --- .../versions/039_update_instance_states.py | 57 ---------------------- .../versions/040_update_instance_states.py | 57 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 57 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/039_update_instance_states.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/040_update_instance_states.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/039_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/039_update_instance_states.py deleted file mode 100644 index 07efbf90f..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/039_update_instance_states.py +++ /dev/null @@ -1,57 +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 MetaData, Table, Column, String - -meta = MetaData() - -c_task_state = Column('task_state', - 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 - - instances = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - c_state = instances.c.state - c_state.alter(name='power_state') - - c_vm_state = instances.c.state_description - c_vm_state.alter(name='vm_state') - - instances.create_column(c_task_state) - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - - instances = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - c_state = instances.c.power_state - c_state.alter(name='state') - - c_vm_state = instances.c.vm_state - c_vm_state.alter(name='state_description') - - instances.drop_column('task_state') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/040_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/040_update_instance_states.py new file mode 100644 index 000000000..07efbf90f --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/040_update_instance_states.py @@ -0,0 +1,57 @@ +# 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 MetaData, Table, Column, String + +meta = MetaData() + +c_task_state = Column('task_state', + 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 + + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_state = instances.c.state + c_state.alter(name='power_state') + + c_vm_state = instances.c.state_description + c_vm_state.alter(name='vm_state') + + instances.create_column(c_task_state) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_state = instances.c.power_state + c_state.alter(name='state') + + c_vm_state = instances.c.vm_state + c_vm_state.alter(name='state_description') + + instances.drop_column('task_state') -- cgit From c2736787be23d0893e2d4aebcc2cad6fdc5c2bd1 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 22 Aug 2011 11:57:42 -0400 Subject: Fix scheduler and integrated tests. --- nova/tests/integrated/test_servers.py | 11 +++++++++-- nova/tests/scheduler/test_scheduler.py | 6 +++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index deeb3d008..0e3a6eefb 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -35,7 +35,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): server = self.api.get_server(server['id']) print server retries = retries + 1 - if retries > 8: + if retries > 5: break return server @@ -48,8 +48,8 @@ class ServersTest(integrated_helpers._IntegratedTestBase): def test_create_and_delete_server(self): """Creates and deletes a server.""" self.flags(stub_network=True) - # Create server + # Create server # Build the server data gradually, checking errors along the way server = {} good_server = self._build_minimal_create_server_request() @@ -184,6 +184,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): def test_create_and_rebuild_server(self): """Rebuild a server.""" + self.flags(stub_network=True) # create a server with initially has no metadata server = self._build_minimal_create_server_request() @@ -216,6 +217,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): def test_create_and_rebuild_server_with_metadata(self): """Rebuild a server with metadata.""" + self.flags(stub_network=True) # create a server with initially has no metadata server = self._build_minimal_create_server_request() @@ -225,6 +227,8 @@ class ServersTest(integrated_helpers._IntegratedTestBase): self.assertTrue(created_server['id']) created_server_id = created_server['id'] + created_server = self._wait_for_creation(created_server) + # rebuild the server with metadata post = {} post['rebuild'] = { @@ -252,6 +256,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): def test_create_and_rebuild_server_with_metadata_removal(self): """Rebuild a server with metadata.""" + self.flags(stub_network=True) # create a server with initially has no metadata server = self._build_minimal_create_server_request() @@ -268,6 +273,8 @@ class ServersTest(integrated_helpers._IntegratedTestBase): self.assertTrue(created_server['id']) created_server_id = created_server['id'] + created_server = self._wait_for_creation(created_server) + # rebuild the server with metadata post = {} post['rebuild'] = { diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index 629019eaf..a1281ae73 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -95,7 +95,7 @@ class SchedulerTestCase(test.TestCase): inst['vcpus'] = kwargs.get('vcpus', 1) inst['memory_mb'] = kwargs.get('memory_mb', 10) inst['local_gb'] = kwargs.get('local_gb', 20) - inst['vm_state'] = kwargs.get('vm_state', vm_state.ACTIVE) + inst['vm_state'] = kwargs.get('vm_state', vm_states.ACTIVE) inst['power_state'] = kwargs.get('power_state', power_state.RUNNING) inst['task_state'] = kwargs.get('task_state', None) return db.instance_create(ctxt, inst) @@ -275,7 +275,7 @@ class SimpleDriverTestCase(test.TestCase): inst['memory_mb'] = kwargs.get('memory_mb', 20) inst['local_gb'] = kwargs.get('local_gb', 30) inst['launched_on'] = kwargs.get('launghed_on', 'dummy') - inst['vm_state'] = kwargs.get('vm_state', vm_state.ACTIVE) + inst['vm_state'] = kwargs.get('vm_state', vm_states.ACTIVE) inst['task_state'] = kwargs.get('task_state', None) inst['power_state'] = kwargs.get('power_state', power_state.RUNNING) return db.instance_create(self.context, inst)['id'] @@ -669,7 +669,7 @@ class SimpleDriverTestCase(test.TestCase): block_migration=False) i_ref = db.instance_get(self.context, instance_id) - self.assertTrue(i_ref['vm_state'] == vm_state.MIGRATE) + self.assertTrue(i_ref['vm_state'] == vm_states.MIGRATE) db.instance_destroy(self.context, instance_id) db.volume_destroy(self.context, v_ref['id']) -- cgit From d60f813201df345507ce0aca7bed0f8b719aabfe Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 22 Aug 2011 11:59:08 -0400 Subject: Fixes/updates to make test_cloud pass. --- nova/api/ec2/cloud.py | 1 + nova/compute/manager.py | 2 +- nova/tests/test_cloud.py | 7 ++++--- nova/virt/fake.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b7c7d2e12..8bddf3032 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -82,6 +82,7 @@ def _gen_key(context, user_id, key_name): # EC2 API: Valid Values: # pending | running | shutting-down | terminated | stopping | stopped _STATE_DESCRIPTION_MAP = { + None: 'pending', vm_states.ACTIVE: 'running', vm_states.BUILD: 'pending', vm_states.REBUILD: 'pending', diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 5a4f62b76..75928f7ef 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1706,7 +1706,7 @@ class ComputeManager(manager.SchedulerDependentManager): if vm_instance is None: vm_power_state = power_state.NOSTATE else: - vm_power_state = vm_instance["power_state"] + vm_power_state = vm_instance.state if vm_power_state == db_power_state: continue diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index cce9514ec..4d148f39e 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -38,6 +38,7 @@ from nova import test from nova import utils from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils +from nova.compute import vm_states from nova.image import fake @@ -1174,12 +1175,12 @@ class CloudTestCase(test.TestCase): def _wait_for_running(self, instance_id): def is_running(info): - return info['vm_state'] == 'running' + return info['vm_state'] == vm_states.ACTIVE self._wait_for_state(self.context, instance_id, is_running) def _wait_for_stopped(self, instance_id): def is_stopped(info): - return info['vm_state'] == 'stopped' + return info['vm_state'] == vm_states.STOP self._wait_for_state(self.context, instance_id, is_stopped) def _wait_for_terminate(self, instance_id): @@ -1562,7 +1563,7 @@ class CloudTestCase(test.TestCase): 'id': 0, 'root_device_name': '/dev/sdh', 'security_groups': [{'name': 'fake0'}, {'name': 'fake1'}], - 'vm_state': 'stopped', + 'vm_state': vm_states.STOP, 'instance_type': {'name': 'fake_type'}, 'kernel_id': 1, 'ramdisk_id': 2, diff --git a/nova/virt/fake.py b/nova/virt/fake.py index c12ee3ab8..dc0628772 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -120,7 +120,7 @@ class FakeConnection(driver.ComputeDriver): def _map_to_instance_info(self, instance): instance = utils.check_isinstance(instance, FakeInstance) - info = driver.InstanceInfo(instance.name, instance.power_state) + info = driver.InstanceInfo(instance.name, instance.state) return info def list_instances_detail(self): -- cgit From 393c9375626812ecb904d9048c833b0d110e9aa8 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 22 Aug 2011 13:04:05 -0400 Subject: Use 'vm_state' instead of 'state' in instance filters query. --- 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 a5ed2363f..3e690e094 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1239,7 +1239,7 @@ def instance_get_all_by_filters(context, filters): # Filters for exact matches that we can do along with the SQL query... # For other filters that don't match this, we will do regexp matching exact_match_filter_names = ['project_id', 'user_id', 'image_ref', - 'state', 'instance_type_id', 'deleted'] + 'vm_state', 'instance_type_id', 'deleted'] query_filters = [key for key in filters.iterkeys() if key in exact_match_filter_names] -- cgit From ea3684d2a2e60f19bdea6b3117be613103a605dc Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 22 Aug 2011 13:16:48 -0400 Subject: Fixes for a number of tests. --- nova/compute/api.py | 3 +++ nova/compute/task_states.py | 1 + nova/tests/vmwareapi/db_fakes.py | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 3664be5ed..0f993015b 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -36,6 +36,7 @@ from nova import utils from nova import volume from nova.compute import instance_types from nova.compute import power_state +from nova.compute import task_states from nova.compute import vm_states from nova.compute.utils import terminate_volumes from nova.scheduler import api as scheduler_api @@ -397,6 +398,8 @@ class API(base.Base): updates['display_name'] = "Server %s" % instance_id instance['display_name'] = updates['display_name'] updates['hostname'] = self.hostname_factory(instance) + updates['vm_state'] = vm_states.BUILD + updates['task_state'] = task_states.SCHEDULING instance = self.update(context, instance_id, **updates) return instance diff --git a/nova/compute/task_states.py b/nova/compute/task_states.py index 55466c783..885a30ebe 100644 --- a/nova/compute/task_states.py +++ b/nova/compute/task_states.py @@ -17,6 +17,7 @@ """Possible task states for instances""" +SCHEDULING='scheduling' BLOCK_DEVICE_MAPPING='block_device_mapping' NETWORKING='networking' SPAWN='spawn' diff --git a/nova/tests/vmwareapi/db_fakes.py b/nova/tests/vmwareapi/db_fakes.py index b046071c7..b56956f96 100644 --- a/nova/tests/vmwareapi/db_fakes.py +++ b/nova/tests/vmwareapi/db_fakes.py @@ -67,7 +67,7 @@ def stub_out_db_instance_api(stubs): 'kernel_id': values['kernel_id'], 'ramdisk_id': values['ramdisk_id'], 'vm_state': vm_states.BUILD, - 'task_state': task_states.SCHEDULE, + 'task_state': task_states.SCHEDULING, 'user_id': values['user_id'], 'project_id': values['project_id'], 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), -- cgit From 7726b3d763a136347f2324e630f0a3cdc60a045b Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 22 Aug 2011 14:08:03 -0700 Subject: Simple usage extension for nova. Uses db to calculate tenant_usage for specified time periods. Methods: index: return a list of tenant_usages, with option of incuding detailed server_usage show: returns a specific tenant_usage object tenant_usage object: tenant_usage.total_memory_mb_usage: sum of memory_mb * hours for all instances in tenant for this period tenant_usage.total_local_gb_usage: sum of local_gb * hours for all instances in tenant for this period tenant_usage.total_vcpus_usage: sum of vcpus * hours for all instances in tenant for this period tenant_usage.total_hours: sum of all instance hours for this period tenant_usage.server_usages: A detailed list of server_usages, which describe the usage of a specific server For larger instances db tables, indexes on instance.launched_at and instance.terminated_at should significantly help performance. --- nova/api/openstack/contrib/simple_tenant_usage.py | 268 +++++++++++++++++++++ .../openstack/contrib/test_simple_tenant_usage.py | 189 +++++++++++++++ 2 files changed, 457 insertions(+) create mode 100644 nova/api/openstack/contrib/simple_tenant_usage.py create mode 100644 nova/tests/api/openstack/contrib/test_simple_tenant_usage.py diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py new file mode 100644 index 000000000..d578b2b67 --- /dev/null +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -0,0 +1,268 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 urlparse +import webob + +from datetime import datetime +from nova import db +from nova import exception +from nova import flags +from nova.compute import instance_types +from nova.api.openstack import extensions +from nova.api.openstack import views +from nova.db.sqlalchemy.session import get_session +from webob import exc + + +FLAGS = flags.FLAGS + +INSTANCE_FIELDS = ['id', + 'image_ref', + 'project_id', + 'user_id', + 'display_name', + 'state_description', + 'instance_type_id', + 'launched_at', + 'terminated_at'] + + +class SimpleTenantUsageController(object): + + def _get_instances_for_time_period(self, period_start, period_stop, + tenant_id): + tenant_clause = '' + if tenant_id: + tenant_clause = " and project_id='%s'" % tenant_id + + conn = get_session().connection() + rows = conn.execute("select %s from instances where \ + (terminated_at is NULL or terminated_at > '%s') \ + and (launched_at < '%s') %s" %\ + (','.join(INSTANCE_FIELDS), + period_start.isoformat(' '),\ + period_stop.isoformat(' '), + tenant_clause)).fetchall() + + return rows + + def _hours_for(self, instance, period_start, period_stop): + launched_at = instance['launched_at'] + terminated_at = instance['terminated_at'] + if terminated_at is not None: + if not isinstance(terminated_at, datetime): + terminated_at = datetime.strptime(terminated_at, + "%Y-%m-%d %H:%M:%S.%f") + + if launched_at is not None: + if not isinstance(launched_at, datetime): + launched_at = datetime.strptime(launched_at, + "%Y-%m-%d %H:%M:%S.%f") + + if terminated_at and terminated_at < period_start: + return 0 + # nothing if it started after the usage report ended + if launched_at and launched_at > period_stop: + return 0 + if launched_at: + # if instance launched after period_started, don't charge for first + start = max(launched_at, period_start) + if terminated_at: + # if instance stopped before period_stop, don't charge after + stop = min(period_stop, terminated_at) + else: + # instance is still running, so charge them up to current time + stop = period_stop + dt = stop - start + seconds = dt.days * 3600 * 24 + dt.seconds\ + + dt.microseconds / 100000.0 + + return seconds / 3600.0 + else: + # instance hasn't launched, so no charge + return 0 + + def _usage_for_period(self, context, period_start, + period_stop, tenant_id=None, detailed=True): + + rows = self._get_instances_for_time_period(period_start, + period_stop, + tenant_id) + rval = {} + flavors = {} + + for row in rows: + info = {} + for i in range(len(INSTANCE_FIELDS)): + info[INSTANCE_FIELDS[i]] = row[i] + info['hours'] = self._hours_for(info, period_start, period_stop) + flavor_type = info['instance_type_id'] + + if not flavors.get(flavor_type): + try: + flavors[flavor_type] = db.instance_type_get(context, + info['instance_type_id']) + except exception.InstanceTypeNotFound: + # can't bill if there is no instance type + continue + + flavor = flavors[flavor_type] + + info['name'] = info['display_name'] + del(info['display_name']) + + info['memory_mb'] = flavor['memory_mb'] + info['local_gb'] = flavor['local_gb'] + info['vcpus'] = flavor['vcpus'] + + info['tenant_id'] = info['project_id'] + del(info['project_id']) + + info['flavor'] = flavor['name'] + del(info['instance_type_id']) + + info['started_at'] = info['launched_at'] + del(info['launched_at']) + + info['ended_at'] = info['terminated_at'] + del(info['terminated_at']) + + if info['ended_at']: + info['state'] = 'terminated' + else: + info['state'] = info['state_description'] + + del(info['state_description']) + + now = datetime.utcnow() + + if info['state'] == 'terminated': + delta = self._parse_datetime(info['ended_at'])\ + - self._parse_datetime(info['started_at']) + else: + delta = now - self._parse_datetime(info['started_at']) + + info['uptime'] = delta.days * 24 * 60 + delta.seconds + + if not info['tenant_id'] in rval: + summary = {} + summary['tenant_id'] = info['tenant_id'] + if detailed: + summary['server_usages'] = [] + summary['total_local_gb_usage'] = 0 + summary['total_vcpus_usage'] = 0 + summary['total_memory_mb_usage'] = 0 + summary['total_hours'] = 0 + summary['start'] = period_start + summary['stop'] = period_stop + rval[info['tenant_id']] = summary + + summary = rval[info['tenant_id']] + summary['total_local_gb_usage'] += info['local_gb'] * info['hours'] + summary['total_vcpus_usage'] += info['vcpus'] * info['hours'] + summary['total_memory_mb_usage'] += info['memory_mb']\ + * info['hours'] + + summary['total_hours'] += info['hours'] + if detailed: + summary['server_usages'].append(info) + + return rval.values() + + def _parse_datetime(self, dtstr): + if isinstance(dtstr, datetime): + return dtstr + try: + return datetime.strptime(dtstr, "%Y-%m-%dT%H:%M:%S") + except: + try: + return datetime.strptime(dtstr, "%Y-%m-%dT%H:%M:%S.%f") + except: + return datetime.strptime(dtstr, "%Y-%m-%d %H:%M:%S.%f") + + def _get_datetime_range(self, req): + qs = req.environ.get('QUERY_STRING', '') + env = urlparse.parse_qs(qs) + period_start = self._parse_datetime(env.get('start', + [datetime.utcnow().isoformat()])[0]) + period_stop = self._parse_datetime(env.get('end', + [datetime.utcnow().isoformat()])[0]) + + detailed = bool(env.get('detailed', False)) + return (period_start, period_stop, detailed) + + def index(self, req): + """Retrive tenant_usage for all tenants""" + (period_start, period_stop, detailed) = self._get_datetime_range(req) + context = req.environ['nova.context'] + + if not context.is_admin and FLAGS.allow_admin_api: + return webob.Response(status_int=403) + + usages = self._usage_for_period(context, + period_start, + period_stop, + detailed=detailed) + return {'tenant_usages': usages} + + def show(self, req, id): + """Retrive tenant_usage for a specified tenant""" + (period_start, period_stop, ignore) = self._get_datetime_range(req) + context = req.environ['nova.context'] + + if not context.is_admin and FLAGS.allow_admin_api: + if id != context.project_id: + return webob.Response(status_int=403) + + usage = self._usage_for_period(context, + period_start, + period_stop, + id, + detailed=True) + if len(usage): + usage = usage[0] + else: + usage = {} + return {'tenant_usage': usage} + + +class Simple_tenant_usage(extensions.ExtensionDescriptor): + + def get_name(self): + return "Simple_tenant_usage" + + def get_alias(self): + return "os-simple-tenant-usage" + + def get_description(self): + return "Simple tenant usage extension" + + def get_namespace(self): + return "http://docs.openstack.org/ext/os-simple-tenant-usage/api/v1.1" + + def get_updated(self): + return "2011-08-19T00:00:00+00:00" + + def get_resources(self): + resources = [] + + res = extensions.ResourceExtension('os-simple-tenant-usage', + SimpleTenantUsageController()) + resources.append(res) + + return resources diff --git a/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py b/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py new file mode 100644 index 000000000..d20e36aaf --- /dev/null +++ b/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py @@ -0,0 +1,189 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 json +import webob + +from nova import context +from nova import db +from nova import flags +from nova import test +from nova.compute import instance_types +from nova.db.sqlalchemy import models +from nova.db.sqlalchemy import session +from nova.tests.api.openstack import fakes +from webob import exc + +from nova.api.openstack.contrib import simple_tenant_usage + + +FLAGS = flags.FLAGS + +SERVERS = 5 +TENANTS = 2 +HOURS = 24 +LOCAL_GB = 10 +MEMORY_MB = 1024 +VCPUS = 2 +STOP = datetime.datetime.utcnow() +START = STOP - datetime.timedelta(hours=HOURS) + + +def fake_get_session(): + class FakeFetcher(object): + def fetchall(fetcher_self): + # return 10 rows, 2 tenants, 5 servers each, each run for 1 day + return [get_fake_db_row(START, + STOP, + x, + "faketenant_%s" % (x / SERVERS)) + for x in xrange(TENANTS * SERVERS)] + + class FakeConn(object): + def execute(self, query): + return FakeFetcher() + + class FakeSession(object): + def connection(self): + return FakeConn() + + return FakeSession() + + +def fake_instance_type_get(context, instance_type_id): + return {'id': 1, + 'vcpus': VCPUS, + 'local_gb': LOCAL_GB, + 'memory_mb': MEMORY_MB, + 'name': + 'fakeflavor'} + + +def get_fake_db_row(start, end, instance_id, tenant_id): + return [instance_id, + '1', + tenant_id, + 'fakeuser', + 'name', + 'state', + 1, + start, + None] + + +class SimpleTenantUsageTest(test.TestCase): + def setUp(self): + super(SimpleTenantUsageTest, self).setUp() + self.stubs.Set(session, "get_session", + fake_get_session) + self.stubs.Set(db, "instance_type_get", + fake_instance_type_get) + self.admin_context = context.RequestContext('fakeadmin_0', + 'faketenant_0', + is_admin=True) + self.user_context = context.RequestContext('fakeadmin_0', + 'faketenant_0', + is_admin=False) + self.alt_user_context = context.RequestContext('fakeadmin_0', + 'faketenant_1', + is_admin=False) + FLAGS.allow_admin_api = True + + def test_verify_db_fields_exist_in_instance_model(self): + for field in simple_tenant_usage.INSTANCE_FIELDS: + self.assertTrue(field in models.Instance.__table__.columns) + + def test_verify_index(self): + req = webob.Request.blank( + '/v1.1/os-simple-tenant-usage?start=%s&end=%s' % + (START.isoformat(), STOP.isoformat())) + req.method = "GET" + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app( + fake_auth_context=self.admin_context)) + + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + usages = res_dict['tenant_usages'] + for i in xrange(TENANTS): + self.assertEqual(int(usages[i]['total_hours']), + SERVERS * HOURS) + self.assertEqual(int(usages[i]['total_local_gb_usage']), + SERVERS * LOCAL_GB * HOURS) + self.assertEqual(int(usages[i]['total_memory_mb_usage']), + SERVERS * MEMORY_MB * HOURS) + self.assertEqual(int(usages[i]['total_vcpus_usage']), + SERVERS * VCPUS * HOURS) + self.assertFalse(usages[i].get('server_usages')) + + def test_verify_detailed_index(self): + req = webob.Request.blank( + '/v1.1/os-simple-tenant-usage?detailed=1&start=%s&end=%s' % + (START.isoformat(), STOP.isoformat())) + req.method = "GET" + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app( + fake_auth_context=self.admin_context)) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + usages = res_dict['tenant_usages'] + for i in xrange(TENANTS): + servers = usages[i]['server_usages'] + for j in xrange(SERVERS): + self.assertEqual(int(servers[j]['hours']), HOURS) + + def test_verify_index_fails_for_nonadmin(self): + req = webob.Request.blank( + '/v1.1/os-simple-tenant-usage?detailed=1&start=%s&end=%s' % + (START.isoformat(), STOP.isoformat())) + req.method = "GET" + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 403) + + def test_verify_show(self): + req = webob.Request.blank( + '/v1.1/os-simple-tenant-usage/faketenant_0?start=%s&end=%s' % + (START.isoformat(), STOP.isoformat())) + req.method = "GET" + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app( + fake_auth_context=self.user_context)) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + + usage = res_dict['tenant_usage'] + servers = usage['server_usages'] + self.assertEqual(len(usage['server_usages']), SERVERS) + for j in xrange(SERVERS): + self.assertEqual(int(servers[j]['hours']), HOURS) + + def test_verify_show_cant_view_other_tenant(self): + req = webob.Request.blank( + '/v1.1/os-simple-tenant-usage/faketenant_0?start=%s&end=%s' % + (START.isoformat(), STOP.isoformat())) + req.method = "GET" + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app( + fake_auth_context=self.alt_user_context)) + self.assertEqual(res.status_int, 403) -- cgit From 4b0944731a25d3cfcd30358619376dedf2251701 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 22 Aug 2011 14:31:26 -0700 Subject: some readability fixes per ja feedback --- nova/api/openstack/contrib/simple_tenant_usage.py | 29 ++++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index d578b2b67..80d819365 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -97,8 +97,8 @@ class SimpleTenantUsageController(object): # instance hasn't launched, so no charge return 0 - def _usage_for_period(self, context, period_start, - period_stop, tenant_id=None, detailed=True): + def _tenant_usages_for_period(self, context, period_start, + period_stop, tenant_id=None, detailed=True): rows = self._get_instances_for_time_period(period_start, period_stop, @@ -208,32 +208,33 @@ class SimpleTenantUsageController(object): def index(self, req): """Retrive tenant_usage for all tenants""" - (period_start, period_stop, detailed) = self._get_datetime_range(req) context = req.environ['nova.context'] if not context.is_admin and FLAGS.allow_admin_api: return webob.Response(status_int=403) - usages = self._usage_for_period(context, - period_start, - period_stop, - detailed=detailed) + (period_start, period_stop, detailed) = self._get_datetime_range(req) + usages = self._tenant_usages_for_period(context, + period_start, + period_stop, + detailed=detailed) return {'tenant_usages': usages} def show(self, req, id): """Retrive tenant_usage for a specified tenant""" - (period_start, period_stop, ignore) = self._get_datetime_range(req) + tenant_id = id context = req.environ['nova.context'] if not context.is_admin and FLAGS.allow_admin_api: - if id != context.project_id: + if tenant_id != context.project_id: return webob.Response(status_int=403) - usage = self._usage_for_period(context, - period_start, - period_stop, - id, - detailed=True) + (period_start, period_stop, ignore) = self._get_datetime_range(req) + usage = self._tenant_usages_for_period(context, + period_start, + period_stop, + tenant_id=tenant_id, + detailed=True) if len(usage): usage = usage[0] else: -- cgit From de0a17310e7228aa96263243851a89fb016f9730 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 22 Aug 2011 15:21:31 -0700 Subject: remove extra spaces --- nova/api/openstack/contrib/simple_tenant_usage.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index 80d819365..5f4218237 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -43,7 +43,6 @@ INSTANCE_FIELDS = ['id', class SimpleTenantUsageController(object): - def _get_instances_for_time_period(self, period_start, period_stop, tenant_id): tenant_clause = '' @@ -243,7 +242,6 @@ class SimpleTenantUsageController(object): class Simple_tenant_usage(extensions.ExtensionDescriptor): - def get_name(self): return "Simple_tenant_usage" -- cgit From d2ea838bfad96bae172458ac6389a9f98111a7df Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Mon, 22 Aug 2011 17:46:47 -0500 Subject: update test_network test_get_instance_nw_info() --- nova/network/manager.py | 12 +++---- nova/tests/fake_network_info.py | 21 ++++++------ nova/tests/test_network.py | 71 +++++++++++++++++------------------------ 3 files changed, 48 insertions(+), 56 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index 921c27e45..dcd5aad9a 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -468,17 +468,17 @@ class NetworkManager(manager.SchedulerDependentManager): # TODO(tr3buchet) eventually "enabled" should be determined def ip_dict(ip): return { - "ip": ip, - "netmask": network["netmask"], - "enabled": "1"} + 'ip': ip, + 'netmask': network['netmask'], + 'enabled': '1'} def ip6_dict(): return { - "ip": ipv6.to_global(network['cidr_v6'], + 'ip': ipv6.to_global(network['cidr_v6'], vif['address'], network['project_id']), - "netmask": network['netmask_v6'], - "enabled": "1"} + 'netmask': network['netmask_v6'], + 'enabled': '1'} network_dict = { 'bridge': network['bridge'], 'id': network['id'], diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 0c33898cb..072585195 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -42,8 +42,8 @@ def fake_network(n): 'gateway_v6': '2001:db8:0:%x::1' % n, 'netmask_v6': '64', 'netmask': '255.255.255.0', - 'bridge': 'fa%d' % n, - 'bridge_interface': 'fake_br%d' % n, + 'bridge': 'fake_br%d' % n, + 'bridge_interface': 'fake_eth%d' % n, 'gateway': '192.168.%d.1' % n, 'broadcast': '192.168.%d.255' % n, 'dns1': '192.168.%d.3' % n, @@ -54,26 +54,29 @@ def fake_network(n): 'vpn_public_address': '192.168.%d.2' % n} -def fixed_ips(num_networks, num_ips): +def fixed_ips(num_networks, num_ips, num_floating_ips=0): for network in xrange(num_networks): for ip in xrange(num_ips): - yield {'id': network * num_ips + ip, + id = network * num_ips + ip + f_ips = [floating_ips(id).next() for i in xrange(num_floating_ips)] + yield {'id': id, 'network_id': network, 'address': '192.168.%d.1%02d' % (network, ip), 'instance_id': 0, 'allocated': False, # and since network_id and vif_id happen to be equivalent 'virtual_interface_id': network, - 'floating_ips': [FakeModel(**floating_ip)]} + 'floating_ips': [FakeModel(**ip) for ip in f_ips]} flavor = {'id': 0, 'rxtx_cap': 3} - -floating_ip = {'id': 0, - 'address': '10.10.10.10', - 'fixed_ip_id': 0, +def floating_ips(fixed_ip_id): + for i in xrange(154): + yield {'id': 0, + 'address': '10.10.10.%d' % (i + 100), + 'fixed_ip_id': fixed_ip_id, 'project_id': None, 'auto_assigned': False} diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index e5c80b6f6..7822fbc70 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -14,15 +14,14 @@ # 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 mox from nova import db from nova import exception from nova import log as logging from nova import test from nova.network import manager as network_manager - - -import mox +from nova.tests import fake_network_info LOG = logging.getLogger('nova.tests.network') @@ -128,60 +127,50 @@ class FlatNetworkTestCase(test.TestCase): self.network.db = db def test_get_instance_nw_info(self): - self.mox.StubOutWithMock(db, 'fixed_ip_get_by_instance') - self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') - self.mox.StubOutWithMock(db, 'instance_type_get') - - db.fixed_ip_get_by_instance(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(fixed_ips) - db.virtual_interface_get_by_instance(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(vifs) - db.instance_type_get(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(flavor) - self.mox.ReplayAll() - - nw_info = self.network.get_instance_nw_info(None, 0, 0, None) + fake_get_instance_nw_info = fake_network_info.fake_get_instance_nw_info - self.assertTrue(nw_info) + nw_info = fake_get_instance_nw_info(self.stubs, 0, 2) + self.assertFalse(nw_info) - for i, nw in enumerate(nw_info): - i8 = i + 8 - check = {'bridge': 'fa%s' % i, + for i, (nw, info) in enumerate(nw_info): + check = {'bridge': 'fake_br%d' % i, 'cidr': '192.168.%s.0/24' % i, - 'cidr_v6': '2001:db%s::/64' % i8, + 'cidr_v6': '2001:db8:0:%x::/64' % i, 'id': i, 'multi_host': False, - 'injected': 'DONTCARE', - 'bridge_interface': 'fake_fa%s' % i, + 'injected': False, + 'bridge_interface': 'fake_eth%d' % i, 'vlan': None} - self.assertDictMatch(nw[0], check) + self.assertDictMatch(nw, check) - check = {'broadcast': '192.168.%s.255' % i, - 'dhcp_server': '192.168.%s.1' % i, - 'dns': 'DONTCARE', - 'gateway': '192.168.%s.1' % i, - 'gateway6': '2001:db%s::1' % i8, + check = {'broadcast': '192.168.%d.255' % i, + 'dhcp_server': '192.168.%d.1' % i, + 'dns': ['192.168.%d.3' % n, '192.168.%d.4' % n] + 'gateway': '192.168.%d.1' % i, + 'gateway6': '2001:db8:0:%x::1' % i, 'ip6s': 'DONTCARE', 'ips': 'DONTCARE', - 'label': 'test%s' % i, - 'mac': 'DE:AD:BE:EF:00:0%s' % i, - 'vif_uuid': ('00000000-0000-0000-0000-000000000000000%s' % - i), - 'rxtx_cap': 'DONTCARE', + 'label': 'test%d' % i, + 'mac': 'DE:AD:BE:EF:00:%02x' % i, + 'vif_uuid': + '00000000-0000-0000-0000-00000000000000%02d' % i, + 'rxtx_cap': 3, 'should_create_vlan': False, 'should_create_bridge': False} - self.assertDictMatch(nw[1], check) + self.assertDictMatch(info, check) check = [{'enabled': 'DONTCARE', - 'ip': '2001:db%s::dcad:beff:feef:%s' % (i8, i), + 'ip': '2001:db8::dcad:beff:feef:%s' % i, 'netmask': '64'}] - self.assertDictListMatch(nw[1]['ip6s'], check) + self.assertDictListMatch(info['ip6s'], check) - check = [{'enabled': '1', - 'ip': '192.168.%s.100' % i, - 'netmask': '255.255.255.0'}] - self.assertDictListMatch(nw[1]['ips'], check) + num_fixed_ips = len(info['ips']) + check = [{'enabled': 'DONTCARE', + 'ip': '192.168.%d.1%02d' % (i, ip_num), + 'netmask': '255.255.255.0'} + for ip_num in xrange(num_fixed_ips)] + self.assertDictListMatch(info['ips'], check) class VlanNetworkTestCase(test.TestCase): -- cgit From 158a953f98f8d4ee365cbc4936754fe7a8c89082 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Mon, 22 Aug 2011 17:52:54 -0500 Subject: syntax --- nova/tests/test_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 7822fbc70..91105ece3 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -146,7 +146,7 @@ class FlatNetworkTestCase(test.TestCase): check = {'broadcast': '192.168.%d.255' % i, 'dhcp_server': '192.168.%d.1' % i, - 'dns': ['192.168.%d.3' % n, '192.168.%d.4' % n] + 'dns': ['192.168.%d.3' % n, '192.168.%d.4' % n], 'gateway': '192.168.%d.1' % i, 'gateway6': '2001:db8:0:%x::1' % i, 'ip6s': 'DONTCARE', -- cgit From 49ef06ba21115a64c2efbb6fa81e0e6ee3f9095d Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 22 Aug 2011 16:21:29 -0700 Subject: xml deserialization, and test fixes --- nova/api/openstack/servers.py | 9 ++++- nova/tests/api/openstack/test_servers.py | 66 +++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 57ed5f45e..7faeb7278 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -183,6 +183,10 @@ class Controller(object): self.helper._validate_server_name(name) update_dict['display_name'] = name.strip() + if 'description' in body['server']: + description = body['server']['description'] + update_dict['display_description'] = description.strip() + try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: @@ -836,9 +840,12 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): def _add_server_attributes(self, node, server): node.setAttribute('id', str(server['id'])) + node.setAttribute('userId', str(server['user_id'])) + node.setAttribute('tenantId', str(server['tenant_id'])) node.setAttribute('uuid', str(server['uuid'])) node.setAttribute('hostId', str(server['hostId'])) node.setAttribute('name', server['name']) + node.setAttribute('description', server['description']) node.setAttribute('created', str(server['created'])) node.setAttribute('updated', str(server['updated'])) node.setAttribute('status', server['status']) @@ -945,7 +952,7 @@ def create_resource(version='1.0'): "attributes": { "server": ["id", "imageId", "name", "flavorId", "hostId", "status", "progress", "adminPass", "flavorRef", - "imageRef"], + "imageRef", "userId", "tenantId", "description"], "link": ["rel", "type", "href"], }, "dict_collections": { diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 480d6a370..2f849f07a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -145,7 +145,8 @@ def instance_addresses(context, instance_id): def stub_instance(id, user_id='fake', project_id='fake', private_address=None, public_addresses=None, host=None, power_state=0, reservation_id="", uuid=FAKE_UUID, image_ref="10", - flavor_id="1", interfaces=None, name=None): + flavor_id="1", interfaces=None, name=None, + description='fakedescription'): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -194,7 +195,7 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, "terminated_at": utils.utcnow(), "availability_zone": "", "display_name": server_name, - "display_description": "fakedescription", + "display_description": description, "locked": False, "metadata": metadata, "uuid": uuid, @@ -427,9 +428,12 @@ class ServersTest(test.TestCase): expected = minidom.parseString(""" Date: Mon, 22 Aug 2011 17:02:54 -0700 Subject: fix pep8 issue --- nova/api/openstack/create_instance_helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 96f817d38..b0cdd87ea 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -154,8 +154,8 @@ class CreateInstanceHelper(object): kernel_id=kernel_id, ramdisk_id=ramdisk_id, display_name=name, - display_description=\ - server_dict.get('description', ''), + display_description=server_dict.\ + get('description', ''), key_name=key_name, key_data=key_data, metadata=server_dict.get('metadata', {}), -- cgit From 632526f0cf7a5be3a26c3ae14683b75bfb6afbfd Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 22 Aug 2011 22:18:43 -0700 Subject: pulling all qmanager changes into a branch based on trunk, as they were previously stacked on top of melange --- bin/nova-manage | 35 ++- nova/db/api.py | 5 + nova/db/sqlalchemy/api.py | 20 +- .../versions/041_add_network_priority.py | 45 +++ nova/db/sqlalchemy/models.py | 1 + nova/network/manager.py | 37 ++- nova/network/quantum/__init__.py | 16 ++ nova/network/quantum/client.py | 306 +++++++++++++++++++++ nova/network/quantum/fake.py | 213 ++++++++++++++ nova/network/quantum/manager.py | 232 ++++++++++++++++ nova/network/quantum/melange_connection.py | 133 +++++++++ nova/network/quantum/melange_ipam_lib.py | 135 +++++++++ nova/network/quantum/nova_ipam_lib.py | 152 ++++++++++ nova/network/quantum/quantum_connection.py | 97 +++++++ nova/tests/test_quantum.py | 261 ++++++++++++++++++ 15 files changed, 1660 insertions(+), 28 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py create mode 100644 nova/network/quantum/__init__.py create mode 100644 nova/network/quantum/client.py create mode 100644 nova/network/quantum/fake.py create mode 100644 nova/network/quantum/manager.py create mode 100644 nova/network/quantum/melange_connection.py create mode 100644 nova/network/quantum/melange_ipam_lib.py create mode 100644 nova/network/quantum/nova_ipam_lib.py create mode 100644 nova/network/quantum/quantum_connection.py create mode 100644 nova/tests/test_quantum.py diff --git a/bin/nova-manage b/bin/nova-manage index 1b29d7196..9819ef206 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -58,11 +58,11 @@ import glob import json import math import netaddr +from optparse import OptionParser import os import sys import time -from optparse import OptionParser # If ../nova/__init__.py exists, add ../ to Python search path, so that # it will override what happens to be installed in /usr/(local/)lib/python... @@ -681,10 +681,15 @@ class NetworkCommands(object): help='Multi host') @args('--dns1', dest="dns1", metavar="", help='First DNS') @args('--dns2', dest="dns2", metavar="", help='Second DNS') + @args('--project_id', dest="project_id", metavar="", + help='Project id') + @args('--priority', dest="priority", metavar="", + help='Network interface priority') def create(self, label=None, fixed_range_v4=None, num_networks=None, network_size=None, multi_host=None, vlan_start=None, vpn_start=None, fixed_range_v6=None, gateway_v6=None, - bridge=None, bridge_interface=None, dns1=None, dns2=None): + bridge=None, bridge_interface=None, dns1=None, dns2=None, + project_id=None, priority=None): """Creates fixed ips for host by range""" # check for certain required inputs @@ -761,11 +766,14 @@ class NetworkCommands(object): bridge=bridge, bridge_interface=bridge_interface, dns1=dns1, - dns2=dns2) + dns2=dns2, + project_id=project_id, + priority=priority) def list(self): """List all created networks""" - _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" + _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s"\ + "\t%-15s\t%-15s\t-15s\t-15s" print _fmt % (_('id'), _('IPv4'), _('IPv6'), @@ -774,7 +782,9 @@ class NetworkCommands(object): _('DNS2'), _('VlanID'), _('project'), - _("uuid")) + _("uuid"), + _('priority'), + _('bridge')) for network in db.network_get_all(context.get_admin_context()): print _fmt % (network.id, network.cidr, @@ -784,18 +794,19 @@ class NetworkCommands(object): network.dns2, network.vlan, network.project_id, - network.uuid) + network.uuid, + network.priority, + network.bridge) @args('--network', dest="fixed_range", metavar='', help='Network to delete') def delete(self, fixed_range): """Deletes a network""" - network = db.network_get_by_cidr(context.get_admin_context(), \ - fixed_range) - if network.project_id is not None: - raise ValueError(_('Network must be disassociated from project %s' - ' before delete' % network.project_id)) - db.network_delete_safe(context.get_admin_context(), network.id) + + # delete the network + net_manager = utils.import_object(FLAGS.network_manager) + net_manager.delete_network(context.get_admin_context(), fixed_range) + class VmCommands(object): diff --git a/nova/db/api.py b/nova/db/api.py index 2d854f24c..9ff3a1c74 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -419,6 +419,11 @@ def virtual_interface_get_by_address(context, address): return IMPL.virtual_interface_get_by_address(context, address) +def virtual_interface_get_by_uuid(context, vif_uuid): + """Gets a virtual interface from the table filtering on vif uuid.""" + return IMPL.virtual_interface_get_by_uuid(context, vif_uuid) + + def virtual_interface_get_by_fixed_ip(context, fixed_ip_id): """Gets the virtual interface fixed_ip is associated with.""" return IMPL.virtual_interface_get_by_fixed_ip(context, fixed_ip_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 04b5405f6..d96b951a1 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -688,10 +688,8 @@ def fixed_ip_associate(context, address, instance_id, network_id=None): def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): session = get_session() with session.begin(): - network_or_none = or_(models.FixedIp.network_id == network_id, - models.FixedIp.network_id == None) fixed_ip_ref = session.query(models.FixedIp).\ - filter(network_or_none).\ + filter_by(network_id=network_id).\ filter_by(reserved=False).\ filter_by(deleted=False).\ filter_by(instance=None).\ @@ -928,6 +926,22 @@ def virtual_interface_get_by_address(context, address): return vif_ref +@require_context +def virtual_interface_get_by_uuid(context, vif_uuid): + """Gets a virtual interface from the table. + + :param vif_uuid: = the uuid of the interface you're looking to get + """ + session = get_session() + vif_ref = session.query(models.VirtualInterface).\ + filter_by(uuid=vif_uuid).\ + options(joinedload('network')).\ + options(joinedload('instance')).\ + options(joinedload('fixed_ips')).\ + first() + return vif_ref + + @require_context def virtual_interface_get_by_fixed_ip(context, fixed_ip_id): """Gets the virtual interface fixed_ip is associated with. diff --git a/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py new file mode 100644 index 000000000..e619b1fcd --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py @@ -0,0 +1,45 @@ +# 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 + +from sqlalchemy import * +from migrate import * + +from nova import log as logging +from nova import utils + +meta = MetaData() + +# Add priority column to networks table +priority = Column('priority', Integer()) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + # grab tables and (column for dropping later) + networks = Table('networks', meta, autoload=True) + + try: + networks.create_column(priority) + except Exception: + logging.error(_("priority column not added to networks table")) + raise + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + networks.drop_column(priority) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 19dc3302e..11b147802 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -560,6 +560,7 @@ class Network(BASE, NovaBase): dhcp_start = Column(String(255)) project_id = Column(String(255)) + priority = Column(Integer) host = Column(String(255)) # , ForeignKey('hosts.id')) uuid = Column(String(36)) diff --git a/nova/network/manager.py b/nova/network/manager.py index aa2a3700c..b778377a0 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -443,7 +443,7 @@ class NetworkManager(manager.SchedulerDependentManager): try: fixed_ips = kwargs.get('fixed_ips') or \ self.db.fixed_ip_get_by_instance(context, instance_id) - except exceptions.FixedIpNotFoundForInstance: + except exception.FixedIpNotFoundForInstance: fixed_ips = [] LOG.debug(_("network deallocation for instance |%s|"), instance_id, context=context) @@ -541,21 +541,23 @@ class NetworkManager(manager.SchedulerDependentManager): def _allocate_mac_addresses(self, context, instance_id, networks): """Generates mac addresses and creates vif rows in db for them.""" for network in networks: - vif = {'address': self.generate_mac_address(), + self.add_virtual_interface(context, instance_id, network['id']) + + def add_virtual_interface(self, context, instance_id, network_id): + vif = {'address': self.generate_mac_address(), 'instance_id': instance_id, - 'network_id': network['id'], + 'network_id': network_id, 'uuid': str(utils.gen_uuid())} - # try FLAG times to create a vif record with a unique mac_address - for i in range(FLAGS.create_unique_mac_address_attempts): - try: - self.db.virtual_interface_create(context, vif) - break - except exception.VirtualInterfaceCreateException: - vif['address'] = self.generate_mac_address() - else: - self.db.virtual_interface_delete_by_instance(context, + # try FLAG times to create a vif record with a unique mac_address + for i in range(FLAGS.create_unique_mac_address_attempts): + try: + return self.db.virtual_interface_create(context, vif) + except exception.VirtualInterfaceCreateException: + vif['address'] = self.generate_mac_address() + else: + self.db.virtual_interface_delete_by_instance(context, instance_id) - raise exception.VirtualInterfaceMacAddressException() + raise exception.VirtualInterfaceMacAddressException() def generate_mac_address(self): """Generate an Ethernet MAC address.""" @@ -784,6 +786,15 @@ class NetworkManager(manager.SchedulerDependentManager): self._create_fixed_ips(context, network['id']) return networks + def delete_network(self, context, fixed_range, require_disassociated=True): + + network = db.network_get_by_cidr(context, fixed_range) + + if require_disassociated and network.project_id is not None: + raise ValueError(_('Network must be disassociated from project %s' + ' before delete' % network.project_id)) + self.db.network_delete_safe(context, network.id) + @property def _bottom_reserved_ips(self): # pylint: disable=R0201 """Number of reserved ips at the bottom of the range.""" diff --git a/nova/network/quantum/__init__.py b/nova/network/quantum/__init__.py new file mode 100644 index 000000000..f7fbfb511 --- /dev/null +++ b/nova/network/quantum/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks +# 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. diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py new file mode 100644 index 000000000..a0c7dc6d8 --- /dev/null +++ b/nova/network/quantum/client.py @@ -0,0 +1,306 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Citrix Systems +# 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. +# @author: Tyler Smith, Cisco Systems + +import httplib +import json +import socket +import urllib + + +# this is a simple json-only serializer to use until +# we can just grab the standard serializer +# from the quantum library +class Serializer: + + def serialize(self, data, content_type): + try: + return json.dumps(data) + except TypeError: + pass + return json.dumps(to_primitive(data)) + + def deserialize(self, data, content_type): + return json.loads(data) + + +class api_call(object): + """A Decorator to add support for format and tenant overriding""" + def __init__(self, f): + self.f = f + + def __get__(self, instance, owner): + def with_params(*args, **kwargs): + # Temporarily set format and tenant for this request + (format, tenant) = (instance.format, instance.tenant) + + if 'format' in kwargs: + instance.format = kwargs['format'] + if 'tenant' in kwargs: + instance.tenant = kwargs['tenant'] + + ret = self.f(instance, *args) + (instance.format, instance.tenant) = (format, tenant) + return ret + return with_params + + +class Client(object): + + """A base client class - derived from Glance.BaseClient""" + + action_prefix = '/v0.1/tenants/{tenant_id}' + + """Action query strings""" + networks_path = "/networks" + network_path = "/networks/%s" + ports_path = "/networks/%s/ports" + port_path = "/networks/%s/ports/%s" + attachment_path = "/networks/%s/ports/%s/attachment" + + def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, + format="xml", testingStub=None, key_file=None, cert_file=None, + logger=None): + """ + Creates a new client to some service. + + :param host: The host where service resides + :param port: The port where service resides + :param use_ssl: True to use SSL, False to use HTTP + :param tenant: The tenant ID to make requests with + :param format: The format to query the server with + :param testingStub: A class that stubs basic server methods for tests + :param key_file: The SSL key file to use if use_ssl is true + :param cert_file: The SSL cert file to use if use_ssl is true + """ + self.host = host + self.port = port + self.use_ssl = use_ssl + self.tenant = tenant + self.format = format + self.connection = None + self.testingStub = testingStub + self.key_file = key_file + self.cert_file = cert_file + self.logger = logger + + def get_connection_type(self): + """ + Returns the proper connection type + """ + if self.testingStub: + return self.testingStub + if self.use_ssl: + return httplib.HTTPSConnection + else: + return httplib.HTTPConnection + + def do_request(self, method, action, body=None, + headers=None, params=None): + """ + Connects to the server and issues a request. + Returns the result data, or raises an appropriate exception if + HTTP status code is not 2xx + + :param method: HTTP method ("GET", "POST", "PUT", etc...) + :param body: string of data to send, or None (default) + :param headers: mapping of key/value pairs to add as headers + :param params: dictionary of key/value pairs to add to append + to action + + """ + + # Ensure we have a tenant id + if not self.tenant: + raise Exception("Tenant ID not set") + + # Add format and tenant_id + action += ".%s" % self.format + action = Client.action_prefix + action + action = action.replace('{tenant_id}', self.tenant) + + if type(params) is dict: + action += '?' + urllib.urlencode(params) + + try: + connection_type = self.get_connection_type() + headers = headers or {"Content-Type": + "application/%s" % self.format} + + # Open connection and send request, handling SSL certs + certs = {'key_file': self.key_file, 'cert_file': self.cert_file} + certs = dict((x, certs[x]) for x in certs if certs[x] != None) + + if self.use_ssl and len(certs): + c = connection_type(self.host, self.port, **certs) + else: + c = connection_type(self.host, self.port) + + if self.logger: + self.logger.debug("Quantum Client Request:\n" \ + + method + " " + action + "\n") + if body: + self.logger.debug(body) + + c.request(method, action, body, headers) + res = c.getresponse() + status_code = self.get_status_code(res) + data = res.read() + + if self.logger: + self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \ + % (str(status_code), data)) + + if status_code in (httplib.OK, + httplib.CREATED, + httplib.ACCEPTED, + httplib.NO_CONTENT): + return self.deserialize(data, status_code) + else: + raise Exception("Server returned error: %s" % res.read()) + + except (socket.error, IOError), e: + raise Exception("Unable to connect to " + "server. Got error: %s" % e) + + def get_status_code(self, response): + """ + Returns the integer status code from the response, which + can be either a Webob.Response (used in testing) or httplib.Response + """ + if hasattr(response, 'status_int'): + return response.status_int + else: + return response.status + + def serialize(self, data): + if data is None: + return None + elif type(data) is dict: + return Serializer().serialize(data, self.content_type()) + else: + raise Exception("unable to deserialize object of type = '%s'" \ + % type(data)) + + def deserialize(self, data, status_code): + if status_code == 202: + return data + return Serializer().deserialize(data, self.content_type()) + + def content_type(self, format=None): + if not format: + format = self.format + return "application/%s" % (format) + + @api_call + def list_networks(self): + """ + Fetches a list of all networks for a tenant + """ + return self.do_request("GET", self.networks_path) + + @api_call + def show_network_details(self, network): + """ + Fetches the details of a certain network + """ + return self.do_request("GET", self.network_path % (network)) + + @api_call + def create_network(self, body=None): + """ + Creates a new network + """ + body = self.serialize(body) + return self.do_request("POST", self.networks_path, body=body) + + @api_call + def update_network(self, network, body=None): + """ + Updates a network + """ + body = self.serialize(body) + return self.do_request("PUT", self.network_path % (network), body=body) + + @api_call + def delete_network(self, network): + """ + Deletes the specified network + """ + return self.do_request("DELETE", self.network_path % (network)) + + @api_call + def list_ports(self, network): + """ + Fetches a list of ports on a given network + """ + return self.do_request("GET", self.ports_path % (network)) + + @api_call + def show_port_details(self, network, port): + """ + Fetches the details of a certain port + """ + return self.do_request("GET", self.port_path % (network, port)) + + @api_call + def create_port(self, network, body=None): + """ + Creates a new port on a given network + """ + body = self.serialize(body) + return self.do_request("POST", self.ports_path % (network), body=body) + + @api_call + def delete_port(self, network, port): + """ + Deletes the specified port from a network + """ + return self.do_request("DELETE", self.port_path % (network, port)) + + @api_call + def set_port_state(self, network, port, body=None): + """ + Sets the state of the specified port + """ + body = self.serialize(body) + return self.do_request("PUT", + self.port_path % (network, port), body=body) + + @api_call + def show_port_attachment(self, network, port): + """ + Fetches the attachment-id associated with the specified port + """ + return self.do_request("GET", self.attachment_path % (network, port)) + + @api_call + def attach_resource(self, network, port, body=None): + """ + Sets the attachment-id of the specified port + """ + body = self.serialize(body) + return self.do_request("PUT", + self.attachment_path % (network, port), body=body) + + @api_call + def detach_resource(self, network, port): + """ + Removes the attachment-id of the specified port + """ + return self.do_request("DELETE", + self.attachment_path % (network, port)) diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py new file mode 100644 index 000000000..ff2b1e9d5 --- /dev/null +++ b/nova/network/quantum/fake.py @@ -0,0 +1,213 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks, Inc +# 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 exception +from nova import ipv6 +from nova import log as logging +from nova import utils +import math +from netaddr import IPNetwork + + +LOG = logging.getLogger("network.quantum.fake") + + +# this class can be used for unit functional/testing on nova, +# as it does not actually make remote calls to the Quantum service +class FakeQuantumClientConnection: + + def __init__(self): + self.nets = {} + + def get_networks_for_tenant(self, tenant_id): + net_ids = [] + for net_id, n in self.nets.items(): + if n['tenant-id'] == tenant_id: + net_ids.append(net_id) + return net_ids + + def create_network(self, tenant_id, network_name): + + uuid = str(utils.gen_uuid()) + self.nets[uuid] = {'net-name': network_name, + 'tenant-id': tenant_id, + 'ports': {}} + return uuid + + def delete_network(self, tenant_id, net_id): + if self.nets[net_id]['tenant-id'] == tenant_id: + del self.nets[net_id] + + def network_exists(self, tenant_id, net_id): + try: + return self.nets[net_id]['tenant-id'] == tenant_id + except: + return False + + def _confirm_not_attached(self, interface_id): + for n in self.nets.values(): + for p in n['ports'].values(): + if p['attachment-id'] == interface_id: + raise Exception("interface '%s' is already attached" %\ + interface_id) + + def create_and_attach_port(self, tenant_id, net_id, interface_id): + if not self.network_exists(tenant_id, net_id): + raise Exception("network %s does not exist for tenant %s" %\ + (net_id, tenant_id)) + + self._confirm_not_attached(interface_id) + uuid = str(utils.gen_uuid()) + self.nets[net_id]['ports'][uuid] = \ + {"port-state": "ACTIVE", + "attachment-id": interface_id} + + def detach_and_delete_port(self, tenant_id, net_id, port_id): + if not self.network_exists(tenant_id, net_id): + raise Exception("network %s does not exist for tenant %s" %\ + (net_id, tenant_id)) + del self.nets[net_id]['ports'][port_id] + + def get_port_by_attachment(self, tenant_id, attachment_id): + for net_id, n in self.nets.items(): + if n['tenant-id'] == tenant_id: + for port_id, p in n['ports'].items(): + if p['attachment-id'] == attachment_id: + return (net_id, port_id) + + return (None, None) + + +def get_ipam_lib(net_man): + return FakeQuantumIPAMLib() + + +class FakeQuantumIPAMLib(): + + def __init__(self): + self.subnets = {} + + def create_subnet(self, context, label, tenant_id, quantum_net_id, + cidr=None, gateway_v6=None, cidr_v6=None, + dns1=None, dns2=None): + if int(cidr.split("/")[1]) != 24: + raise Exception("fake ipam_lib only supports /24s") + v4_ips = [] + net_start = cidr[0:cidr.rfind(".") + 1] + subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) + for i in xrange(2, subnet_size - 1): + v4_ips.append({"ip": net_start + str(i), + "allocated": False, + "virtual_interface_id": None, + "instance_id": None}) + self.subnets[quantum_net_id] = {\ + "label": label, + "gateway": net_start + "1", + "netmask": "255.255.255.0", + "broadcast": net_start + "255", + "cidr": cidr, + "gateway_v6": gateway_v6, + "cidr_v6": cidr_v6, + "dns1": dns1, + "dns2": dns2, + "project_id": tenant_id, + "v4_ips": v4_ips} + + def get_network_id_by_cidr(self, context, cidr, project_id): + for net_id, s in self.subnets.items(): + if s['cidr'] == cidr or s['cidr_v6'] == cidr: + return net_id + return None + + def delete_subnets_by_net_id(self, context, net_id, project_id): + self.verify_subnet_exists(context, project_id, net_id) + del self.subnets[net_id] + + def get_project_and_global_net_ids(self, context, project_id): + net_ids = [] + for nid, s in self.subnets.items(): + if s['project_id'] == project_id or \ + s['project_id'] == None: + net_ids.append((nid, s['project_id'])) + return net_ids + + def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): + subnet = self.subnets[quantum_net_id] + for i in xrange(0, len(subnet['v4_ips'])): + ip = subnet['v4_ips'][i] + if not ip['allocated']: + subnet['v4_ips'][i] = {'ip': ip['ip'], + 'allocated': True, + 'virtual_interface_id': vif_rec['uuid'], + 'instance_id': vif_rec['instance_id']} + return + raise Exception("Unable to find available IP for net '%s'" %\ + quantum_net_id) + + def get_subnets_by_net_id(self, context, tenant_id, net_id): + self.verify_subnet_exists(context, tenant_id, net_id) + + subnet_data = self.subnets[net_id] + subnet_data_v4 = { + 'network_id': net_id, + 'cidr': subnet_data['cidr'], + 'gateway': subnet_data['gateway'], + 'broadcast': subnet_data['broadcast'], + 'netmask': subnet_data['netmask'], + 'dns1': subnet_data['dns1'], + 'dns2': subnet_data['dns2']} + subnet_data_v6 = { + 'network_id': net_id, + 'cidr': subnet_data['cidr_v6'], + 'gateway': subnet_data['gateway_v6'], + 'broadcast': None, + 'netmask': None, + 'dns1': None, + 'dns2': None} + return (subnet_data_v4, subnet_data_v6) + + def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): + self.verify_subnet_exists(context, project_id, net_id) + + subnet_data = self.subnets[net_id] + if subnet_data['cidr_v6']: + ip = ipv6.to_global(subnet_data['cidr_v6'], + "ca:fe:de:ad:be:ef", + project_id) + return [ip] + return [] + + def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): + self.verify_subnet_exists(context, project_id, net_id) + + subnet_data = self.subnets[net_id] + for ip in subnet_data['v4_ips']: + if ip['virtual_interface_id'] == vif_id: + return [ip['ip']] + return [] + + def verify_subnet_exists(self, context, tenant_id, quantum_net_id): + if quantum_net_id not in self.subnets: + raise exception.NetworkNotFound(network_id=quantum_net_id) + + def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): + s = self.subnets[net_id] + for ip in s['v4_ips']: + if ip['virtual_interface_id'] == vif_ref['id']: + ip['allocated'] = False + ip['instance_id'] = None + ip['virtual_interface_id'] = None diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py new file mode 100644 index 000000000..f712a93c4 --- /dev/null +++ b/nova/network/quantum/manager.py @@ -0,0 +1,232 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks, Inc +# 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 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.network import manager +from nova.network.quantum import quantum_connection +from nova.network.quantum import fake + +LOG = logging.getLogger("quantum_manager") + +FLAGS = flags.FLAGS + +flags.DEFINE_string('quantum_ipam_lib', + 'nova.network.quantum.nova_ipam_lib', + "Indicates underlying IP address management library") + + +class QuantumManager(manager.FlatManager): + + def __init__(self, ipam_lib=None, *args, **kwargs): + + if FLAGS.fake_network: + self.q_conn = fake.FakeQuantumClientConnection() + else: + self.q_conn = quantum_connection.QuantumClientConnection() + + if not ipam_lib: + ipam_lib = FLAGS.quantum_ipam_lib + self.ipam = utils.import_object(ipam_lib).get_ipam_lib(self) + + super(QuantumManager, self).__init__(*args, **kwargs) + + def create_networks(self, context, label, cidr, multi_host, num_networks, + network_size, cidr_v6, gateway_v6, bridge, + bridge_interface, dns1=None, dns2=None, **kwargs): + if num_networks != 1: + raise Exception("QuantumManager requires that only one" + " network is created per call") + q_tenant_id = kwargs["project_id"] or \ + FLAGS.quantum_default_tenant_id + quantum_net_id = bridge + if quantum_net_id: + if not q_conn.network_exists(q_tenant_id, quantum_net_id): + raise Exception("Unable to find existing quantum " \ + " network for tenant '%s' with net-id '%s'" % \ + (q_tenant_id, quantum_net_id)) + else: + # otherwise, create network from default quantum pool + quantum_net_id = self.q_conn.create_network(q_tenant_id, label) + + ipam_tenant_id = kwargs.get("project_id", None) + self.ipam.create_subnet(context, label, ipam_tenant_id, quantum_net_id, + cidr, gateway_v6, cidr_v6, dns1, dns2) + + def delete_network(self, context, fixed_range): + project_id = context.project_id + quantum_net_id = self.ipam.get_network_id_by_cidr( + context, fixed_range, project_id) + self.ipam.delete_subnets_by_net_id(context, quantum_net_id, + project_id) + try: + q_tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.q_conn.delete_network(q_tenant_id, quantum_net_id) + except Exception, e: + raise Exception("Unable to delete Quantum Network with " + "fixed_range = %s (ports still in use?)." % fixed_range) + + def allocate_for_instance(self, context, **kwargs): + instance_id = kwargs.pop('instance_id') + instance_type_id = kwargs['instance_type_id'] + host = kwargs.pop('host') + project_id = kwargs.pop('project_id') + LOG.debug(_("network allocations for instance %s"), instance_id) + + # if using the create-server-networks extension, 'requested_networks' + # will be defined, otherwise, just grab relevant nets from IPAM + requested_networks = kwargs.get('requested_networks') + + if requested_networks: + net_proj_pairs = [(net_id, project_id) \ + for (net_id, _i) in requested_networks] + else: + net_proj_pairs = self.ipam.get_project_and_global_net_ids(context, + project_id) + + # Create a port via quantum and attach the vif + for (net_id, project_id) in net_proj_pairs: + vif_rec = manager.FlatManager.add_virtual_interface(self, + context, instance, None) + + q_tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.q_conn.create_and_attach_port(q_tenant_id, net_id, + vif_rec['uuid']) + self.ipam.allocate_fixed_ip(context, project_id, net_id, vif_rec) + + return self.get_instance_nw_info(context, instance_id, + instance_type_id, host) + + def get_instance_nw_info(self, context, instance_id, + instance_type_id, host): + network_info = [] + project_id = context.project_id + + admin_context = context.elevated() + vifs = db.virtual_interface_get_by_instance(admin_context, + instance_id) + for vif in vifs: + q_tenant_id = project_id + ipam_tenant_id = project_id + net_id, port_id = self.q_conn.get_port_by_attachment(q_tenant_id, + vif['uuid']) + if not net_id: + q_tenant_id = FLAGS.quantum_default_tenant_id + ipam_tenant_id = None + net_id, port_id = self.q_conn.get_port_by_attachment( + q_tenant_id, vif['uuid']) + if not net_id: + raise Exception(_("No network for for virtual interface %s") %\ + vif['uuid']) + (v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context, + ipam_tenant_id, net_id) + v4_ips = self.ipam.get_v4_ips_by_interface(context, + net_id, vif['uuid'], + project_id=ipam_tenant_id) + v6_ips = self.ipam.get_v6_ips_by_interface(context, + net_id, vif['uuid'], + project_id=ipam_tenant_id) + + quantum_net_id = v4_subnet['network_id'] or v6_subnet['network_id'] + + def ip_dict(ip, subnet): + return { + "ip": ip, + "netmask": subnet["netmask"], + "enabled": "1"} + + network_dict = { + 'cidr': v4_subnet['cidr'], + 'injected': True, + 'multi_host': False} + + info = { + 'gateway': v4_subnet['gateway'], + 'dhcp_server': v4_subnet['gateway'], + 'broadcast': v4_subnet['broadcast'], + 'mac': vif['address'], + 'vif_uuid': vif['uuid'], + 'dns': [], + 'ips': [ip_dict(ip, v4_subnet) for ip in v4_ips]} + + if v6_subnet['cidr']: + network_dict['cidr_v6'] = v6_subnet['cidr'] + info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips] + # TODO(tr3buchet): handle ip6 routes here as well + if v6_subnet['gateway']: + info['gateway6'] = v6_subnet['gateway'] + + dns_dict = {} + for s in [v4_subnet, v6_subnet]: + for k in ['dns1', 'dns2']: + if s[k]: + dns_dict[s[k]] = None + info['dns'] = [d for d in dns_dict.keys()] + + network_info.append((network_dict, info)) + return network_info + + def deallocate_for_instance(self, context, **kwargs): + instance_id = kwargs.get('instance_id') + project_id = kwargs.pop('project_id', None) + + admin_context = context.elevated() + vifs = db.virtual_interface_get_by_instance(admin_context, + instance_id) + for vif_ref in vifs: + interface_id = vif_ref['uuid'] + q_tenant_id = project_id + ipam_tenant_id = project_id + (net_id, port_id) = self.q_conn.get_port_by_attachment(q_tenant_id, + interface_id) + if not net_id: + q_tenant_id = FLAGS.quantum_default_tenant_id + ipam_tenant_id = None + (net_id, port_id) = self.q_conn.get_port_by_attachment( + q_tenant_id, interface_id) + if not net_id: + LOG.error("Unable to find port with attachment: %s" % \ + (interface_id)) + continue + self.q_conn.detach_and_delete_port(q_tenant_id, + net_id, port_id) + + self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id, + net_id, vif_ref) + + self.net_manager.db.virtual_interface_delete_by_instance(admin_context, + instance_id) + self._do_trigger_security_group_members_refresh_for_instance( + instance_id) + + # validates that this tenant has quantum networks with the associated + # UUIDs. This is called by the 'os-create-server-ext' API extension + # code so that we can return an API error code to the caller if they + # request an invalid network. + def validate_networks(self, context, networks): + if networks is None: + return + + project_id = context.project_id + for (net_id, _i) in networks: + self.ipam.verify_subnet_exists(context, project_id, net_id) + if not self.q_conn.network_exists(project_id, net_id): + raise exception.NetworkNotFound(network_id=net_id) diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py new file mode 100644 index 000000000..b3955138d --- /dev/null +++ b/nova/network/quantum/melange_connection.py @@ -0,0 +1,133 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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 httplib +import socket +import urllib +import json +from nova import flags + + +FLAGS = flags.FLAGS + +flags.DEFINE_string('melange_host', + '127.0.0.1', + 'HOST for connecting to melange') + +flags.DEFINE_string('melange_port', + '9898', + 'PORT for connecting to melange') + +json_content_type = {'Content-type': "application/json"} + + +class MelangeConnection(object): + + def __init__(self, host=None, port=None, use_ssl=False): + if host is None: + host = FLAGS.melange_host + if port is None: + port = int(FLAGS.melange_port) + self.host = host + self.port = port + self.use_ssl = use_ssl + + def get(self, path, params={}, headers={}): + return self.do_request("GET", path, params=params, headers=headers) + + def post(self, path, body=None, headers={}): + return self.do_request("POST", path, body=body, headers=headers) + + def delete(self, path, headers={}): + return self.do_request("DELETE", path, headers=headers) + + def _get_connection(self): + if self.use_ssl: + return httplib.HTTPSConnection(self.host, self.port) + else: + return httplib.HTTPConnection(self.host, self.port) + + def do_request(self, method, path, body=None, headers={}, params={}): + + url = path + '.json?' + urllib.urlencode(params) + + try: + connection = self._get_connection() + connection.request(method, url, body, headers) + response = connection.getresponse() + response_str = response.read() + if response.status < 400: + return response_str + raise Exception("Server returned error: %s", response_str) + except (socket.error, IOError), e: + raise Exception("Unable to connect to " + "server. Got error: %s" % e) + + def allocate_ip(self, network_id, vif_id, + project_id=None, mac_address=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + request_body = (json.dumps(dict(network=dict(mac_address=mac_address, + tenant_id=project_id))) + if mac_address else None) + url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + "interfaces/%(vif_id)s/ip_allocations" % locals()) + response = self.post(url, body=request_body, + headers=json_content_type) + return json.loads(response)['ip_addresses'] + + def create_block(self, network_id, cidr, + project_id=None, dns1=None, dns2=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + + url = "/ipam%(tenant_scope)s/ip_blocks" % locals() + + req_params = dict(ip_block=dict(cidr=cidr, network_id=network_id, + type='private', dns1=dns1, dns2=dns2)) + self.post(url, body=json.dumps(req_params), + headers=json_content_type) + + def delete_block(self, block_id, project_id=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + + url = "/ipam%(tenant_scope)s/ip_blocks/%(block_id)s" % locals() + + self.delete(url, headers=json_content_type) + + def get_blocks(self, project_id=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + + url = "/ipam%(tenant_scope)s/ip_blocks" % locals() + + response = self.get(url, headers=json_content_type) + return json.loads(response) + + def get_allocated_ips(self, network_id, vif_id, project_id=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + + url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + "interfaces/%(vif_id)s/ip_allocations" % locals()) + + response = self.get(url, headers=json_content_type) + return json.loads(response)['ip_addresses'] + + def deallocate_ips(self, network_id, vif_id, project_id=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + + url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + "interfaces/%(vif_id)s/ip_allocations" % locals()) + + self.delete(url, headers=json_content_type) diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py new file mode 100644 index 000000000..46038c349 --- /dev/null +++ b/nova/network/quantum/melange_ipam_lib.py @@ -0,0 +1,135 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks, Inc +# 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 netaddr import IPNetwork + +from nova import flags +from nova import log as logging +from nova.network.quantum import melange_connection + +LOG = logging.getLogger("quantum_melange_ipam") + +FLAGS = flags.FLAGS + + +def get_ipam_lib(net_man): + return QuantumMelangeIPAMLib() + + +class QuantumMelangeIPAMLib: + + def __init__(self): + self.m_conn = melange_connection.MelangeConnection() + + def create_subnet(self, context, label, project_id, + quantum_net_id, cidr=None, + gateway_v6=None, cidr_v6=None, + dns1=None, dns2=None): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + if cidr: + self.m_conn.create_block(quantum_net_id, cidr, + project_id=tenant_id, + dns1=dns1, dns2=dns2) + if cidr_v6: + self.m_conn.create_block(quantum_net_id, cidr_v6, + project_id=tenant_id, + dns1=dns1, dns2=dns2) + + def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.m_conn.allocate_ip(quantum_net_id, + vif_ref['uuid'], project_id=tenant_id, + mac_address=vif_ref['address']) + + def get_network_id_by_cidr(self, context, cidr, project_id): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + all_blocks = self.m_conn.get_blocks(tenant_id) + for b in all_blocks['ip_blocks']: + if b['cidr'] == cidr: + return b['network_id'] + + def delete_subnets_by_net_id(self, context, net_id, project_id): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + all_blocks = self.m_conn.get_blocks(tenant_id) + for b in all_blocks['ip_blocks']: + if b['network_id'] == net_id: + self.m_conn.delete_block(b['id'], tenant_id) + + # get all networks with this project_id, as well as all networks + # where the project-id is not set (these are shared networks) + def get_project_and_global_net_ids(self, context, project_id): + id_proj_map = {} + if not project_id: + raise Exception("get_project_and_global_net_ids must be called" \ + " with a non-null project_id") + tenant_id = project_id + all_tenant_blocks = self.m_conn.get_blocks(tenant_id) + for b in all_tenant_blocks['ip_blocks']: + id_proj_map[b['network_id']] = tenant_id + tenant_id = FLAGS.quantum_default_tenant_id + all_provider_blocks = self.m_conn.get_blocks(tenant_id) + for b in all_provider_blocks['ip_blocks']: + id_proj_map[b['network_id']] = tenant_id + return id_proj_map.items() + + # FIXME: there must be a more efficient way to do this, + # talk to the melange folks + def get_subnets_by_net_id(self, context, project_id, net_id): + subnet_v4 = None + subnet_v6 = None + tenant_id = project_id or FLAGS.quantum_default_tenant_id + all_blocks = self.m_conn.get_blocks(tenant_id) + for b in all_blocks['ip_blocks']: + if b['network_id'] == net_id: + subnet = {'network_id': b['network_id'], + 'cidr': b['cidr'], + 'gateway': b['gateway'], + 'broadcast': b['broadcast'], + 'netmask': b['netmask'], + 'dns1': b['dns1'], + 'dns2': b['dns2']} + + if IPNetwork(b['cidr']).version == 6: + subnet_v6 = subnet + else: + subnet_v4 = subnet + return (subnet_v4, subnet_v6) + + def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): + return self.get_ips_by_interface(context, net_id, vif_id, + project_id, 4) + + def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): + return self.get_ips_by_interface(context, net_id, vif_id, + project_id, 6) + + def get_ips_by_interface(self, context, net_id, vif_id, project_id, + ip_version): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + ip_list = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id) + return [ip['address'] for ip in ip_list \ + if IPNetwork(ip['address']).version == ip_version] + + def verify_subnet_exists(self, context, project_id, quantum_net_id): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + v4_subnet, v6_subnet = self.get_subnets_by_net_id(context, tenant_id, + quantum_net_id) + return v4_subnet is not None + + def deallocate_ips_by_vif(self, context, project_id, net_id, vif_ref): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.m_conn.deallocate_ips(net_id, vif_ref['uuid'], tenant_id) diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py new file mode 100644 index 000000000..0fc74fa49 --- /dev/null +++ b/nova/network/quantum/nova_ipam_lib.py @@ -0,0 +1,152 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks, Inc +# 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 math + +#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.network import manager +from nova.network.quantum import melange_connection as melange + +LOG = logging.getLogger("quantum_nova_ipam_lib") + +FLAGS = flags.FLAGS + + +def get_ipam_lib(net_man): + return QuantumNovaIPAMLib(net_man) + + +class QuantumNovaIPAMLib: + + def __init__(self, net_manager): + self.net_manager = net_manager + + def create_subnet(self, context, label, tenant_id, + quantum_net_id, cidr=None, + gateway_v6=None, cidr_v6=None, + dns1=None, dns2=None): + print "creating subnet %s" % cidr + admin_context = context.elevated() + subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) + manager.FlatManager.create_networks(self.net_manager, + admin_context, label, cidr, + False, 1, subnet_size, cidr_v6, + gateway_v6, quantum_net_id, None, dns1, dns2) + + # now grab the network and update project_id + network = db.network_get_by_bridge(admin_context, quantum_net_id) + net = {"project_id": tenant_id} + db.network_update(admin_context, network['id'], net) + + def get_network_id_by_cidr(self, context, cidr, project_id): + admin_context = context.elevated() + network = db.network_get_by_cidr(admin_context, cidr) + if not network: + raise Exception("No network with fixed_range = %s" \ + % fixed_range) + return network['bridge'] + + def delete_subnets_by_net_id(self, context, net_id, project_id): + admin_context = context.elevated() + network = db.network_get_by_bridge(admin_context, net_id) + if not network: + raise Exception("No network with net_id = %s" % net_id) + manager.FlatManager.delete_network(self.net_manager, + admin_context, network['cidr'], + require_disassociated=False) + + def get_project_and_global_net_ids(self, context, project_id): + + # get all networks with this project_id, as well as all networks + # where the project-id is not set (these are shared networks) + admin_context = context.elevated() + networks = db.project_get_networks(admin_context, project_id, False) + networks.extend(db.project_get_networks(admin_context, None, False)) + return [(n['bridge'], n['project_id']) for n in networks] + + def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): + admin_context = context.elevated() + network = db.network_get_by_bridge(admin_context, quantum_net_id) + if network['cidr']: + address = db.fixed_ip_associate_pool(admin_context, + network['id'], + vif_rec['instance_id']) + values = {'allocated': True, + 'virtual_interface_id': vif_rec['id']} + db.fixed_ip_update(admin_context, address, values) + + def get_subnets_by_net_id(self, context, tenant_id, net_id): + n = db.network_get_by_bridge(context.elevated(), net_id) + subnet_data_v4 = { + 'network_id': n['bridge'], + 'cidr': n['cidr'], + 'gateway': n['gateway'], + 'broadcast': n['broadcast'], + 'netmask': n['netmask'], + 'dns1': n['dns1'], + 'dns2': n['dns2'] + } + subnet_data_v6 = { + 'network_id': n['bridge'], + 'cidr': n['cidr_v6'], + 'gateway': n['gateway_v6'], + 'broadcast': None, + 'netmask': None, + 'dns1': None, + 'dns2': None + } + return (subnet_data_v4, subnet_data_v6) + + def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): + vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) + fixed_ips = db.fixed_ip_get_by_virtual_interface(context, + vif_rec['id']) + return [f['address'] for f in fixed_ips] + + def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): + admin_context = context.elevated() + network = db.network_get_by_bridge(admin_context, net_id) + vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) + if network['cidr_v6']: + ip = ipv6.to_global(network['cidr_v6'], + vif_rec['address'], + project_id) + return [ip] + return [] + + def verify_subnet_exists(self, context, tenant_id, quantum_net_id): + admin_context = context.elevated() + network = db.network_get_by_bridge(admin_context, quantum_net_id) + + def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): + try: + admin_context = context.elevated() + fixed_ips = db.fixed_ip_get_by_virtual_interface(admin_context, + vif_ref['id']) + for f in fixed_ips: + db.fixed_ip_update(admin_context, f['address'], + {'allocated': False, + 'virtual_interface_id': None}) + except exception.FixedIpNotFoundForInstance: + LOG.error(_('Failed to deallocate fixed IP for vif %s' % \ + vif_ref['id'])) diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py new file mode 100644 index 000000000..3aa017bcd --- /dev/null +++ b/nova/network/quantum/quantum_connection.py @@ -0,0 +1,97 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova import flags +from nova import log as logging +from nova import utils + +from nova.network.quantum.client import Client + +LOG = logging.getLogger("nova.network.quantum") +FLAGS = flags.FLAGS + +flags.DEFINE_string('quantum_connection_host', + '127.0.0.1', + 'HOST for connecting to quantum') + +flags.DEFINE_string('quantum_connection_port', + '9696', + 'PORT for connecting to quantum') + +flags.DEFINE_string('quantum_default_tenant_id', + "default", + 'Default tenant id when creating quantum networks') + + +class QuantumClientConnection: + + def __init__(self): + self.client = Client(FLAGS.quantum_connection_host, + FLAGS.quantum_connection_port, + format="json", + logger=LOG) + + def create_network(self, tenant_id, network_name): + data = {'network': {'net-name': network_name}} + resdict = self.client.create_network(data, tenant=tenant_id) + return resdict["networks"]["network"]["id"] + + def delete_network(self, tenant_id, net_id): + self.client.delete_network(net_id, tenant=tenant_id) + + def network_exists(self, tenant_id, net_id): + try: + self.client.show_network_details(net_id, tenant=tenant_id) + except: + # FIXME: client lib should expose more granular exceptions + # so we can confirm we're getting a 404 and not some other error + return False + return True + + def create_and_attach_port(self, tenant_id, net_id, interface_id): + LOG.debug("Connecting interface %s to net %s for %s" % \ + (interface_id, net_id, tenant_id)) + port_data = {'port': {'port-state': 'ACTIVE'}} + resdict = self.client.create_port(net_id, port_data, tenant=tenant_id) + port_id = resdict["ports"]["port"]["id"] + + attach_data = {'port': {'attachment-id': interface_id}} + self.client.attach_resource(net_id, port_id, attach_data, + tenant=tenant_id) + + def detach_and_delete_port(self, tenant_id, net_id, port_id): + LOG.debug("Deleteing port %s on net %s for %s" % \ + (port_id, net_id, tenant_id)) + + self.client.detach_resource(net_id, port_id, tenant=tenant_id) + self.client.delete_port(net_id, port_id, tenant=tenant_id) + + # FIXME: this will be inefficient until API implements querying + def get_port_by_attachment(self, tenant_id, attachment_id): + + net_list_resdict = self.client.list_networks(tenant=tenant_id) + for n in net_list_resdict["networks"]: + net_id = n['id'] + port_list_resdict = self.client.list_ports(net_id, + tenant=tenant_id) + for p in port_list_resdict["ports"]: + port_id = p["id"] + port_get_resdict = self.client.show_port_attachment(net_id, + port_id, tenant=tenant_id) + if attachment_id == port_get_resdict["attachment"]: + return (net_id, port_id) + return (None, None) diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py new file mode 100644 index 000000000..378beb6ed --- /dev/null +++ b/nova/tests/test_quantum.py @@ -0,0 +1,261 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira, Inc. +# 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 context +from nova import db +from nova import exception +from nova import log as logging +from nova import test +from nova.network.quantum import manager as quantum_manager + +LOG = logging.getLogger('nova.tests.quantum_network') + +networks = [{'label': 'project1-net1', + 'injected': False, + 'multi_host': False, + 'cidr': '192.168.0.0/24', + 'cidr_v6': '2001:1db8::/64', + 'gateway_v6': '2001:1db8::1', + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': None, + 'bridge_interface': None, + 'gateway': '192.168.0.1', + 'broadcast': '192.168.0.255', + 'dns1': '192.168.0.1', + 'dns2': '192.168.0.2', + 'vlan': None, + 'host': None, + 'vpn_public_address': None, + 'project_id': 'fake_project1'}, + {'label': 'project2-net1', + 'injected': False, + 'multi_host': False, + 'cidr': '192.168.1.0/24', + 'cidr_v6': '2001:1db9::/64', + 'gateway_v6': '2001:1db9::1', + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': None, + 'bridge_interface': None, + 'gateway': '192.168.1.1', + 'broadcast': '192.168.1.255', + 'dns1': '192.168.0.1', + 'dns2': '192.168.0.2', + 'vlan': None, + 'host': None, + 'project_id': 'fake_project2', + 'vpn_public_address': '192.168.1.2'}, + {'label': "public", + 'injected': False, + 'multi_host': False, + 'cidr': '10.0.0.0/24', + 'cidr_v6': '2001:1dba::/64', + 'gateway_v6': '2001:1dba::1', + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': None, + 'bridge_interface': None, + 'gateway': '10.0.0.1', + 'broadcast': '10.0.0.255', + 'dns1': '10.0.0.1', + 'dns2': '10.0.0.2', + 'vlan': None, + 'host': None, + 'vpn_public_address': None, + 'project_id': None}, + {'label': "project2-net2", + 'injected': False, + 'multi_host': False, + 'cidr': '9.0.0.0/24', + 'cidr_v6': '2001:1dbb::/64', + 'gateway_v6': '2001:1dbb::1', + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': None, + 'bridge_interface': None, + 'gateway': '9.0.0.1', + 'broadcast': '9.0.0.255', + 'dns1': '9.0.0.1', + 'dns2': '9.0.0.2', + 'vlan': None, + 'host': None, + 'vpn_public_address': None, + 'project_id': "fake_project2"}] + + +# this is a base class to be used by all other Quantum Test classes +class QuantumTestCaseBase(object): + + def test_create_and_delete_nets(self): + self._create_nets() + self._delete_nets() + + def _create_nets(self): + for n in networks: + ctx = context.RequestContext('user1', n['project_id']) + self.net_man.create_networks(ctx, + label=n['label'], cidr=n['cidr'], + multi_host=n['multi_host'], + num_networks=1, network_size=256, cidr_v6=n['cidr_v6'], + gateway_v6=n['gateway_v6'], bridge=None, + bridge_interface=None, dns1=n['dns1'], + dns2=n['dns2'], project_id=n['project_id']) + + def _delete_nets(self): + for n in networks: + ctx = context.RequestContext('user1', n['project_id']) + self.net_man.delete_network(ctx, n['cidr']) + + def test_allocate_and_deallocate_instance_static(self): + self._create_nets() + + project_id = "fake_project1" + ctx = context.RequestContext('user1', project_id) + + instance_ref = db.api.instance_create(ctx, {}) + nw_info = self.net_man.allocate_for_instance(ctx, + instance_id=instance_ref['id'], host="", + instance_type_id=instance_ref['instance_type_id'], + project_id=project_id) + + self.assertEquals(len(nw_info), 2) + + # we don't know which order the NICs will be in until we + # introduce the notion of priority + # v4 cidr + self.assertTrue(nw_info[0][0]['cidr'].startswith("10.") or \ + nw_info[1][0]['cidr'].startswith("10.")) + self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or \ + nw_info[1][0]['cidr'].startswith("192.")) + + # v4 address + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("10.") or \ + nw_info[1][1]['ips'][0]['ip'].startswith("10.")) + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or \ + nw_info[1][1]['ips'][0]['ip'].startswith("192.")) + + # v6 cidr + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dba:") or \ + nw_info[1][0]['cidr_v6'].startswith("2001:1dba:")) + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db8:") or \ + nw_info[1][0]['cidr_v6'].startswith("2001:1db8:")) + + # v6 address + self.assertTrue(\ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dba:") or \ + nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dba:")) + self.assertTrue(\ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db8:") or \ + nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db8:")) + + self.net_man.deallocate_for_instance(ctx, + instance_id=instance_ref['id'], + project_id=project_id) + + self._delete_nets() + + def test_allocate_and_deallocate_instance_dynamic(self): + self._create_nets() + project_id = "fake_project2" + ctx = context.RequestContext('user1', project_id) + + net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id) + requested_networks = [(net_id, None) for net_id in net_ids] + + self.net_man.validate_networks(ctx, requested_networks) + + instance_ref = db.api.instance_create(ctx, {}) + nw_info = self.net_man.allocate_for_instance(ctx, + instance_id=instance_ref['id'], host="", + instance_type_id=instance_ref['instance_type_id'], + project_id=project_id, + requested_networks=requested_networks) + + self.assertEquals(len(nw_info), 2) + + # we don't know which order the NICs will be in until we + # introduce the notion of priority + # v4 cidr + self.assertTrue(nw_info[0][0]['cidr'].startswith("9.") or \ + nw_info[1][0]['cidr'].startswith("9.")) + self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or \ + nw_info[1][0]['cidr'].startswith("192.")) + + # v4 address + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("9.") or \ + nw_info[1][1]['ips'][0]['ip'].startswith("9.")) + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or \ + nw_info[1][1]['ips'][0]['ip'].startswith("192.")) + + # v6 cidr + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dbb:") or \ + nw_info[1][0]['cidr_v6'].startswith("2001:1dbb:")) + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db9:") or \ + nw_info[1][0]['cidr_v6'].startswith("2001:1db9:")) + + # v6 address + self.assertTrue(\ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dbb:") or \ + nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dbb:")) + self.assertTrue(\ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db9:") or \ + nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db9:")) + + self.net_man.deallocate_for_instance(ctx, + instance_id=instance_ref['id'], + project_id=project_id) + + self._delete_nets() + + def test_validate_bad_network(self): + ctx = context.RequestContext('user1', 'fake_project1') + self.assertRaises(exception.NetworkNotFound, + self.net_man.validate_networks, ctx, [("", None)]) + + +class QuantumFakeIPAMTestCase(QuantumTestCaseBase, test.TestCase): + + def setUp(self): + super(QuantumFakeIPAMTestCase, self).setUp() + self.net_man = quantum_manager.QuantumManager( \ + ipam_lib="nova.network.quantum.fake") + + +class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): + + def setUp(self): + super(QuantumNovaIPAMTestCase, self).setUp() + self.net_man = quantum_manager.QuantumManager( \ + ipam_lib="nova.network.quantum.nova_ipam_lib") + + # tests seem to create some networks by default, which + # don't want. So we delete them. + + ctx = context.RequestContext('user1', 'fake_project1').elevated() + for n in db.network_get_all(ctx): + db.network_delete_safe(ctx, n['id']) + +# Cannot run this unit tests auotmatically for now, as it requires +# melange to be running locally. +# +#class QuantumMelangeIPAMTestCase(QuantumTestCaseBase, test.TestCase): +# +# def setUp(self): +# super(QuantumMelangeIPAMTestCase, self).setUp() +# self.net_man = quantum_manager.QuantumManager( \ +# ipam_lib="nova.network.quantum.melange_ipam_lib") -- cgit From 01a219f7bb5fb5101bdbaa49d7e46f55d51dcfd2 Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Tue, 23 Aug 2011 05:21:47 +0000 Subject: Launchpad automatic translations update. --- po/it.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/it.po b/po/it.po index e166297f1..462254c46 100644 --- a/po/it.po +++ b/po/it.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-22 04:48+0000\n" +"X-Launchpad-Export-Date: 2011-08-23 05:21+0000\n" "X-Generator: Launchpad (build 13697)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 -- cgit From 2fbaac5e07b558d7829253915523f073b07e24d4 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 23 Aug 2011 10:57:47 -0400 Subject: PEP8 fixes --- nova/api/ec2/cloud.py | 18 ++++++++----- nova/compute/task_states.py | 63 ++++++++++++++++++++++----------------------- nova/compute/vm_states.py | 24 ++++++++--------- nova/tests/test_compute.py | 15 ++++++----- 4 files changed, 63 insertions(+), 57 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index a7a343938..c5a360426 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -93,7 +93,7 @@ _STATE_DESCRIPTION_MAP = { vm_states.VERIFY_RESIZE: 'verify_resize', vm_states.PAUSED: 'pause', vm_states.SUSPENDED: 'suspend', - vm_states.RESCUED: 'rescue' + vm_states.RESCUED: 'rescue', } @@ -104,10 +104,12 @@ def state_description_from_vm_state(vm_state): # TODO(yamahata): hypervisor dependent default device name _DEFAULT_ROOT_DEVICE_NAME = '/dev/sda1' -_DEFAULT_MAPPINGS = {'ami': 'sda1', - 'ephemeral0': 'sda2', - 'root': _DEFAULT_ROOT_DEVICE_NAME, - 'swap': 'sda3'} +_DEFAULT_MAPPINGS = { + 'ami': 'sda1', + 'ephemeral0': 'sda2', + 'root': _DEFAULT_ROOT_DEVICE_NAME, + 'swap': 'sda3', +} def _parse_block_device_mapping(bdm): @@ -1064,8 +1066,10 @@ class CloudController(object): def _format_attr_instance_initiated_shutdown_behavior(instance, result): vm_state = instance['vm_state'] - state_to_value = {vm_states.STOPPED: 'stop', - vm_states.DELETED: 'terminate'} + state_to_value = { + vm_states.STOPPED: 'stop', + vm_states.DELETED: 'terminate', + } value = state_to_value.get(vm_state) if value: result['instanceInitiatedShutdownBehavior'] = value diff --git a/nova/compute/task_states.py b/nova/compute/task_states.py index d186edc3f..5f78495ea 100644 --- a/nova/compute/task_states.py +++ b/nova/compute/task_states.py @@ -17,35 +17,34 @@ """Possible task states for instances""" -SCHEDULING='scheduling' -BLOCK_DEVICE_MAPPING='block_device_mapping' -NETWORKING='networking' -SPAWN='spawn' - -SNAPSHOTTING='snapshotting' -BACKING_UP='backing_up' -PASSWORD='password' - -RESIZE_PREP='resize_prep' -RESIZE_MIGRATING='resize_migrating' -RESIZE_MIGRATED='resize_migrated' -RESIZE_FINISH='resize_finish' -RESIZE_REVERTING='resize_reverting' -RESIZE_CONFIRMING='resize_confirming' - -REBUILDING='rebuilding' - -REBOOTING='rebooting' -HARD_REBOOTING='hard_rebooting' -PAUSING='pausing' -UNPAUSING='unpausing' -SUSPENDING='suspending' -RESUMING='resuming' - -RESCUING='rescuing' -RESCUED='rescued' -UNRESCUING='unrescuing' - -DELETING='deleting' -STOPPING='stopping' -STARTING='starting' +SCHEDULING = 'scheduling' +BLOCK_DEVICE_MAPPING = 'block_device_mapping' +NETWORKING = 'networking' +SPAWN = 'spawn' + +SNAPSHOTTING = 'snapshotting' +BACKING_UP = 'backing_up' +PASSWORD = 'password' + +RESIZE_PREP = 'resize_prep' +RESIZE_MIGRATING = 'resize_migrating' +RESIZE_MIGRATED = 'resize_migrated' +RESIZE_FINISH = 'resize_finish' +RESIZE_REVERTING = 'resize_reverting' +RESIZE_CONFIRMING = 'resize_confirming' + +REBUILDING = 'rebuilding' + +REBOOTING = 'rebooting' +HARD_REBOOTING = 'hard_rebooting' +PAUSING = 'pausing' +UNPAUSING = 'unpausing' +SUSPENDING = 'suspending' +RESUMING = 'resuming' + +RESCUING = 'rescuing' +UNRESCUING = 'unrescuing' + +DELETING = 'deleting' +STOPPING = 'stopping' +STARTING = 'starting' diff --git a/nova/compute/vm_states.py b/nova/compute/vm_states.py index 342fa905e..560e6d688 100644 --- a/nova/compute/vm_states.py +++ b/nova/compute/vm_states.py @@ -17,18 +17,18 @@ """Possible vm states for instances""" -ACTIVE='active' -BUILDING='building' -REBUILDING='rebuilding' +ACTIVE = 'active' +BUILDING = 'building' +REBUILDING = 'rebuilding' -PAUSED='paused' -SUSPENDED='suspended' -RESCUED='rescued' -DELETED='deleted' -STOPPED='stopped' +PAUSED = 'paused' +SUSPENDED = 'suspended' +RESCUED = 'rescued' +DELETED = 'deleted' +STOPPED = 'stopped' -MIGRATING='migrating' -RESIZING='resizing' -VERIFY_RESIZE='verify_resize' +MIGRATING = 'migrating' +RESIZING = 'resizing' +VERIFY_RESIZE = 'verify_resize' -ERROR='error' +ERROR = 'error' diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 2cf694d2c..11e1fd540 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -1308,14 +1308,17 @@ class ComputeTestCase(test.TestCase): """Test searching instances by state""" c = context.get_admin_context() - instance_id1 = self._create_instance({'power_state': power_state.SHUTDOWN}) + instance_id1 = self._create_instance({ + 'power_state': power_state.SHUTDOWN, + }) instance_id2 = self._create_instance({ - 'id': 2, - 'power_state': power_state.RUNNING}) + 'id': 2, + 'power_state': power_state.RUNNING, + }) instance_id3 = self._create_instance({ - 'id': 10, - 'power_state': power_state.RUNNING}) - + 'id': 10, + 'power_state': power_state.RUNNING, + }) instances = self.compute_api.get_all(c, search_opts={'power_state': power_state.SUSPENDED}) self.assertEqual(len(instances), 0) -- cgit From 657e58113d481d5c03cb3395cd714846434675f0 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 23 Aug 2011 11:01:57 -0400 Subject: Updated migration number. --- .../versions/040_update_instance_states.py | 57 ---------------------- .../versions/042_update_instance_states.py | 57 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 57 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/040_update_instance_states.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/040_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/040_update_instance_states.py deleted file mode 100644 index 07efbf90f..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/040_update_instance_states.py +++ /dev/null @@ -1,57 +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 MetaData, Table, Column, String - -meta = MetaData() - -c_task_state = Column('task_state', - 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 - - instances = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - c_state = instances.c.state - c_state.alter(name='power_state') - - c_vm_state = instances.c.state_description - c_vm_state.alter(name='vm_state') - - instances.create_column(c_task_state) - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - - instances = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - c_state = instances.c.power_state - c_state.alter(name='state') - - c_vm_state = instances.c.vm_state - c_vm_state.alter(name='state_description') - - instances.drop_column('task_state') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py new file mode 100644 index 000000000..07efbf90f --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py @@ -0,0 +1,57 @@ +# 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 MetaData, Table, Column, String + +meta = MetaData() + +c_task_state = Column('task_state', + 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 + + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_state = instances.c.state + c_state.alter(name='power_state') + + c_vm_state = instances.c.state_description + c_vm_state.alter(name='vm_state') + + instances.create_column(c_task_state) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_state = instances.c.power_state + c_state.alter(name='state') + + c_vm_state = instances.c.vm_state + c_vm_state.alter(name='state_description') + + instances.drop_column('task_state') -- cgit From da02fc6e4191bdbbb2015b78f9c3fe5045bb0460 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 23 Aug 2011 15:14:09 -0700 Subject: Fix not found exceptions to properly use ec2_ips for not found --- nova/api/ec2/__init__.py | 10 ++++++---- nova/exception.py | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 5430f443d..363dad7cd 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -392,17 +392,19 @@ class Executor(wsgi.Application): except exception.InstanceNotFound as ex: LOG.info(_('InstanceNotFound raised: %s'), unicode(ex), context=context) - return self._error(req, context, type(ex).__name__, ex.message) + ec2_id = ec2utils.id_to_ec2_id(ex.kwargs['instance_id']) + message = ex.message % {'instance_id': ec2_id} + return self._error(req, context, type(ex).__name__, message) except exception.VolumeNotFound as ex: LOG.info(_('VolumeNotFound raised: %s'), unicode(ex), context=context) - ec2_id = ec2utils.id_to_ec2_vol_id(ex.volume_id) - message = _('Volume %s not found') % ec2_id + ec2_id = ec2utils.id_to_ec2_vol_id(ex.kwargs['volume_id']) + message = ex.message % {'volume_id': ec2_id} return self._error(req, context, type(ex).__name__, message) except exception.SnapshotNotFound as ex: LOG.info(_('SnapshotNotFound raised: %s'), unicode(ex), context=context) - ec2_id = ec2utils.id_to_ec2_snap_id(ex.snapshot_id) + ec2_id = ec2utils.id_to_ec2_snap_id(ex.kwargs['snapshot_id']) message = _('Snapshot %s not found') % ec2_id return self._error(req, context, type(ex).__name__, message) except exception.NotFound as ex: diff --git a/nova/exception.py b/nova/exception.py index 66740019b..5b86059d8 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -146,6 +146,7 @@ class NovaException(Exception): message = _("An unknown exception occurred.") def __init__(self, **kwargs): + self.kwargs = kwargs try: self._error_string = self.message % kwargs -- cgit From c96a9ae1b84ee370ff5d8282a8e0531a97c5a396 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Tue, 23 Aug 2011 21:18:47 -0700 Subject: Address code review feedback from Rick and Matt --- bin/nova-manage | 11 ++-- nova/network/manager.py | 2 +- nova/network/quantum/client.py | 63 +++++++++++---------- nova/network/quantum/fake.py | 19 +++---- nova/network/quantum/manager.py | 35 ++++++------ nova/network/quantum/melange_connection.py | 6 +- nova/network/quantum/melange_ipam_lib.py | 90 +++++++++++++++--------------- nova/network/quantum/nova_ipam_lib.py | 39 ++++++------- nova/network/quantum/quantum_connection.py | 10 ++-- nova/tests/test_quantum.py | 18 +++--- 10 files changed, 146 insertions(+), 147 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 051079ef3..3a17818b2 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -804,11 +804,11 @@ class NetworkCommands(object): def quantum_list(self): """List all created networks with Quantum-relevant fields""" _fmt = "%-32s\t%-10s\t%-10s\t%s , %s" - print _fmt % ( _('uuid'), - _('project'), - _('priority'), - _('cidr_v4'), - _('cidr_v6')) + print _fmt % (_('uuid'), + _('project'), + _('priority'), + _('cidr_v4'), + _('cidr_v6')) for network in db.network_get_all(context.get_admin_context()): print _fmt % (network.uuid, network.project_id, @@ -825,7 +825,6 @@ class NetworkCommands(object): net_manager = utils.import_object(FLAGS.network_manager) net_manager.delete_network(context.get_admin_context(), fixed_range) - @args('--network', dest="fixed_range", metavar='', help='Network to modify') @args('--project', dest="project", metavar='', diff --git a/nova/network/manager.py b/nova/network/manager.py index b625e7823..426ff2f33 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -547,7 +547,7 @@ class NetworkManager(manager.SchedulerDependentManager): 'network_id': network_id, 'uuid': str(utils.gen_uuid())} # try FLAG times to create a vif record with a unique mac_address - for i in range(FLAGS.create_unique_mac_address_attempts): + for i in xrange(FLAGS.create_unique_mac_address_attempts): try: return self.db.virtual_interface_create(context, vif) except exception.VirtualInterfaceCreateException: diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 613369c7d..1927015c2 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -22,11 +22,12 @@ import socket import urllib -# this is a simple json-only serializer to use until -# we can just grab the standard serializer -# from the quantum library -class Serializer: - +class JSONSerializer(object): +""" +This is a simple json-only serializer to use until we can just grab +the standard serializer from the quantum library. +TODO(danwent): replace serializer with quantum implementation +""" def serialize(self, data, content_type): try: return json.dumps(data) @@ -40,12 +41,12 @@ class Serializer: class api_call(object): """A Decorator to add support for format and tenant overriding""" - def __init__(self, f): - self.f = f + def __init__(self, func): + self.func = func def __get__(self, instance, owner): def with_params(*args, **kwargs): - # Temporarily set format and tenant for this request + """Temporarily set format and tenant for this request""" (format, tenant) = (instance.format, instance.tenant) if 'format' in kwargs: @@ -53,14 +54,16 @@ class api_call(object): if 'tenant' in kwargs: instance.tenant = kwargs['tenant'] - ret = self.f(instance, *args) - (instance.format, instance.tenant) = (format, tenant) + ret = None + try: + ret = self.func(instance, *args) + finally: + (instance.format, instance.tenant) = (format, tenant) return ret return with_params class Client(object): - """A base client class - derived from Glance.BaseClient""" action_prefix = '/v1.0/tenants/{tenant_id}' @@ -73,8 +76,8 @@ class Client(object): attachment_path = "/networks/%s/ports/%s/attachment" def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, - format="xml", testingStub=None, key_file=None, cert_file=None, - logger=None): + format="xml", testing_stub=None, key_file=None, cert_file=None, + logger=None): """ Creates a new client to some service. @@ -83,7 +86,7 @@ class Client(object): :param use_ssl: True to use SSL, False to use HTTP :param tenant: The tenant ID to make requests with :param format: The format to query the server with - :param testingStub: A class that stubs basic server methods for tests + :param testing_stub: A class that stubs basic server methods for tests :param key_file: The SSL key file to use if use_ssl is true :param cert_file: The SSL cert file to use if use_ssl is true """ @@ -93,7 +96,7 @@ class Client(object): self.tenant = tenant self.format = format self.connection = None - self.testingStub = testingStub + self.testing_stub = testing_stub self.key_file = key_file self.cert_file = cert_file self.logger = logger @@ -102,9 +105,9 @@ class Client(object): """ Returns the proper connection type """ - if self.testingStub: - return self.testingStub - if self.use_ssl: + if self.testing_stub: + return self.testing_stub + elif self.use_ssl: return httplib.HTTPSConnection else: return httplib.HTTPConnection @@ -126,7 +129,7 @@ class Client(object): # Ensure we have a tenant id if not self.tenant: - raise Exception("Tenant ID not set") + raise Exception(_("Tenant ID not set")) # Add format and tenant_id action += ".%s" % self.format @@ -151,8 +154,8 @@ class Client(object): c = connection_type(self.host, self.port) if self.logger: - self.logger.debug("Quantum Client Request:\n" \ - + method + " " + action + "\n") + self.logger.debug(_("Quantum Client Request:\n%s %s\n" % + (method, action))) if body: self.logger.debug(body) @@ -169,14 +172,14 @@ class Client(object): httplib.CREATED, httplib.ACCEPTED, httplib.NO_CONTENT): - if data is not None and len(data): + if not data: return self.deserialize(data, status_code) else: - raise Exception("Server returned error: %s" % res.read()) + raise Exception(_("Server returned error: %s" % res.read())) except (socket.error, IOError), e: - raise Exception("Unable to connect to " - "server. Got error: %s" % e) + raise Exception(_("Unable to connect to " + "server. Got error: %s" % e)) def get_status_code(self, response): """ @@ -189,18 +192,18 @@ class Client(object): return response.status def serialize(self, data): - if data is None: + if not data: return None elif type(data) is dict: - return Serializer().serialize(data, self.content_type()) + return JSONSerializer().serialize(data, self.content_type()) else: - raise Exception("unable to deserialize object of type = '%s'" \ - % type(data)) + raise Exception(_("unable to deserialize object of type = '%s'" % + type(data))) def deserialize(self, data, status_code): if status_code == 202: return data - return Serializer().deserialize(data, self.content_type()) + return JSONSerializer().deserialize(data, self.content_type()) def content_type(self, format=None): if not format: diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index 00cdd8e08..f668edfed 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -15,9 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -import math -from netaddr import IPNetwork - from nova import exception from nova import ipv6 from nova import log as logging @@ -29,7 +26,7 @@ LOG = logging.getLogger("network.quantum.fake") # this class can be used for unit functional/testing on nova, # as it does not actually make remote calls to the Quantum service -class FakeQuantumClientConnection: +class FakeQuantumClientConnection(object): def __init__(self): self.nets = {} @@ -56,20 +53,20 @@ class FakeQuantumClientConnection: def network_exists(self, tenant_id, net_id): try: return self.nets[net_id]['tenant-id'] == tenant_id - except: + except KeyError: return False def _confirm_not_attached(self, interface_id): for n in self.nets.values(): for p in n['ports'].values(): if p['attachment-id'] == interface_id: - raise Exception("interface '%s' is already attached" %\ - interface_id) + raise Exception(_("interface '%s' is already attached" % + interface_id)) def create_and_attach_port(self, tenant_id, net_id, interface_id): if not self.network_exists(tenant_id, net_id): - raise Exception("network %s does not exist for tenant %s" %\ - (net_id, tenant_id)) + raise Exception(_("network %s does not exist for tenant %s" % + (net_id, tenant_id))) self._confirm_not_attached(interface_id) uuid = str(utils.gen_uuid()) @@ -79,8 +76,8 @@ class FakeQuantumClientConnection: def detach_and_delete_port(self, tenant_id, net_id, port_id): if not self.network_exists(tenant_id, net_id): - raise Exception("network %s does not exist for tenant %s" %\ - (net_id, tenant_id)) + raise Exception(_("network %s does not exist for tenant %s" %\ + (net_id, tenant_id))) del self.nets[net_id]['ports'][port_id] def get_port_by_attachment(self, tenant_id, attachment_id): diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index a002a3d7b..975598324 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -84,16 +84,15 @@ class QuantumManager(manager.FlatManager): In both cases, we initialize a subnet using the IPAM lib. """ if num_networks != 1: - raise Exception("QuantumManager requires that only one" - " network is created per call") - q_tenant_id = kwargs["project_id"] or \ - FLAGS.quantum_default_tenant_id + raise Exception(_("QuantumManager requires that only one" + " network is created per call")) + q_tenant_id = kwargs.get("project_id", FLAGS.quantum_default_tenant_id) quantum_net_id = uuid if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): - raise Exception("Unable to find existing quantum " \ - " network for tenant '%s' with net-id '%s'" % \ - (q_tenant_id, quantum_net_id)) + raise Exception(_("Unable to find existing quantum " \ + " network for tenant '%s' with net-id '%s'" % \ + (q_tenant_id, quantum_net_id))) else: # otherwise, create network from default quantum pool quantum_net_id = self.q_conn.create_network(q_tenant_id, label) @@ -156,18 +155,18 @@ class QuantumManager(manager.FlatManager): # Create a port via quantum and attach the vif for (quantum_net_id, project_id) in net_proj_pairs: - # FIXME: (danwent). We'd like to have the manager be completely - # decoupled from the nova networks table. - # However, other parts of nova sometimes go behind - # our back and access network data directly from the DB. So + # FIXME(danwent): We'd like to have the manager be + # completely decoupled from the nova networks table. + # However, other parts of nova sometimes go behind our + # back and access network data directly from the DB. So # for now, the quantum manager knows that there is a nova - # networks DB table and accesses it here. - # updating the virtual_interfaces table to use UUIDs would - # be one solution, but this would require significant work + # networks DB table and accesses it here. updating the + # virtual_interfaces table to use UUIDs would be one + # solution, but this would require significant work # elsewhere. admin_context = context.elevated() network_ref = db.network_get_by_uuid(admin_context, - quantum_net_id) + quantum_net_id) vif_rec = manager.FlatManager.add_virtual_interface(self, context, instance_id, network_ref['id']) @@ -177,7 +176,7 @@ class QuantumManager(manager.FlatManager): self.q_conn.create_and_attach_port(q_tenant_id, quantum_net_id, vif_rec['uuid']) self.ipam.allocate_fixed_ip(context, project_id, quantum_net_id, - vif_rec) + vif_rec) return self.get_instance_nw_info(context, instance_id, instance_type_id, host) @@ -214,8 +213,8 @@ class QuantumManager(manager.FlatManager): net_id, port_id = self.q_conn.get_port_by_attachment( q_tenant_id, vif['uuid']) if not net_id: - raise Exception(_("No network for for virtual interface %s") %\ - vif['uuid']) + raise Exception(_("No network for for virtual interface %s") % + vif['uuid']) (v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context, ipam_tenant_id, net_id) v4_ips = self.ipam.get_v4_ips_by_interface(context, diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index 2d884fa60..1ee0c29a2 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -73,10 +73,10 @@ class MelangeConnection(object): response_str = response.read() if response.status < 400: return response_str - raise Exception("Server returned error: %s", response_str) + raise Exception(_("Server returned error: %s", response_str)) except (socket.error, IOError), e: - raise Exception("Unable to connect to " - "server. Got error: %s" % e) + raise Exception(_("Unable to connect to " + "server. Got error: %s" % e)) def allocate_ip(self, network_id, vif_id, project_id=None, mac_address=None): diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 7b7baf281..24a7c5404 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -31,7 +31,7 @@ def get_ipam_lib(net_man): return QuantumMelangeIPAMLib() -class QuantumMelangeIPAMLib: +class QuantumMelangeIPAMLib(object): """ Implements Quantum IP Address Management (IPAM) interface using the Melange service, which is access using the Melange web services API. @@ -42,9 +42,9 @@ class QuantumMelangeIPAMLib: self.m_conn = melange_connection.MelangeConnection() def create_subnet(self, context, label, project_id, - quantum_net_id, priority, cidr=None, - gateway_v6=None, cidr_v6=None, - dns1=None, dns2=None): + quantum_net_id, priority, cidr=None, + gateway_v6=None, cidr_v6=None, + dns1=None, dns2=None): """ Contact Melange and create a subnet for any non-NULL IPv4 or IPv6 subnets. @@ -56,25 +56,25 @@ class QuantumMelangeIPAMLib: tenant_id = project_id or FLAGS.quantum_default_tenant_id if cidr: self.m_conn.create_block(quantum_net_id, cidr, - project_id=tenant_id, - dns1=dns1, dns2=dns2) + project_id=tenant_id, + dns1=dns1, dns2=dns2) if cidr_v6: self.m_conn.create_block(quantum_net_id, cidr_v6, project_id=tenant_id, dns1=dns1, dns2=dns2) net = {"uuid": quantum_net_id, - "project_id": project_id, - "priority": priority, - "label": label} + "project_id": project_id, + "priority": priority, + "label": label} network = self.db.network_create_safe(context, net) def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): """ Pass call to allocate fixed IP on to Melange""" tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.allocate_ip(quantum_net_id, - vif_ref['uuid'], project_id=tenant_id, - mac_address=vif_ref['address']) + vif_ref['uuid'], project_id=tenant_id, + mac_address=vif_ref['address']) def get_network_id_by_cidr(self, context, cidr, project_id): """ Find the Quantum UUID associated with a IPv4 CIDR @@ -85,7 +85,7 @@ class QuantumMelangeIPAMLib: for b in all_blocks['ip_blocks']: if b['cidr'] == cidr: return b['network_id'] - raise Exception("No network found for cidr %s" % cidr) + raise Exception(_("No network found for cidr %s" % cidr)) def delete_subnets_by_net_id(self, context, net_id, project_id): """ Find Melange block associated with the Quantum UUID, @@ -107,38 +107,38 @@ class QuantumMelangeIPAMLib: that are "global" (i.e., have no project set). Returns list sorted by 'priority'. """ - admin_context = context.elevated() - id_proj_map = {} if project_id is None: - raise Exception("get_project_and_global_net_ids must be called" \ - " with a non-null project_id") - tenant_id = project_id - all_tenant_blocks = self.m_conn.get_blocks(tenant_id) - for b in all_tenant_blocks['ip_blocks']: - id_proj_map[b['network_id']] = tenant_id - tenant_id = FLAGS.quantum_default_tenant_id - all_provider_blocks = self.m_conn.get_blocks(tenant_id) - for b in all_provider_blocks['ip_blocks']: - id_proj_map[b['network_id']] = tenant_id - - id_priority_map = {} - for net_id, project_id in id_project_map.item(): - network = db.network_get_by_uuid(admin_context, net_id) - if network is None: - del id_proj_map[net_id] - else: - id_priority_map[net_id] = network['priority'] - return sorted(id_priority_map.items(), - key=lambda x: id_priority_map[x[0]]) + raise Exception(_("get_project_and_global_net_ids must be called" + " with a non-null project_id")) + + admin_context = context.elevated() + + # Decorate with priority + priority_nets = [] + for tenant_id in (project_id, FLAGS.quantum_default_tenant_id): + blocks = self.m_conn.get_blocks(tenant_id) + for ip_block in blocks['ip_blocks']: + network_id = ip_block['network_id'] + network = db.network_get_by_uuid(admin_context, network_id) + if network: + priority = network['priority'] + priority_nets.append((priority, network_id, tenant_id)) + + # Sort by priority + priority_nets.sort() + + # Undecorate + return [(network_id, tenant_id) + for priority, network_id, tenant_id in priority_nets] def get_subnets_by_net_id(self, context, project_id, net_id): """ Returns information about the IPv4 and IPv6 subnets associated with a Quantum Network UUID. """ - # FIXME: (danwent) Melange actually returns the subnet info - # when we query for a particular interface. we may want to - # reworks the ipam_manager python API to let us take advantage of + # FIXME(danwent): Melange actually returns the subnet info + # when we query for a particular interface. We may want to + # rework the ipam_manager python API to let us take advantage of # this, as right now we have to get all blocks and cycle through # them. subnet_v4 = None @@ -148,12 +148,12 @@ class QuantumMelangeIPAMLib: for b in all_blocks['ip_blocks']: if b['network_id'] == net_id: subnet = {'network_id': b['network_id'], - 'cidr': b['cidr'], - 'gateway': b['gateway'], - 'broadcast': b['broadcast'], - 'netmask': b['netmask'], - 'dns1': b['dns1'], - 'dns2': b['dns2']} + 'cidr': b['cidr'], + 'gateway': b['gateway'], + 'broadcast': b['broadcast'], + 'netmask': b['netmask'], + 'dns1': b['dns1'], + 'dns2': b['dns2']} if IPNetwork(b['cidr']).version == 6: subnet_v6 = subnet @@ -182,8 +182,8 @@ class QuantumMelangeIPAMLib: """ tenant_id = project_id or FLAGS.quantum_default_tenant_id ip_list = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id) - return [ip['address'] for ip in ip_list \ - if IPNetwork(ip['address']).version == ip_version] + return [ip['address'] for ip in ip_list + if IPNetwork(ip['address']).version == ip_version] def verify_subnet_exists(self, context, project_id, quantum_net_id): """ Confirms that a subnet exists that is associated with the diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index ce7d3efcb..71e723cb9 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -36,7 +36,7 @@ def get_ipam_lib(net_man): return QuantumNovaIPAMLib(net_man) -class QuantumNovaIPAMLib: +class QuantumNovaIPAMLib(object): """ Implements Quantum IP Address Management (IPAM) interface using the local Nova database. This implementation is inline with how IPAM is used by other NetworkManagers. @@ -50,9 +50,9 @@ class QuantumNovaIPAMLib: self.net_manager = net_manager def create_subnet(self, context, label, tenant_id, - quantum_net_id, priority, cidr=None, - gateway_v6=None, cidr_v6=None, - dns1=None, dns2=None): + quantum_net_id, priority, cidr=None, + gateway_v6=None, cidr_v6=None, + dns1=None, dns2=None): """ Re-use the basic FlatManager create_networks method to initialize the networks and fixed_ips tables in Nova DB. @@ -60,6 +60,7 @@ class QuantumNovaIPAMLib: are needed by Quantum but not the FlatManager. """ admin_context = context.elevated() + # FIXME(danwent): Use the netaddr library here subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) networks = manager.FlatManager.create_networks(self.net_manager, admin_context, label, cidr, @@ -67,12 +68,12 @@ class QuantumNovaIPAMLib: gateway_v6, quantum_net_id, None, dns1, dns2) if len(networks) != 1: - raise Exception("Error creating network entry") + raise Exception(_("Error creating network entry")) network = networks[0] net = {"project_id": tenant_id, - "priority": priority, - "uuid": quantum_net_id} + "priority": priority, + "uuid": quantum_net_id} db.network_update(admin_context, network['id'], net) def get_network_id_by_cidr(self, context, cidr, project_id): @@ -80,7 +81,7 @@ class QuantumNovaIPAMLib: admin_context = context.elevated() network = db.network_get_by_cidr(admin_context, cidr) if not network: - raise Exception("No network with fixed_range = %s" % fixed_range) + raise Exception(_("No network with fixed_range = %s" % fixed_range)) return network['uuid'] def delete_subnets_by_net_id(self, context, net_id, project_id): @@ -90,10 +91,10 @@ class QuantumNovaIPAMLib: admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, net_id) if not network: - raise Exception("No network with net_id = %s" % net_id) + raise Exception(_("No network with net_id = %s" % net_id)) manager.FlatManager.delete_network(self.net_manager, - admin_context, network['cidr'], - require_disassociated=False) + admin_context, network['cidr'], + require_disassociated=False) def get_project_and_global_net_ids(self, context, project_id): """ Fetches all networks associated with this project, or @@ -118,8 +119,8 @@ class QuantumNovaIPAMLib: network = db.network_get_by_uuid(admin_context, quantum_net_id) if network['cidr']: address = db.fixed_ip_associate_pool(admin_context, - network['id'], - vif_rec['instance_id']) + network['id'], + vif_rec['instance_id']) values = {'allocated': True, 'virtual_interface_id': vif_rec['id']} db.fixed_ip_update(admin_context, address, values) @@ -186,10 +187,10 @@ class QuantumNovaIPAMLib: admin_context = context.elevated() fixed_ips = db.fixed_ip_get_by_virtual_interface(admin_context, vif_ref['id']) - for f in fixed_ips: - db.fixed_ip_update(admin_context, f['address'], - {'allocated': False, - 'virtual_interface_id': None}) + for fixed_ip in fixed_ips: + db.fixed_ip_update(admin_context, fixed_ip['address'], + {'allocated': False, + 'virtual_interface_id': None}) except exception.FixedIpNotFoundForInstance: - LOG.error(_('No fixed IPs to deallocate for vif %s' % \ - vif_ref['id'])) + LOG.error(_('No fixed IPs to deallocate for vif %s' % + vif_ref['id'])) diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index bd3592c2c..e2218c68d 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -37,7 +37,7 @@ flags.DEFINE_string('quantum_default_tenant_id', 'Default tenant id when creating quantum networks') -class QuantumClientConnection: +class QuantumClientConnection(object): """ Abstracts connection to Quantum service into higher level operations performed by the QuantumManager. @@ -71,7 +71,7 @@ class QuantumClientConnection: try: self.client.show_network_details(net_id, tenant=tenant_id) except: - # FIXME: (danwent) client lib should expose granular exceptions + # FIXME(danwent): client lib should expose granular exceptions # so we can confirm we're getting a 404 and not some other error return False return True @@ -81,8 +81,8 @@ class QuantumClientConnection: status to ACTIVE to enable traffic, and attaches the vNIC with the specified interface-id. """ - LOG.debug("Connecting interface %s to net %s for %s" % \ - (interface_id, net_id, tenant_id)) + LOG.debug(_("Connecting interface %s to net %s for %s" % + (interface_id, net_id, tenant_id))) port_data = {'port': {'state': 'ACTIVE'}} resdict = self.client.create_port(net_id, port_data, tenant=tenant_id) port_id = resdict["port"]["id"] @@ -103,7 +103,7 @@ class QuantumClientConnection: """ Given a tenant, search for the Quantum network and port UUID that has the specified interface-id attachment. """ - # FIXME: (danwent) this will be inefficient until the Quantum + # FIXME(danwent): this will be inefficient until the Quantum # API implements querying a port by the interface-id net_list_resdict = self.client.list_networks(tenant=tenant_id) for n in net_list_resdict["networks"]: diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 3efdba910..2cc83adf1 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -155,9 +155,9 @@ class QuantumTestCaseBase(object): self.assertTrue(nw_info[1][0]['cidr_v6'].startswith("2001:1db8:")) # v6 address - self.assertTrue(\ + self.assertTrue( nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dba:")) - self.assertTrue(\ + self.assertTrue( nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db8:")) self.net_man.deallocate_for_instance(ctx, @@ -233,24 +233,24 @@ class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): self.net_man = quantum_manager.QuantumManager( \ ipam_lib="nova.network.quantum.nova_ipam_lib") - # tests seem to create some networks by default, which - # don't want. So we delete them. + # Tests seem to create some networks by default, which + # we don't want. So we delete them. ctx = context.RequestContext('user1', 'fake_project1').elevated() for n in db.network_get_all(ctx): db.network_delete_safe(ctx, n['id']) - # I've found that other unit tests have a nasty habit of - # of creating fixed IPs and not cleaning up, which can - # confuse these tests, so we clean them all. + # NOTE(danwent): I've found that other unit tests have a nasty + # habit of of creating fixed IPs and not cleaning up, which + # can confuse these tests, so we clean them all. session = get_session() result = session.query(models.FixedIp).all() with session.begin(): for fip_ref in result: session.delete(fip_ref) -# Cannot run this unit tests auotmatically for now, as it requires -# melange to be running locally. +# FIXME(danwent): Cannot run this unit tests automatically for now, as +# it requires melange to be running locally. # #class QuantumMelangeIPAMTestCase(QuantumTestCaseBase, test.TestCase): # -- cgit From f95d187e7b9f6f1c3d244668cc4aec49339ed169 Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Wed, 24 Aug 2011 04:47:59 +0000 Subject: Launchpad automatic translations update. --- po/cs.po | 26 +--- po/de.po | 44 +----- po/es.po | 484 +----------------------------------------------------------- po/ja.po | 479 +---------------------------------------------------------- po/pt_BR.po | 156 +------------------- po/ru.po | 126 +--------------- po/tl.po | 13 +- po/uk.po | 42 +----- 8 files changed, 31 insertions(+), 1339 deletions(-) diff --git a/po/cs.po b/po/cs.po index 07bdf1928..561c71a4b 100644 --- a/po/cs.po +++ b/po/cs.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-02-07 12:45+0000\n" -"Last-Translator: David Pravec \n" +"PO-Revision-Date: 2011-08-23 11:22+0000\n" +"Last-Translator: Thierry Carrez \n" "Language-Team: Czech \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-03 04:43+0000\n" -"X-Generator: Launchpad (build 13573)\n" +"X-Launchpad-Export-Date: 2011-08-24 04:47+0000\n" +"X-Generator: Launchpad (build 13697)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -2789,21 +2789,3 @@ msgstr "" #, python-format msgid "Removing user %(user)s from project %(project)s" msgstr "" - -#, python-format -#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds." -#~ msgstr "AMQP server na %s:%d není dosažitelný. Zkusím znovu za %d sekund." - -#, python-format -#~ msgid "" -#~ "%s\n" -#~ "Command: %s\n" -#~ "Exit code: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" -#~ msgstr "" -#~ "%s\n" -#~ "Příkaz: %s\n" -#~ "Vrácená hodnota: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" diff --git a/po/de.po b/po/de.po index 1f652c373..6d8c1372c 100644 --- a/po/de.po +++ b/po/de.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-06-06 07:58+0000\n" -"Last-Translator: Christian Berendt \n" +"PO-Revision-Date: 2011-08-23 11:23+0000\n" +"Last-Translator: Thierry Carrez \n" "Language-Team: German \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" -"X-Generator: Launchpad (build 13573)\n" +"X-Launchpad-Export-Date: 2011-08-24 04:47+0000\n" +"X-Generator: Launchpad (build 13697)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -2798,42 +2798,6 @@ msgstr "" msgid "Removing user %(user)s from project %(project)s" msgstr "" -#, python-format -#~ msgid "" -#~ "%s\n" -#~ "Command: %s\n" -#~ "Exit code: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" -#~ msgstr "" -#~ "%s\n" -#~ "Kommando: %s\n" -#~ "Exit Code: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" - -#, python-format -#~ msgid "(%s) publish (key: %s) %s" -#~ msgstr "(%s) öffentlich (Schlüssel: %s) %s" - -#, python-format -#~ msgid "Getting from %s: %s" -#~ msgstr "Beziehe von %s: %s" - -#, python-format -#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds." -#~ msgstr "" -#~ "Der AMQP server %s:%d ist nicht erreichbar. Erneuter Versuch in %d Sekunden." - -#, python-format -#~ msgid "volume %s: creating lv of size %sG" -#~ msgstr "Volume %s: erstelle LV mit %sG" - -#, python-format -#~ msgid "Data store %s is unreachable. Trying again in %d seconds." -#~ msgstr "" -#~ "Datastore %s ist nicht erreichbar. Versuche es erneut in %d Sekunden." - #~ msgid "Full set of FLAGS:" #~ msgstr "Alle vorhandenen FLAGS:" diff --git a/po/es.po b/po/es.po index 7371eae8c..d50269378 100644 --- a/po/es.po +++ b/po/es.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-08-01 03:23+0000\n" -"Last-Translator: Juan Alfredo Salas Santillana \n" +"PO-Revision-Date: 2011-08-23 11:22+0000\n" +"Last-Translator: Thierry Carrez \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" -"X-Generator: Launchpad (build 13573)\n" +"X-Launchpad-Export-Date: 2011-08-24 04:47+0000\n" +"X-Generator: Launchpad (build 13697)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -2838,221 +2838,16 @@ msgstr "Agregando usuario %(user)s al proyecto %(project)s" msgid "Removing user %(user)s from project %(project)s" msgstr "Eliminando el usuario %(user)s del proyecto %(project)s" -#, python-format -#~ msgid "" -#~ "%s\n" -#~ "Command: %s\n" -#~ "Exit code: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" -#~ msgstr "" -#~ "%s\n" -#~ "Comando: %s\n" -#~ "Código de salida: %s\n" -#~ "Stdout: %s\n" -#~ "Stderr: %r" - -#, python-format -#~ msgid "(%s) publish (key: %s) %s" -#~ msgstr "(%s) públicar (clave: %s) %s" - -#, python-format -#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds." -#~ msgstr "" -#~ "El servidor AMQP en %s:%d no se puede alcanzar. Se reintentará en %d " -#~ "segundos." - -#, python-format -#~ msgid "Binding %s to %s with key %s" -#~ msgstr "Asociando %s a %s con clave %s" - -#, python-format -#~ msgid "Getting from %s: %s" -#~ msgstr "Obteniendo desde %s: %s" - -#, python-format -#~ msgid "Starting %s node" -#~ msgstr "Inciando nodo %s" - -#, python-format -#~ msgid "Data store %s is unreachable. Trying again in %d seconds." -#~ msgstr "" -#~ "El almacen de datos %s es inalcanzable. Reintentandolo en %d segundos." - #, python-format #~ msgid "Serving %s" #~ msgstr "Sirviendo %s" -#, python-format -#~ msgid "Couldn't get IP, using 127.0.0.1 %s" -#~ msgstr "No puedo obtener IP, usando 127.0.0.1 %s" - -#, python-format -#~ msgid "" -#~ "Access key %s has had %d failed authentications and will be locked out for " -#~ "%d minutes." -#~ msgstr "" -#~ "La clave de acceso %s ha tenido %d fallos de autenticación y se bloqueará " -#~ "por %d minutos." - -#, python-format -#~ msgid "arg: %s\t\tval: %s" -#~ msgstr "arg: %s \t \t val: %s" - -#, python-format -#~ msgid "Authenticated Request For %s:%s)" -#~ msgstr "Solicitud de autenticación para %s:%s" - -#, python-format -#~ msgid "Adding role %s to user %s for project %s" -#~ msgstr "Añadiendo rol %s al usuario %s para el proyecto %s" - -#, python-format -#~ msgid "Removing role %s from user %s for project %s" -#~ msgstr "Eliminando rol %s del usuario %s para el proyecto %s" - -#, python-format -#~ msgid "Unauthorized request for controller=%s and action=%s" -#~ msgstr "Solicitud no autorizada para controller=%s y action=%s" - -#, python-format -#~ msgid "Getting x509 for user: %s on project: %s" -#~ msgstr "Obteniendo x509 para el usuario: %s en el proyecto %s" - -#, python-format -#~ msgid "Create project %s managed by %s" -#~ msgstr "Creación del proyecto %s gestionada por %s" - -#, python-format -#~ msgid "Removing user %s from project %s" -#~ msgstr "Eliminando usuario %s del proyecto %s" - -#, python-format -#~ msgid "Adding user %s to project %s" -#~ msgstr "Añadiendo usuario %s al proyecto %s" - -#, python-format -#~ msgid "Unsupported API request: controller = %s,action = %s" -#~ msgstr "Solicitud de API no soportada: controller=%s,action=%s" - -#, python-format -#~ msgid "Associate address %s to instance %s" -#~ msgstr "Asociar dirección %s a la instancia %s" - -#, python-format -#~ msgid "Attach volume %s to instacne %s at %s" -#~ msgstr "Asociar volumen %s a la instancia %s en %s" - -#, python-format -#~ msgid "Registered image %s with id %s" -#~ msgstr "Registrada imagen %s con id %s" - -#, python-format -#~ msgid "User %s is already a member of the group %s" -#~ msgstr "El usuario %s ya es miembro de el grupo %s" - -#, python-format -#~ msgid "User %s is not a member of project %s" -#~ msgstr "El usuario %s no es miembro del proyecto %s" - -#, python-format -#~ msgid "failed authorization: no project named %s (user=%s)" -#~ msgstr "" -#~ "fallo de autorización: no existe proyecto con el nombre %s (usuario=%s)" - -#, python-format -#~ msgid "Failed authorization: user %s not admin and not member of project %s" -#~ msgstr "" -#~ "Fallo de autorización: el usuario %s no es administrador y no es miembro del " -#~ "proyecto %s" - -#, python-format -#~ msgid "Created user %s (admin: %r)" -#~ msgstr "Creado usuario %s (administrador: %r)" - -#, python-format -#~ msgid "Created project %s with manager %s" -#~ msgstr "Proyecto %s creado con administrador %s" - -#, python-format -#~ msgid "Removing role %s from user %s on project %s" -#~ msgstr "Eliminando rol %s al usuario %s en el proyecto %s" - -#, python-format -#~ msgid "Adding role %s to user %s in project %s" -#~ msgstr "Añadiendo rol %s al usuario %s en el proyecto %s" - -#, python-format -#~ msgid "Remove user %s from project %s" -#~ msgstr "Eliminar usuario %s del proyecto %s" - -#, python-format -#~ msgid "Admin status set to %r for user %s" -#~ msgstr "El estado del administrador se ha fijado a %r para el usuario %s" - -#, python-format -#~ msgid "Going to try and terminate %s" -#~ msgstr "Se va a probar y terminar %s" - -#, python-format -#~ msgid "Casting to scheduler for %s/%s's instance %s" -#~ msgstr "Llamando al planificar para %s/%s insntancia %s" - -#, python-format -#~ msgid "Quota exceeeded for %s, tried to run %s instances" -#~ msgstr "Quota superada por %s, intentando lanzar %s instancias" - -#, python-format -#~ msgid "check_instance_lock: arguments: |%s| |%s| |%s|" -#~ msgstr "check_instance_lock: arguments: |%s| |%s| |%s|" - -#, python-format -#~ msgid "Input partition size not evenly divisible by sector size: %d / %d" -#~ msgstr "" -#~ "El tamaño de la partición de entrada no es divisible de forma uniforme por " -#~ "el tamaño del sector: %d / %d" - -#, python-format -#~ msgid "Bytes for local storage not evenly divisible by sector size: %d / %d" -#~ msgstr "" -#~ "Los bytes del almacenamiento local no son divisibles de forma uniforme por " -#~ "el tamaño del sector: %d / %d" - -#, python-format -#~ msgid "volume %s: creating lv of size %sG" -#~ msgstr "volumen %s: creando lv de tamaño %sG" - -#, python-format -#~ msgid "Disassociating address %s" -#~ msgstr "Desasociando la dirección %s" - -#, python-format -#~ msgid "trying to reboot a non-running instance: %s (state: %s excepted: %s)" -#~ msgstr "" -#~ "intentando reiniciar una instancia que no está en ejecución: %s (estado: %s " -#~ "esperado: %s)" - -#, python-format -#~ msgid "" -#~ "trying to snapshot a non-running instance: %s (state: %s excepted: %s)" -#~ msgstr "" -#~ "intentando crear un snapshot de una instancia que no está en ejecución: %s " -#~ "(estado: %s esperado: %s)" - -#, python-format -#~ msgid "Detach volume %s from mountpoint %s on instance %s" -#~ msgstr "Desvinculando volumen %s del punto de montaje %s en la instancia %s" - #~ msgid "unexpected exception getting connection" #~ msgstr "excepción inexperada al obtener la conexión" #~ msgid "unexpected error during update" #~ msgstr "error inesperado durante la actualización" -#, python-format -#~ msgid "Cannot get blockstats for \"%s\" on \"%s\"" -#~ msgstr "No puedo obtener estadísticas del bloque para \"%s\" en \"%s\"" - #, python-format #~ msgid "updating %s..." #~ msgstr "actualizando %s..." @@ -3061,285 +2856,14 @@ msgstr "Eliminando el usuario %(user)s del proyecto %(project)s" #~ msgid "Found instance: %s" #~ msgstr "Encontrada interfaz: %s" -#, python-format -#~ msgid "Cannot get ifstats for \"%s\" on \"%s\"" -#~ msgstr "No puedo obtener estadísticas de la interfaz para \"%s\" en \"%s\"" - -#, python-format -#~ msgid "No instance for id %s" -#~ msgstr "No hay instancia con id %s" - -#, python-format -#~ msgid "no keypair for user %s, name %s" -#~ msgstr "no hay par de claves para el usuario %s, nombre %s" - -#, python-format -#~ msgid "No service for %s, %s" -#~ msgstr "No hay servicio para %s, %s" - -#, python-format -#~ msgid "No volume for id %s" -#~ msgstr "No hay volumen para el id %s" - -#, python-format -#~ msgid "No security group named %s for project: %s" -#~ msgstr "No hay un grupo de seguridad con nombre %s para el proyecto: %s" - -#, python-format -#~ msgid "Parallax returned HTTP error %d from request for /images/detail" -#~ msgstr "" -#~ "Parallax ha devuelto un error HTTP %d para la petición para /images/detail" - -#, python-format -#~ msgid "Parallax returned HTTP error %d from request for /images" -#~ msgstr "Parallax ha devuelto un error HTTP %d a la petición para /images" - -#, python-format -#~ msgid "IP %s leased to bad mac %s vs %s" -#~ msgstr "IP %s asociada a una mac incorrecta %s vs %s" - -#, python-format -#~ msgid "Unauthorized attempt to get object %s from bucket %s" -#~ msgstr "Intento no autorizado de obtener el objeto %s en el cubo %s" - -#, python-format -#~ msgid "Getting object: %s / %s" -#~ msgstr "Obteniendo objeto: %s / %s" - -#, python-format -#~ msgid "Putting object: %s / %s" -#~ msgstr "Colocando objeto: %s / %s" - -#, python-format -#~ msgid "Unauthorized attempt to upload object %s to bucket %s" -#~ msgstr "Intento no autorizado de subir el objeto %s al cubo %s" - -#, python-format -#~ msgid "Deleting object: %s / %s" -#~ msgstr "Eliminando objeto: %s / %s" - -#, python-format -#~ msgid "Toggling publicity flag of image %s %r" -#~ msgstr "Cambiando los atributos de publicidad de la imagen %s %r" - -#, python-format -#~ msgid "Creating disk for %s by attaching disk file %s" -#~ msgstr "" -#~ "Creando disco para %s a través de la asignación del fichero de disco %s" - -#, python-format -#~ msgid "WMI job succeeded: %s, Elapsed=%s " -#~ msgstr "Trabajo WMI ha tenido exito: %s, Transcurrido=%s " - -#, python-format -#~ msgid "Created switch port %s on switch %s" -#~ msgstr "Creado puerto %s en el switch %s" - -#, python-format -#~ msgid "instance %s: deleting instance files %s" -#~ msgstr "instancia %s: eliminando los ficheros de la instancia %s" - -#, python-format -#~ msgid "Finished retreving %s -- placed in %s" -#~ msgstr "Finalizada la obtención de %s -- coloado en %s" - -#, python-format -#~ msgid "Failed to change vm state of %s to %s" -#~ msgstr "Fallo al cambiar el estado de la vm de %s a %s" - -#, python-format -#~ msgid "Successfully changed vm state of %s to %s" -#~ msgstr "Cambio de estado de la vm con éxito de %s a %s" - -#, python-format -#~ msgid "" -#~ "Got Info for vm %s: state=%s, mem=%s, num_cpu=%s, " -#~ "cpu_time=%s" -#~ msgstr "" -#~ "Obtenida información para vm %s: state=%s, mem=%s, num_cpu=%s, cpu_time=%s" - -#, python-format -#~ msgid "instance %s: ignoring error injecting data into image %s (%s)" -#~ msgstr "" -#~ "instancia %s: ignorando el error al inyectar datos en la imagen %s (%s)" - -#, python-format -#~ msgid "Contents of file %s: %r" -#~ msgstr "Contenidos del fichero %s: %r" - -#, python-format -#~ msgid "instance %s: injecting net into image %s" -#~ msgstr "instancia %s: inyectando red en la imagen %s" - -#, python-format -#~ msgid "instance %s: injecting key into image %s" -#~ msgstr "instancia %s: inyectando clave en la imagen %s" - -#, python-format -#~ msgid "data: %r, fpath: %r" -#~ msgstr "datos: %r, fpath: %r" - -#, python-format -#~ msgid "Task [%s] %s status: %s %s" -#~ msgstr "Tarea [%s] %s estado: %s %s" - -#, python-format -#~ msgid "Task [%s] %s status: success %s" -#~ msgstr "Tarea [%s] %s estado: éxito %s" - -#, python-format -#~ msgid "Calling %s %s" -#~ msgstr "Llamando %s %s" - -#, python-format -#~ msgid "%s: _db_content => %s" -#~ msgstr "%s: _db_content => %s" - -#, python-format -#~ msgid "Created VBD %s for VM %s, VDI %s." -#~ msgstr "Creado VBD %s for VM %s, VDI %s." - -#, python-format -#~ msgid "Creating VBD for VM %s, VDI %s ... " -#~ msgstr "Creando VBD para VM %s, VDI %s... " - -#, python-format -#~ msgid "Created VIF %s for VM %s, network %s." -#~ msgstr "Creado VIF %s para VM %s, red %s." - -#, python-format -#~ msgid "Creating VIF for VM %s, network %s." -#~ msgstr "Creando VIF para VM %s, red %s." - -#, python-format -#~ msgid "Created VM %s as %s." -#~ msgstr "Creada VM %s cómo %s" - -#, python-format -#~ msgid "Asking xapi to upload %s as '%s'" -#~ msgstr "Solicitando a xapi la subida de %s cómo %s'" - -#, python-format -#~ msgid "VHD %s has parent %s" -#~ msgstr "VHD %s tiene cómo padre a %s" - -#, python-format -#~ msgid "Asking xapi to fetch %s as %s" -#~ msgstr "Solicitando a xapi obtener %s cómo %s" - #, python-format #~ msgid "PV Kernel in VDI:%d" #~ msgstr "PV Kernel en VDI:%d" -#, python-format -#~ msgid "Unexpected number of VDIs (%s) found for VM %s" -#~ msgstr "Número no esperado de VDIs (%s) encontrados para VM %s" - -#, python-format -#~ msgid "Parent %s doesn't match original parent %s, waiting for coalesce..." -#~ msgstr "" -#~ "El padre %s no concuerda con el padre original %s, esperando la unión..." - -#, python-format -#~ msgid "suspend: instance not present %s" -#~ msgstr "suspendido: instancia no encontrada: %s" - -#, python-format -#~ msgid "Introduced %s as %s." -#~ msgstr "Introducido %s cómo %s." - -#, python-format -#~ msgid "resume: instance not present %s" -#~ msgstr "reanudar: instancia no encontrada %s" - -#, python-format -#~ msgid "Instance not found %s" -#~ msgstr "instancia no encontrada %s" - -#, python-format -#~ msgid "Ignoring exception %s when getting PBDs for %s" -#~ msgstr "Ignorando excepción %s al obtener PBDs de %s" - -#, python-format -#~ msgid "Unable to create VDI on SR %s for instance %s" -#~ msgstr "Inpoisble crear VDI en SR %s para la instancia %s" - -#, python-format -#~ msgid "Unable to obtain target information %s, %s" -#~ msgstr "Imposible obtener información del destino %s, %s" - -#, python-format -#~ msgid "Ignoring exception %s when forgetting SR %s" -#~ msgstr "Ignorando excepción %s al olvidar SR %s" - -#, python-format -#~ msgid "Ignoring exception %s when unplugging PBD %s" -#~ msgstr "Ignorando excepción %s al desconectar PBD %s" - -#, python-format -#~ msgid "Attach_volume: %s, %s, %s" -#~ msgstr "Attach_volume: %s, %s, %s" - -#, python-format -#~ msgid "Unable to use SR %s for instance %s" -#~ msgstr "Imposible utilizar SR %s para la instancia %s" - -#, python-format -#~ msgid "Mountpoint %s attached to instance %s" -#~ msgstr "Punto de montaje %s unido a la instancia %s" - -#, python-format -#~ msgid "Detach_volume: %s, %s" -#~ msgstr "Detach_volume: %s, %s" - -#, python-format -#~ msgid "Mountpoint %s detached from instance %s" -#~ msgstr "Punto d emontaje %s desasociado de la instancia %s" - -#, python-format -#~ msgid "Quota exceeeded for %s, tried to create %sG volume" -#~ msgstr "Quota excedida para %s, intentando crear el volumen %sG" - #, python-format #~ msgid "Volume quota exceeded. You cannot create a volume of size %s" #~ msgstr "Quota de volumen superada. No puedes crear un volumen de tamaño %s" -#, python-format -#~ msgid "instance %s: attach failed %s, removing" -#~ msgstr "instalación %s: asociación fallida %s, eliminando" - -#, python-format -#~ msgid "instance %s: attaching volume %s to %s" -#~ msgstr "instancia %s: asociando volumen %s a %s" - -#, python-format -#~ msgid "Snapshotting VM %s with label '%s'..." -#~ msgstr "Creando snapshot de la VM %s con la etiqueta '%s'..." - -#, python-format -#~ msgid "Created snapshot %s from VM %s." -#~ msgstr "Creando snapshot %s de la VM %s" - -#, python-format -#~ msgid "Unable to Snapshot %s: %s" -#~ msgstr "Incapaz de realizar snapshot %s: %s" - -#, python-format -#~ msgid "Adding sitewide role %s to user %s" -#~ msgstr "Añadiendo rol global %s al usuario %s" - -#, python-format -#~ msgid "Removing sitewide role %s from user %s" -#~ msgstr "Eliminando rol global %s del usuario %s" - -#, python-format -#~ msgid "Del: disk %s vm %s" -#~ msgstr "Del: disco %s vm %s" - -#, python-format -#~ msgid "Spawning VM %s created %s." -#~ msgstr "Iniciando VM %s creado %s." - #~ msgid "No such process" #~ msgstr "No existe el proceso" diff --git a/po/ja.po b/po/ja.po index 179302b55..8bc282ec0 100644 --- a/po/ja.po +++ b/po/ja.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-05-10 10:26+0000\n" -"Last-Translator: Akira YOSHIYAMA \n" +"PO-Revision-Date: 2011-08-23 11:22+0000\n" +"Last-Translator: Thierry Carrez \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" -"X-Generator: Launchpad (build 13573)\n" +"X-Launchpad-Export-Date: 2011-08-24 04:47+0000\n" +"X-Generator: Launchpad (build 13697)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -2840,44 +2840,6 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s ã«è¿½åŠ ã—ã¾ã™ msgid "Removing user %(user)s from project %(project)s" msgstr "ユーザ %(user)s をプロジェクト %(project)s ã‹ã‚‰å‰Šé™¤ã—ã¾ã™ã€‚" -#, python-format -#~ msgid "" -#~ "%s\n" -#~ "Command: %s\n" -#~ "Exit code: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" -#~ msgstr "" -#~ "%s\n" -#~ "コマンド: %s\n" -#~ "終了コード: %s\n" -#~ "標準出力: %r\n" -#~ "標準エラー出力: %r" - -#, python-format -#~ msgid "(%s) publish (key: %s) %s" -#~ msgstr "(%s) パブリッシュ (key: %s) %s" - -#, python-format -#~ msgid "Binding %s to %s with key %s" -#~ msgstr "%s ã‚’ %s ã«ã‚­ãƒ¼ %s ã§ãƒã‚¤ãƒ³ãƒ‰ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Getting from %s: %s" -#~ msgstr "%s ã‹ã‚‰ %s ã‚’å–å¾—" - -#, python-format -#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds." -#~ msgstr "AMQPサーム%s:%d ã«æŽ¥ç¶šã§ãã¾ã›ã‚“。 %d 秒後ã«å†åº¦è©¦ã¿ã¾ã™ã€‚" - -#, python-format -#~ msgid "Starting %s node" -#~ msgstr "ノード %s ã‚’é–‹å§‹ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Data store %s is unreachable. Trying again in %d seconds." -#~ msgstr "データストア %s ã«æŽ¥ç¶šã§ãã¾ã›ã‚“。 %d 秒後ã«å†æŽ¥ç¶šã—ã¾ã™ã€‚" - #, python-format #~ msgid "Serving %s" #~ msgstr "%s サービスã®é–‹å§‹" @@ -2889,166 +2851,6 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s ã‹ã‚‰å‰Šé™¤ã—ã¾ #~ msgid "pidfile %s does not exist. Daemon not running?\n" #~ msgstr "pidfile %s ãŒå­˜åœ¨ã—ã¾ã›ã‚“。デーモンã¯å®Ÿè¡Œä¸­ã§ã™ã‹ï¼Ÿ\n" -#, python-format -#~ msgid "Couldn't get IP, using 127.0.0.1 %s" -#~ msgstr "IPã‚’å–å¾—ã§ãã¾ã›ã‚“。127.0.0.1 ã‚’ %s ã¨ã—ã¦ä½¿ã„ã¾ã™ã€‚" - -#, python-format -#~ msgid "" -#~ "Access key %s has had %d failed authentications and will be locked out for " -#~ "%d minutes." -#~ msgstr "アクセスキー %s 㯠%d 回èªè¨¼ã«å¤±æ•—ã—ãŸãŸã‚ã€%d 分間ロックã•れã¾ã™ã€‚" - -#, python-format -#~ msgid "Authenticated Request For %s:%s)" -#~ msgstr "リクエストをèªè¨¼ã—ã¾ã—ãŸï¼š %s:%s" - -#, python-format -#~ msgid "arg: %s\t\tval: %s" -#~ msgstr "引数(arg): %s\t値(val): %s" - -#, python-format -#~ msgid "Unauthorized request for controller=%s and action=%s" -#~ msgstr "許å¯ã•れã¦ã„ãªã„リクエスト: controller=%s, action %sã§ã™ã€‚" - -#, python-format -#~ msgid "Adding role %s to user %s for project %s" -#~ msgstr "Adding role: ロール %s をユーザ %sã€ãƒ—ロジェクト %s ã«è¿½åŠ ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Adding sitewide role %s to user %s" -#~ msgstr "Adding sitewide role: サイトワイドã®ãƒ­ãƒ¼ãƒ« %s をユーザ %s ã«è¿½åŠ ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Removing role %s from user %s for project %s" -#~ msgstr "Removing role: ロール %s をユーザ %s プロジェクト %s ã‹ã‚‰å‰Šé™¤ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Removing sitewide role %s from user %s" -#~ msgstr "Removing sitewide role: サイトワイドã®ãƒ­ãƒ¼ãƒ« %s をユーザ %s ã‹ã‚‰å‰Šé™¤ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Getting x509 for user: %s on project: %s" -#~ msgstr "Getting X509: x509ã®å–得: ユーザ %s, プロジェクト %s" - -#, python-format -#~ msgid "Create project %s managed by %s" -#~ msgstr "Create project: プロジェクト %s (%s ã«ã‚ˆã‚Šç®¡ç†ã•れる)を作æˆã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Adding user %s to project %s" -#~ msgstr "Adding user: ユーザ %s をプロジェクト %s ã«è¿½åŠ ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Removing user %s from project %s" -#~ msgstr "Removing user: ユーザ %s をプロジェクト %s ã‹ã‚‰å‰Šé™¤ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Unsupported API request: controller = %s,action = %s" -#~ msgstr "サãƒãƒ¼ãƒˆã•れã¦ã„ãªã„APIリクエストã§ã™ã€‚ controller = %s,action = %s" - -#, python-format -#~ msgid "Attach volume %s to instacne %s at %s" -#~ msgstr "Attach volume: ボリューム%s をインスタンス %s ã«ãƒ‡ãƒã‚¤ã‚¹ %s ã§ã‚¢ã‚¿ãƒƒãƒã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Associate address %s to instance %s" -#~ msgstr "Associate address: アドレス %s をインスタンス %s ã«é–¢é€£ä»˜ã‘ã¾ã™ã€‚" - -#, python-format -#~ msgid "Registered image %s with id %s" -#~ msgstr "Registered image: イメージ %s ã‚’id %s ã§ç™»éŒ²ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "User %s is already a member of the group %s" -#~ msgstr "ユーザ %s ã¯æ—¢ã«ã‚°ãƒ«ãƒ¼ãƒ— %s ã®ãƒ¡ãƒ³ãƒãƒ¼ã§ã™ã€‚" - -#, python-format -#~ msgid "failed authorization: no project named %s (user=%s)" -#~ msgstr "Failed authorization: èªè¨¼ã«å¤±æ•—ã—ã¾ã—ãŸã€‚プロジェクトå %s (ユーザ = %s) ã¯å­˜åœ¨ã—ã¾ã›ã‚“。" - -#, python-format -#~ msgid "Failed authorization: user %s not admin and not member of project %s" -#~ msgstr "" -#~ "Failed authorization: èªè¨¼ã«å¤±æ•—ã—ã¾ã—ãŸï¼š ユーザ %s ã¯ç®¡ç†è€…ã§ã¯ãªãã‹ã¤ãƒ—ロジェクト %s ã®ãƒ¡ãƒ³ãƒãƒ¼ã§ã¯ã‚りã¾ã›ã‚“。" - -#, python-format -#~ msgid "User %s is not a member of project %s" -#~ msgstr "ユーザ %s ã¯ãƒ—ロジェクト %s ã®ãƒ¡ãƒ³ãƒãƒ¼ã§ã¯ã‚りã¾ã›ã‚“。" - -#, python-format -#~ msgid "Adding role %s to user %s in project %s" -#~ msgstr "Adding role: ロール %s をユーザ %s (プロジェクト %s ã®ï¼‰ ã«è¿½åŠ ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Removing role %s from user %s on project %s" -#~ msgstr "Removing role: ロール %s をユーザ %s (プロジェクト %s ã®ï¼‰ã‹ã‚‰å‰Šé™¤ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Created project %s with manager %s" -#~ msgstr "Created project: プロジェクト %s (マãƒãƒ¼ã‚¸ãƒ£ %s)を作æˆã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Remove user %s from project %s" -#~ msgstr "Remove user: ユーザ %s をプロジェクト %s ã‹ã‚‰å‰Šé™¤ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Created user %s (admin: %r)" -#~ msgstr "Created user: ユーザ %s (admin: %r) を作æˆã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Admin status set to %r for user %s" -#~ msgstr "Admin status set: 管ç†è€…ステータス %r をユーザ %s ã«è¨­å®šã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Quota exceeeded for %s, tried to run %s instances" -#~ msgstr "%s ã®ã‚¯ã‚ªãƒ¼ã‚¿ä¸Šé™ã‚’è¶…ãˆã¾ã—ãŸã€‚%s インスタンスを実行ã—よã†ã¨ã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Casting to scheduler for %s/%s's instance %s" -#~ msgstr "スケジューラã«å¯¾ã—㦠%s/%s ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ %s ã‚’é€ä¿¡ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Going to try and terminate %s" -#~ msgstr "%s を終了ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Input partition size not evenly divisible by sector size: %d / %d" -#~ msgstr "インプットパーティションサイズãŒã‚»ã‚¯ã‚¿ãƒ¼ã‚µã‚¤ã‚ºã§å‰²ã‚Šåˆ‡ã‚Œã¾ã›ã‚“。 %d / %d" - -#, python-format -#~ msgid "Bytes for local storage not evenly divisible by sector size: %d / %d" -#~ msgstr "ローカルストレージã®ãƒã‚¤ãƒˆæ•°ãŒã‚»ã‚¯ã‚¿ãƒ¼ã‚µã‚¤ã‚ºã§å‰²ã‚Šåˆ‡ã‚Œã¾ã›ã‚“: %d / %d" - -#, python-format -#~ msgid "check_instance_lock: arguments: |%s| |%s| |%s|" -#~ msgstr "check_instance_lock: arguments: |%s| |%s| |%s|" - -#, python-format -#~ msgid "Disassociating address %s" -#~ msgstr "アドレス %s ã®é–¢é€£ä»˜ã‘を解除(disassociate)ã—ã¦ã„ã¾ã™ã€‚" - -#, python-format -#~ msgid "trying to reboot a non-running instance: %s (state: %s excepted: %s)" -#~ msgstr "実行ã—ã¦ã„ãªã„インスタンスã®å†èµ·å‹•を試ã¿ã¾ã™ã€‚%s (状態: %s 期待ã™ã‚‹çŠ¶æ…‹: %s)" - -#, python-format -#~ msgid "" -#~ "trying to snapshot a non-running instance: %s (state: %s excepted: %s)" -#~ msgstr "実行ã—ã¦ã„ãªã„インスタンスã®ã‚¹ãƒŠãƒƒãƒ—ショットå–得を試ã¿ã¾ã™ã€‚%s (状態: %s 期待ã™ã‚‹çŠ¶æ…‹: %s)" - -#, python-format -#~ msgid "instance %s: attaching volume %s to %s" -#~ msgstr "attaching volume: インスタンス %s ã«ã¤ã„ã¦ãƒœãƒªãƒ¥ãƒ¼ãƒ  %s ã‚’ %s ã«ã‚¢ã‚¿ãƒƒãƒã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "instance %s: attach failed %s, removing" -#~ msgstr "インスタンス %s: %sã®ã‚¢ã‚¿ãƒƒãƒã«å¤±æ•—ã—ã¾ã—ãŸã€‚リムーブã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Detach volume %s from mountpoint %s on instance %s" -#~ msgstr "Detach volume: ボリューム %s をマウントãƒã‚¤ãƒ³ãƒˆ %s (インスタンス%s)ã‹ã‚‰ãƒ‡ã‚¿ãƒƒãƒã—ã¾ã™ã€‚" - #, python-format #~ msgid "updating %s..." #~ msgstr "%s ã®æƒ…å ±ã®æ›´æ–°â€¦" @@ -3056,14 +2858,6 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s ã‹ã‚‰å‰Šé™¤ã—ã¾ #~ msgid "unexpected error during update" #~ msgstr "æ›´æ–°ã®æœ€ä¸­ã«äºˆæœŸã—ãªã„エラーãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" -#, python-format -#~ msgid "Cannot get blockstats for \"%s\" on \"%s\"" -#~ msgstr "ブロックデãƒã‚¤ã‚¹ \"%s\" ã®çµ±è¨ˆã‚’ \"%s\" ã«ã¤ã„ã¦å–å¾—ã§ãã¾ã›ã‚“。" - -#, python-format -#~ msgid "Cannot get ifstats for \"%s\" on \"%s\"" -#~ msgstr "インタフェース \"%s\" ã®çµ±è¨ˆã‚’ \"%s\" ã«ã¤ã„ã¦å–å¾—ã§ãã¾ã›ã‚“。" - #~ msgid "unexpected exception getting connection" #~ msgstr "接続ã«éš›ã—予期ã—ãªã„エラーãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" @@ -3071,279 +2865,14 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s ã‹ã‚‰å‰Šé™¤ã—ã¾ #~ msgid "Found instance: %s" #~ msgstr "インスタンス %s ãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸã€‚" -#, python-format -#~ msgid "No service for %s, %s" -#~ msgstr "%s, %s ã®serviceãŒå­˜åœ¨ã—ã¾ã›ã‚“。" - -#, python-format -#~ msgid "No instance for id %s" -#~ msgstr "id %s ã®instanceãŒå­˜åœ¨ã—ã¾ã›ã‚“。" - -#, python-format -#~ msgid "no keypair for user %s, name %s" -#~ msgstr "ユーザ %s, ãƒãƒ¼ãƒ %s ã«è©²å½“ã™ã‚‹ã‚­ãƒ¼ãƒšã‚¢ãŒå­˜åœ¨ã—ã¾ã›ã‚“。" - -#, python-format -#~ msgid "No volume for id %s" -#~ msgstr "id %s ã«è©²å½“ã™ã‚‹ãƒœãƒªãƒ¥ãƒ¼ãƒ ãŒå­˜åœ¨ã—ã¾ã›ã‚“。" - -#, python-format -#~ msgid "No security group named %s for project: %s" -#~ msgstr "セキュリティグループå %s ãŒãƒ—ロジェクト %s ã«å­˜åœ¨ã—ã¾ã›ã‚“。" - -#, python-format -#~ msgid "Parallax returned HTTP error %d from request for /images" -#~ msgstr "Parallax ãŒHTTPエラー%d ã‚’ /images ã«å¯¾ã™ã‚‹ãƒªã‚¯ã‚¨ã‚¹ãƒˆã«å¯¾ã—ã¦è¿”ã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Parallax returned HTTP error %d from request for /images/detail" -#~ msgstr "Parallax ãŒHTTPエラー %d ã‚’ /images/detail ã«å¯¾ã™ã‚‹ãƒªã‚¯ã‚¨ã‚¹ãƒˆã«å¯¾ã—ã¦è¿”ã—ã¾ã—ãŸ" - -#, python-format -#~ msgid "IP %s leased to bad mac %s vs %s" -#~ msgstr "IP %s ãŒæœŸå¾…ã—㟠mac %s ã§ã¯ãªã %s ã«ãƒªãƒ¼ã‚¹ã•れã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "IP %s released from bad mac %s vs %s" -#~ msgstr "IP %s ãŒmac %s ã§ã¯ãªã„ mac %s ã¸ã®å‰²å½“ã‹ã‚‰é–‹æ”¾ã•れã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Getting object: %s / %s" -#~ msgstr "オブジェクトã®å–得: %s / %s" - -#, python-format -#~ msgid "Unauthorized attempt to get object %s from bucket %s" -#~ msgstr "" -#~ "Unauthorized attempt to get object: オブジェクト %s ã®ãƒã‚±ãƒƒãƒˆ %s ã‹ã‚‰ã®å–å¾—ã¯è¨±å¯ã•れã¦ã„ã¾ã›ã‚“。" - -#, python-format -#~ msgid "Putting object: %s / %s" -#~ msgstr "ã‚ªãƒ–ã‚¸ã‚§ã‚¯ãƒˆã®æ ¼ç´ï¼š: %s / %s" - -#, python-format -#~ msgid "Unauthorized attempt to upload object %s to bucket %s" -#~ msgstr "" -#~ "Unauthorized attempt to upload: オブジェクト %s ã®ãƒã‚±ãƒƒãƒˆ %s ã¸ã®ã‚¢ãƒƒãƒ—ロードã¯è¨±å¯ã•れã¦ã„ã¾ã›ã‚“。" - -#, python-format -#~ msgid "Deleting object: %s / %s" -#~ msgstr "オブジェクトを削除ã—ã¦ã„ã¾ã™ã€‚: %s / %s" - -#, python-format -#~ msgid "Toggling publicity flag of image %s %r" -#~ msgstr "Toggling publicity flag: イメージ %s ã®å…¬é–‹ãƒ•ラグを %r ã«æ›´æ–°ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Casting to %s %s for %s" -#~ msgstr "メッセージã®cast: %s %s for %s" - -#, python-format -#~ msgid "Nested received %s, %s" -#~ msgstr "ãƒã‚¹ã¨ã—ãŸå—ä¿¡: %s, %s" - -#, python-format -#~ msgid "Creating disk for %s by attaching disk file %s" -#~ msgstr "%s ã®ãƒ‡ã‚£ã‚¹ã‚¯ã‚’ディスクファイル %s をアタッãƒã—ã¦ä½œæˆã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Created switch port %s on switch %s" -#~ msgstr "スイッãƒãƒãƒ¼ãƒˆ %s をスイッム%s ã«ä½œæˆã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "WMI job succeeded: %s, Elapsed=%s " -#~ msgstr "WMIã‚¸ãƒ§ãƒ–ãŒæˆåŠŸã—ã¾ã—ãŸ: %s, çµŒéŽæ™‚é–“=%s " - -#, python-format -#~ msgid "Del: disk %s vm %s" -#~ msgstr "Del: 削除: disk %s vm %s" - -#, python-format -#~ msgid "" -#~ "Got Info for vm %s: state=%s, mem=%s, num_cpu=%s, " -#~ "cpu_time=%s" -#~ msgstr "" -#~ "vm %s ã®æƒ…å ±ã®å–å¾—: state=%s, mem=%s, num_cpu=%s, cpu_time=%s" - -#, python-format -#~ msgid "Successfully changed vm state of %s to %s" -#~ msgstr "vmã®çŠ¶æ…‹ã® %s ã‹ã‚‰ %s ã¸ã®å¤‰æ›´ã«æˆåŠŸã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Failed to change vm state of %s to %s" -#~ msgstr "VMã®çŠ¶æ…‹ã® %s ã‹ã‚‰ %s ã¸ã®å¤‰æ›´ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Finished retreving %s -- placed in %s" -#~ msgstr "%s ã‚’å–å¾—ã—ã¾ã—ãŸã€‚æ ¼ç´å…ˆ: %s" - -#, python-format -#~ msgid "instance %s: deleting instance files %s" -#~ msgstr "インスタンス %s: インスタンスファイル %s を削除ã—ã¦ã„ã¾ã™ã€‚" - -#, python-format -#~ msgid "data: %r, fpath: %r" -#~ msgstr "データ:%r ファイルパス: %r" - -#, python-format -#~ msgid "Contents of file %s: %r" -#~ msgstr "ファイル %s ã®ä¸­èº«: %r" - -#, python-format -#~ msgid "instance %s: injecting key into image %s" -#~ msgstr "インスタンス %s ã«ã‚­ãƒ¼ %s をインジェクトã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "instance %s: injecting net into image %s" -#~ msgstr "インスタンス %s ã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯è¨­å®šã‚’イメージ %s ã«ã‚¤ãƒ³ã‚¸ã‚§ã‚¯ãƒˆã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "instance %s: ignoring error injecting data into image %s (%s)" -#~ msgstr "インスタンス %s: データをイメージ %s ã«ã‚¤ãƒ³ã‚¸ã‚§ã‚¯ãƒˆã™ã‚‹éš›ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚(%s)" - -#, python-format -#~ msgid "Task [%s] %s status: success %s" -#~ msgstr "タスク [%s] %s ステータス: success %s" - -#, python-format -#~ msgid "Task [%s] %s status: %s %s" -#~ msgstr "タスク [%s] %s ステータス: %s %s" - -#, python-format -#~ msgid "%s: _db_content => %s" -#~ msgstr "%s: _db_content => %s" - -#, python-format -#~ msgid "Calling %s %s" -#~ msgstr "呼ã³å‡ºã—: %s %s" - -#, python-format -#~ msgid "Created VM %s as %s." -#~ msgstr "VM %s ã‚’ %s ã¨ã—ã¦ä½œæˆã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Creating VBD for VM %s, VDI %s ... " -#~ msgstr "VM %s, VDI %s ã®VBDを作æˆã—ã¾ã™â€¦ " - -#, python-format -#~ msgid "Created VBD %s for VM %s, VDI %s." -#~ msgstr "VBD %s ã‚’ VM %s, VDI %s ã«å¯¾ã—ã¦ä½œæˆã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Creating VIF for VM %s, network %s." -#~ msgstr "VM %s, ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ %s を作æˆã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Created VIF %s for VM %s, network %s." -#~ msgstr "VIF %s ã‚’ VM %s, ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ %s ã«ä½œæˆã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Snapshotting VM %s with label '%s'..." -#~ msgstr "VM %s ã®ã‚¹ãƒŠãƒƒãƒ—ショットをラベル '%s' ã§ä½œæˆã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Created snapshot %s from VM %s." -#~ msgstr "スナップショット %s ã‚’ VM %s ã«ã¤ã„ã¦ä½œæˆã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Asking xapi to upload %s as '%s'" -#~ msgstr "xapiã«å¯¾ã—㦠%s ã‚’ '%s' ã¨ã—ã¦ã‚¢ãƒƒãƒ—ロードã™ã‚‹ã‚ˆã†ã«æŒ‡ç¤ºã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Asking xapi to fetch %s as %s" -#~ msgstr "xapi ã«å¯¾ã—㦠%s ã‚’ %s ã¨ã—ã¦å–å¾—ã™ã‚‹ã‚ˆã†ã«æŒ‡ç¤ºã—ã¾ã™ã€‚" - #, python-format #~ msgid "PV Kernel in VDI:%d" #~ msgstr "VDIã®PV Kernel: %d" -#, python-format -#~ msgid "VHD %s has parent %s" -#~ msgstr "VHD %s ã®ãƒšã‚¢ãƒ¬ãƒ³ãƒˆã¯ %s ã§ã™ã€‚" - -#, python-format -#~ msgid "Parent %s doesn't match original parent %s, waiting for coalesce..." -#~ msgstr "ペアレント %s ãŒã‚ªãƒªã‚¸ãƒŠãƒ«ã®ãƒšã‚¢ãƒ¬ãƒ³ãƒˆ %s ã¨ä¸€è‡´ã—ã¾ã›ã‚“。åˆè‡´ã™ã‚‹ã®ã‚’å¾…ã¡ã¾ã™â€¦" - -#, python-format -#~ msgid "Unexpected number of VDIs (%s) found for VM %s" -#~ msgstr "予期ã—ãªã„æ•° (%s) ã®VDIãŒVM %s ã«å­˜åœ¨ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Spawning VM %s created %s." -#~ msgstr "VM %s ã®ç”Ÿæˆ(spawning) ã«ã‚ˆã‚Š %s を作æˆã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Unable to Snapshot %s: %s" -#~ msgstr "%s ã®ã‚¹ãƒŠãƒƒãƒ—ショットã«å¤±æ•—ã—ã¾ã—ãŸï¼š %s" - -#, python-format -#~ msgid "suspend: instance not present %s" -#~ msgstr "suspend: インスタンス %s ã¯å­˜åœ¨ã—ã¾ã›ã‚“。" - -#, python-format -#~ msgid "resume: instance not present %s" -#~ msgstr "resume: インスタンス %s ã¯å­˜åœ¨ã—ã¾ã›ã‚“。" - -#, python-format -#~ msgid "Instance not found %s" -#~ msgstr "インスタンス %s ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。" - -#, python-format -#~ msgid "Introduced %s as %s." -#~ msgstr "%s ã‚’ %s ã¨ã—㦠introduce ã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Ignoring exception %s when getting PBDs for %s" -#~ msgstr "例外 %s ㌠%s ã®PBDã‚’å–å¾—ã™ã‚‹éš›ã«ç™ºç”Ÿã—ã¾ã—ãŸãŒç„¡è¦–ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Ignoring exception %s when unplugging PBD %s" -#~ msgstr "例外 %s ㌠%s ã®PBDã‚’unplugã™ã‚‹éš›ã«ç™ºç”Ÿã—ã¾ã—ãŸãŒç„¡è¦–ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Ignoring exception %s when forgetting SR %s" -#~ msgstr "例外 %s ãŒSR %s ã‚’forgetã™ã‚‹éš›ã«ç™ºç”Ÿã—ã¾ã—ãŸãŒç„¡è¦–ã—ã¾ã™ã€‚" - -#, python-format -#~ msgid "Unable to obtain target information %s, %s" -#~ msgstr "ã‚¿ãƒ¼ã‚²ãƒƒãƒˆã®æƒ…報をå–å¾—ã§ãã¾ã›ã‚“。 %s, %s" - -#, python-format -#~ msgid "Attach_volume: %s, %s, %s" -#~ msgstr "Attach_volume: ボリュームã®ã‚¢ã‚¿ãƒƒãƒ: %s, %s, %s" - -#, python-format -#~ msgid "Unable to create VDI on SR %s for instance %s" -#~ msgstr "SR %s ã«ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ %s ã®VDIを作æˆã§ãã¾ã›ã‚“。" - -#, python-format -#~ msgid "Unable to use SR %s for instance %s" -#~ msgstr "SR %s をインスタンス %s ã«å¯¾ã—ã¦åˆ©ç”¨ã§ãã¾ã›ã‚“。" - -#, python-format -#~ msgid "Mountpoint %s attached to instance %s" -#~ msgstr "マウントãƒã‚¤ãƒ³ãƒˆ %s をインスタンス %s ã«ã‚¢ã‚¿ãƒƒãƒã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Detach_volume: %s, %s" -#~ msgstr "Detach_volume: ボリュームã®ãƒ‡ã‚¿ãƒƒãƒ: %s, %s" - -#, python-format -#~ msgid "Mountpoint %s detached from instance %s" -#~ msgstr "マウントãƒã‚¤ãƒ³ãƒˆ %s をインスタンス %s ã‹ã‚‰ãƒ‡ã‚¿ãƒƒãƒã—ã¾ã—ãŸã€‚" - -#, python-format -#~ msgid "Quota exceeeded for %s, tried to create %sG volume" -#~ msgstr "%sã®ã‚¯ã‚ªãƒ¼ã‚¿ã‚’è¶…ãˆã¦ã„ã¾ã™ã€‚サイズ %sG ã®ãƒœãƒªãƒ¥ãƒ¼ãƒ ã®ä½œæˆã‚’行ãŠã†ã¨ã—ã¾ã—ãŸã€‚" - #, python-format #~ msgid "Volume quota exceeded. You cannot create a volume of size %s" #~ msgstr "ボリュームã®ã‚¯ã‚ªãƒ¼ã‚¿ã‚’è¶…ãˆã¦ã„ã¾ã™ã€‚%sã®å¤§ãã•ã®ãƒœãƒªãƒ¥ãƒ¼ãƒ ã¯ä½œæˆã§ãã¾ã›ã‚“。" -#, python-format -#~ msgid "volume %s: creating lv of size %sG" -#~ msgstr "ボリューム%sã®%sGã®lv (è«–ç†ãƒœãƒªãƒ¥ãƒ¼ãƒ ) を作æˆã—ã¾ã™ã€‚" - #~ msgid "Wrong number of arguments." #~ msgstr "å¼•æ•°ã®æ•°ãŒç•°ãªã‚Šã¾ã™ã€‚" diff --git a/po/pt_BR.po b/po/pt_BR.po index d6d57a9b1..bff1135c6 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-07-25 17:40+0000\n" +"PO-Revision-Date: 2011-08-23 11:23+0000\n" "Last-Translator: msinhore \n" "Language-Team: Brazilian Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" -"X-Generator: Launchpad (build 13573)\n" +"X-Launchpad-Export-Date: 2011-08-24 04:47+0000\n" +"X-Generator: Launchpad (build 13697)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -2824,47 +2824,6 @@ msgstr "" msgid "Removing user %(user)s from project %(project)s" msgstr "" -#, python-format -#~ msgid "" -#~ "%s\n" -#~ "Command: %s\n" -#~ "Exit code: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" -#~ msgstr "" -#~ "%s\n" -#~ "Comando: %s\n" -#~ "Código de retorno: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" - -#, python-format -#~ msgid "(%s) publish (key: %s) %s" -#~ msgstr "(%s) publicar (key: %s) %s" - -#, python-format -#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds." -#~ msgstr "" -#~ "Servidor AMQP em %s:%d inatingível. Tentando novamente em %d segundos." - -#, python-format -#~ msgid "Binding %s to %s with key %s" -#~ msgstr "Atribuindo %s para %s com chave %s" - -#, python-format -#~ msgid "Getting from %s: %s" -#~ msgstr "Obtendo de %s: %s" - -#, python-format -#~ msgid "Starting %s node" -#~ msgstr "Iniciando nó %s" - -#, python-format -#~ msgid "Data store %s is unreachable. Trying again in %d seconds." -#~ msgstr "" -#~ "Repositório de dados %s não pode ser atingido. Tentando novamente em %d " -#~ "segundos." - #~ msgid "Full set of FLAGS:" #~ msgstr "Conjunto completo de FLAGS:" @@ -2876,115 +2835,6 @@ msgstr "" #~ msgid "Serving %s" #~ msgstr "Servindo %s" -#, python-format -#~ msgid "Couldn't get IP, using 127.0.0.1 %s" -#~ msgstr "Não foi possível obter IP, usando 127.0.0.1 %s" - -#, python-format -#~ msgid "" -#~ "Access key %s has had %d failed authentications and will be locked out for " -#~ "%d minutes." -#~ msgstr "" -#~ "Chave de acesso %s tem %d falhas de autenticação e vai ser bloqueada por %d " -#~ "minutos." - -#, python-format -#~ msgid "arg: %s\t\tval: %s" -#~ msgstr "argumento: %s\t\tvalor: %s" - -#, python-format -#~ msgid "Authenticated Request For %s:%s)" -#~ msgstr "Pedido de Autenticação Para: %s:%s" - -#, python-format -#~ msgid "Adding sitewide role %s to user %s" -#~ msgstr "Adicionando papel em todo site %s ao usuário %s" - -#, python-format -#~ msgid "Adding role %s to user %s for project %s" -#~ msgstr "Adicionando papel %s ao usuário %s para o projeto %s" - -#, python-format -#~ msgid "Unauthorized request for controller=%s and action=%s" -#~ msgstr "Requisição não autorizada para controlador=%s e ação=%s" - -#, python-format -#~ msgid "Removing role %s from user %s for project %s" -#~ msgstr "Removendo papel %s do usuário %s para o projeto %s" - -#, python-format -#~ msgid "Getting x509 for user: %s on project: %s" -#~ msgstr "Obtendo x509 para usuário: %s do projeto: %s" - -#, python-format -#~ msgid "Create project %s managed by %s" -#~ msgstr "Criar projeto %s gerenciado por %s" - -#, python-format -#~ msgid "Removing user %s from project %s" -#~ msgstr "Excluindo usuário %s do projeto %s" - -#, python-format -#~ msgid "Adding user %s to project %s" -#~ msgstr "Adicionando usuário %s ao projeto %s" - -#, python-format -#~ msgid "Unsupported API request: controller = %s,action = %s" -#~ msgstr "Requisição de API não suportada: controlador = %s,ação = %s" - -#, python-format -#~ msgid "Removing sitewide role %s from user %s" -#~ msgstr "Removendo papel %s em todo site do usuário %s" - -#, python-format -#~ msgid "Associate address %s to instance %s" -#~ msgstr "Atribuir endereço %s à instância %s" - -#, python-format -#~ msgid "Attach volume %s to instacne %s at %s" -#~ msgstr "Anexar volume %s para instância %s em %s" - -#, python-format -#~ msgid "Registered image %s with id %s" -#~ msgstr "Registrada imagem %s com id %s" - -#, python-format -#~ msgid "User %s is already a member of the group %s" -#~ msgstr "Usuário %s já pertence ao grupo %s" - -#, python-format -#~ msgid "User %s is not a member of project %s" -#~ msgstr "Usuário %s não é membro do projeto %s" - -#, python-format -#~ msgid "failed authorization: no project named %s (user=%s)" -#~ msgstr "falha de autorização: nenhum projeto de nome %s (usuário=%s)" - -#, python-format -#~ msgid "Failed authorization: user %s not admin and not member of project %s" -#~ msgstr "" -#~ "Falha de autorização: usuário %s não é administrador nem membro do projeto %s" - -#, python-format -#~ msgid "Created project %s with manager %s" -#~ msgstr "Criado projeto %s com gerente %s" - -#, python-format -#~ msgid "Removing role %s from user %s on project %s" -#~ msgstr "Removendo papel %s do usuário %s no projeto %s" - -#, python-format -#~ msgid "Adding role %s to user %s in project %s" -#~ msgstr "Adicionando papel %s ao usuário %s no projeto %s" - -#, python-format -#~ msgid "Remove user %s from project %s" -#~ msgstr "Remover usuário %s do projeto %s" - -#, python-format -#~ msgid "Created user %s (admin: %r)" -#~ msgstr "Criado usuário %s (administrador: %r)" - #~ msgid "No such process" #~ msgstr "Processo inexistente" diff --git a/po/ru.po b/po/ru.po index 746db964a..951571d9e 100644 --- a/po/ru.po +++ b/po/ru.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-07-09 07:20+0000\n" -"Last-Translator: ilya kislicyn \n" +"PO-Revision-Date: 2011-08-23 11:22+0000\n" +"Last-Translator: Thierry Carrez \n" "Language-Team: Russian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" -"X-Generator: Launchpad (build 13573)\n" +"X-Launchpad-Export-Date: 2011-08-24 04:47+0000\n" +"X-Generator: Launchpad (build 13697)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -2793,58 +2793,6 @@ msgstr "" #~ msgid "Starting %s" #~ msgstr "ЗапуÑкаетÑÑ %s" -#, python-format -#~ msgid "arg: %s\t\tval: %s" -#~ msgstr "arg: %s\t\tval: %s" - -#, python-format -#~ msgid "Adding role %s to user %s for project %s" -#~ msgstr "Добавление роли %s Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %s Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð° %s" - -#, python-format -#~ msgid "Removing role %s from user %s for project %s" -#~ msgstr "Удаление роли %s Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %s Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð° %s" - -#, python-format -#~ msgid "Create project %s managed by %s" -#~ msgstr "Создать проект %s под управлением %s" - -#, python-format -#~ msgid "Removing user %s from project %s" -#~ msgstr "Удаление Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %s Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð° %s" - -#, python-format -#~ msgid "Adding user %s to project %s" -#~ msgstr "Добавление Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %s к проекту %s" - -#, python-format -#~ msgid "User %s is already a member of the group %s" -#~ msgstr "Пользователь %s уже член группы %s" - -#, python-format -#~ msgid "User %s is not a member of project %s" -#~ msgstr "Пользователь %s не ÑвлÑетÑÑ Ñ‡Ð»ÐµÐ½Ð¾Ð¼ группы %s" - -#, python-format -#~ msgid "Created project %s with manager %s" -#~ msgstr "Создан проект %s под управлением %s" - -#, python-format -#~ msgid "Removing role %s from user %s on project %s" -#~ msgstr "Удаление роли %s Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %s в проекте %s" - -#, python-format -#~ msgid "Remove user %s from project %s" -#~ msgstr "Удалить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %s из проекта %s" - -#, python-format -#~ msgid "Created user %s (admin: %r)" -#~ msgstr "Создан пользователь %s (админиÑтратор: %r)" - -#, python-format -#~ msgid "Adding role %s to user %s in project %s" -#~ msgstr "Добавление роли %s Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %s в проект %s" - #~ msgid "unexpected error during update" #~ msgstr "Ð½ÐµÐ¾Ð¶Ð¸Ð´Ð°Ð½Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ" @@ -2852,75 +2800,9 @@ msgstr "" #~ msgid "updating %s..." #~ msgstr "обновление %s..." -#, python-format -#~ msgid "Getting object: %s / %s" -#~ msgstr "Получение объекта: %s / %s" - -#, python-format -#~ msgid "Deleting object: %s / %s" -#~ msgstr "Удаление объекта: %s / %s" - -#, python-format -#~ msgid "%s: _db_content => %s" -#~ msgstr "%s: _db_content => %s" - -#, python-format -#~ msgid "Calling %s %s" -#~ msgstr "Звонок %s %s" - -#, python-format -#~ msgid "" -#~ "%s\n" -#~ "Command: %s\n" -#~ "Exit code: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" -#~ msgstr "" -#~ "%s\n" -#~ "Команда: %s\n" -#~ "Код завершениÑ: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" - -#, python-format -#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds." -#~ msgstr "AMQP Ñервер %s:%d недоÑтупен. ÐŸÐ¾Ð²Ñ‚Ð¾Ñ€Ð½Ð°Ñ Ð¿Ð¾Ð¿Ñ‹Ñ‚ÐºÐ° через %d Ñекунд." - -#, python-format -#~ msgid "Putting object: %s / %s" -#~ msgstr "Ð’Ñтавка объекта: %s / %s" - -#, python-format -#~ msgid "Starting %s node" -#~ msgstr "ЗапуÑкаетÑÑ Ð½Ð¾Ð´Ð° %s" - -#, python-format -#~ msgid "Data store %s is unreachable. Trying again in %d seconds." -#~ msgstr "Хранилище данных %s недоÑтупно. ÐŸÐ¾Ð²Ñ‚Ð¾Ñ€Ð½Ð°Ñ Ð¿Ð¾Ð¿Ñ‹Ñ‚ÐºÐ° через %d Ñекунд." - -#, python-format -#~ msgid "Couldn't get IP, using 127.0.0.1 %s" -#~ msgstr "Ðе удалоÑÑŒ получить IP, иÑпользуем 127.0.0.1 %s" - #, python-format #~ msgid "pidfile %s does not exist. Daemon not running?\n" #~ msgstr "pidfile %s не обнаружен. Демон не запущен?\n" -#, python-format -#~ msgid "Getting from %s: %s" -#~ msgstr "Получение из %s: %s" - -#, python-format -#~ msgid "" -#~ "Access key %s has had %d failed authentications and will be locked out for " -#~ "%d minutes." -#~ msgstr "" -#~ "Ключ доÑтупа %s имеет %d неудачных попыток аутентификации и будет " -#~ "заблокирован на %d минут." - -#, python-format -#~ msgid "Authenticated Request For %s:%s)" -#~ msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ð¸ Ð´Ð»Ñ %s:%s)" - #~ msgid "Wrong number of arguments." #~ msgstr "Ðеверное чиÑло аргументов." diff --git a/po/tl.po b/po/tl.po index 84e9d26e6..3c7cd792f 100644 --- a/po/tl.po +++ b/po/tl.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-02-17 03:24+0000\n" -"Last-Translator: John Michael Baterna \n" +"PO-Revision-Date: 2011-08-23 11:21+0000\n" +"Last-Translator: Thierry Carrez \n" "Language-Team: Tagalog \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" -"X-Generator: Launchpad (build 13573)\n" +"X-Launchpad-Export-Date: 2011-08-24 04:47+0000\n" +"X-Generator: Launchpad (build 13697)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -2789,8 +2789,3 @@ msgstr "" #, python-format msgid "Removing user %(user)s from project %(project)s" msgstr "" - -#, python-format -#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds." -#~ msgstr "" -#~ "Hindi makita o maabot ang AMQP server sa %s:%d. Muling subukan sa %d segundo." diff --git a/po/uk.po b/po/uk.po index bcc53fed3..d040eaf69 100644 --- a/po/uk.po +++ b/po/uk.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-02-03 22:02+0000\n" -"Last-Translator: Wladimir Rossinski \n" +"PO-Revision-Date: 2011-08-23 11:21+0000\n" +"Last-Translator: Thierry Carrez \n" "Language-Team: Ukrainian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" -"X-Generator: Launchpad (build 13573)\n" +"X-Launchpad-Export-Date: 2011-08-24 04:47+0000\n" +"X-Generator: Launchpad (build 13697)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -2788,10 +2788,6 @@ msgstr "" msgid "Removing user %(user)s from project %(project)s" msgstr "" -#, python-format -#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds." -#~ msgstr "AMQP Ñервер %s:%d недоÑтупний. Спроба під'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· %d Ñекунд." - #, python-format #~ msgid "Starting %s" #~ msgstr "ЗапуÑкаєтьÑÑ %s" @@ -2799,33 +2795,3 @@ msgstr "" #, python-format #~ msgid "Serving %s" #~ msgstr "ОбÑÐ»ÑƒÐ³Ð¾Ð²ÑƒÐ²Ð°Ð½Ð½Ñ %s" - -#, python-format -#~ msgid "Couldn't get IP, using 127.0.0.1 %s" -#~ msgstr "Ðе вдалоÑÑŒ отримати IP, викориÑтовуючи 127.0.0.1 %s" - -#, python-format -#~ msgid "Removing user %s from project %s" -#~ msgstr "Ð’Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача %s з проекту %s" - -#, python-format -#~ msgid "Adding user %s to project %s" -#~ msgstr "Ð”Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача %s до проекту %s" - -#, python-format -#~ msgid "" -#~ "%s\n" -#~ "Command: %s\n" -#~ "Exit code: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" -#~ msgstr "" -#~ "%s\n" -#~ "Команда: %s\n" -#~ "Код завершеннÑ: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" - -#, python-format -#~ msgid "Getting from %s: %s" -#~ msgstr "ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð· %s: %s" -- cgit From 3058e5eeb5e1068b7b5f721b985c735bc5486c92 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Wed, 24 Aug 2011 02:11:56 -0700 Subject: Couple of fixes to the review feedback changes --- nova/network/quantum/client.py | 12 ++++++------ nova/network/quantum/manager.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 1927015c2..b457190c1 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -23,11 +23,11 @@ import urllib class JSONSerializer(object): -""" -This is a simple json-only serializer to use until we can just grab -the standard serializer from the quantum library. -TODO(danwent): replace serializer with quantum implementation -""" + """ + This is a simple json-only serializer to use until we can just grab + the standard serializer from the quantum library. + TODO(danwent): replace serializer with quantum implementation + """ def serialize(self, data, content_type): try: return json.dumps(data) @@ -172,7 +172,7 @@ class Client(object): httplib.CREATED, httplib.ACCEPTED, httplib.NO_CONTENT): - if not data: + if data is not None and len(data): return self.deserialize(data, status_code) else: raise Exception(_("Server returned error: %s" % res.read())) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 975598324..d29d1e06b 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -86,7 +86,7 @@ class QuantumManager(manager.FlatManager): if num_networks != 1: raise Exception(_("QuantumManager requires that only one" " network is created per call")) - q_tenant_id = kwargs.get("project_id", FLAGS.quantum_default_tenant_id) + q_tenant_id = kwargs["project_id"] or FLAGS.quantum_default_tenant_id quantum_net_id = uuid if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): @@ -179,7 +179,7 @@ class QuantumManager(manager.FlatManager): vif_rec) return self.get_instance_nw_info(context, instance_id, - instance_type_id, host) + instance_type_id, host) def get_instance_nw_info(self, context, instance_id, instance_type_id, host): -- cgit From 13544ec52d4f84032b7925b1dab00fbdc5ca0c79 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Wed, 24 Aug 2011 03:07:11 -0700 Subject: pep8 fixes --- nova/network/quantum/client.py | 4 ++-- nova/network/quantum/nova_ipam_lib.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index b457190c1..82f23b6b5 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -76,8 +76,8 @@ class Client(object): attachment_path = "/networks/%s/ports/%s/attachment" def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, - format="xml", testing_stub=None, key_file=None, cert_file=None, - logger=None): + format="xml", testing_stub=None, key_file=None, + cert_file=None, logger=None): """ Creates a new client to some service. diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 71e723cb9..4f62887d1 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -81,7 +81,8 @@ class QuantumNovaIPAMLib(object): admin_context = context.elevated() network = db.network_get_by_cidr(admin_context, cidr) if not network: - raise Exception(_("No network with fixed_range = %s" % fixed_range)) + raise Exception(_("No network with fixed_range = %s" % + fixed_range)) return network['uuid'] def delete_subnets_by_net_id(self, context, net_id, project_id): -- cgit From 551b4b1b16c894551e5337663374a40aa46663d7 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Wed, 24 Aug 2011 03:14:39 -0700 Subject: Catch exception for instances that aren't there If an instance failed to spawn it will be in a shutdown state but the instance won't actually be there when we attempt to deallocate for the instance. For now we catch the exception and just log a message. Also made some minor formatting changes. --- nova/network/quantum/manager.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index d29d1e06b..c03622218 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -287,17 +287,21 @@ class QuantumManager(manager.FlatManager): (net_id, port_id) = self.q_conn.get_port_by_attachment( q_tenant_id, interface_id) if not net_id: - LOG.error("Unable to find port with attachment: %s" % \ - (interface_id)) + LOG.error("Unable to find port with attachment: %s" % + (interface_id)) continue self.q_conn.detach_and_delete_port(q_tenant_id, - net_id, port_id) + net_id, port_id) self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id, - net_id, vif_ref) + net_id, vif_ref) - db.virtual_interface_delete_by_instance(admin_context, - instance_id) + try: + db.virtual_interface_delete_by_instance(admin_context, + instance_id) + except exception.InstanceNotFound: + LOG.error(_("Attempted to deallocate non-existent instance: %s" % + (instance_id))) self._do_trigger_security_group_members_refresh_for_instance( instance_id) -- cgit From 8b3ac90bd53ec81e6669c6169969e1e8da3e2d4f Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 10:59:12 -0400 Subject: Commit with test data in migration. --- nova/compute/api.py | 1 - .../versions/042_update_instance_states.py | 165 ++++++++++++++++++--- nova/exception.py | 3 + 3 files changed, 149 insertions(+), 20 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index a373bed90..eeb8f47d9 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -761,7 +761,6 @@ class API(base.Base): self.update(context, instance_id, - vm_state=vm_states.ACTIVE, task_state=task_states.DELETING) host = instance['host'] diff --git a/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py index 07efbf90f..e27b84176 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py @@ -14,44 +14,171 @@ # License for the specific language governing permissions and limitations # under the License. +import sqlalchemy from sqlalchemy import MetaData, Table, Column, String +from nova import log +from nova.compute import task_states +from nova.compute import vm_states +from nova.db.sqlalchemy import models + + +LOG = log.getLogger("farts") meta = MetaData() c_task_state = Column('task_state', - String(length=255, convert_unicode=False, - assert_unicode=None, unicode_error=None, - _warn_on_bytestring=False), - nullable=True) + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + + +_upgrade_translations = { + "stopping": { + "vm_state": vm_states.ACTIVE, + "task_state": task_states.STOPPING, + }, + "stopped": { + "vm_state": vm_states.STOPPED, + "task_state": None, + }, + "terminated": { + "vm_state": vm_states.DELETED, + "task_state": None, + }, + "terminating": { + "vm_state": vm_states.ACTIVE, + "task_state": task_states.DELETING, + }, + "running": { + "vm_state": vm_states.ACTIVE, + "task_state": None, + }, + "scheduling": { + "vm_state": vm_states.BUILDING, + "task_state": task_states.SCHEDULING, + }, + "migrating": { + "vm_state": vm_states.MIGRATING, + "task_state": None, + }, + "pending": { + "vm_state": vm_states.BUILDING, + "task_state": task_states.SCHEDULING, + }, +} + + +_downgrade_translations = { + vm_states.ACTIVE: { + None: "running", + task_states.DELETING: "terminating", + task_states.STOPPING: "stopping", + }, + vm_states.BUILDING: { + None: "pending", + task_states.SCHEDULING: "scheduling", + }, + vm_states.STOPPED: { + None: "stopped", + }, + vm_states.REBUILDING: { + None: "pending", + }, + vm_states.DELETED: { + None: "terminated", + }, + vm_states.MIGRATING: { + None: "migrating", + }, +} + + +def _insert_test_data(instance_table): + running_instance = models.Instance() + running_instance.state_description = "running" + stopped_instance = models.Instance() + stopped_instance.state_description = "stopped" + terminated_instance = models.Instance() + terminated_instance.state_description = "terminated" + migrating_instance = models.Instance() + migrating_instance.state_description = "migrating" + scheduling_instance = models.Instance() + scheduling_instance.state_description = "scheduling" + bad_instance = models.Instance() + bad_instance.state_description = "bad_state_description" + + instance_table.insert(running_instance).execute() + instance_table.insert(stopped_instance).execute() + instance_table.insert(terminated_instance).execute() + instance_table.insert(migrating_instance).execute() + instance_table.insert(scheduling_instance).execute() + instance_table.insert(bad_instance).execute() def upgrade(migrate_engine): - # Upgrade operations go here. Don't create your own engine; - # bind migrate_engine to your metadata + #migrate_engine.echo = True meta.bind = migrate_engine - instances = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - c_state = instances.c.state + instance_table = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + _insert_test_data(instance_table) + for instance in instance_table.select().execute(): + LOG.info(instance) + c_state = instance_table.c.state c_state.alter(name='power_state') - c_vm_state = instances.c.state_description + c_vm_state = instance_table.c.state_description c_vm_state.alter(name='vm_state') - instances.create_column(c_task_state) + instance_table.create_column(c_task_state) + for old_state, values in _upgrade_translations.iteritems(): + new_values = { + "old_state": old_state, + "vm_state": values["vm_state"], + "task_state": values["task_state"], + } -def downgrade(migrate_engine): - meta.bind = migrate_engine + update = sqlalchemy.text("UPDATE instances SET task_state=:task_state " + "WHERE vm_state=:old_state") + migrate_engine.execute(update, **new_values) + + update = sqlalchemy.text("UPDATE instances SET vm_state=:vm_state " + "WHERE vm_state=:old_state") + migrate_engine.execute(update, **new_values) - instances = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) + for instance in instance_table.select().execute(): + LOG.info(instance) - c_state = instances.c.power_state + meta.bind = migrate_engine + + instance_table = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + for old_vm_state, old_task_states in _downgrade_translations.iteritems(): + for old_task_state, new_state_desc in old_task_states.iteritems(): + if old_task_state: + update = sqlalchemy.text("UPDATE instances " + "SET vm_state=:new_state_desc " + "WHERE task_state=:old_task_state " + "AND vm_state=:old_vm_state") + migrate_engine.execute(update, locals()) + else: + update = sqlalchemy.text("UPDATE instances " + "SET vm_state=:new_state_desc " + "WHERE vm_state=:old_vm_state") + migrate_engine.execute(update, locals()) + + #c_state = instance_table.c.power_state c_state.alter(name='state') - c_vm_state = instances.c.vm_state + #c_vm_state = instance_table.c.vm_state c_vm_state.alter(name='state_description') - instances.drop_column('task_state') + instance_table.drop_column('task_state') + + for instance in instance_table.select().execute(): + LOG.info(instance) + + raise Exception() diff --git a/nova/exception.py b/nova/exception.py index 44af8177e..66740019b 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -318,6 +318,9 @@ class InvalidEc2Id(Invalid): class NotFound(NovaException): message = _("Resource could not be found.") + def __init__(self, *args, **kwargs): + super(NotFound, self).__init__(**kwargs) + class FlagNotSet(NotFound): message = _("Required flag %(flag)s not set.") -- cgit From df77c6c168d4370ec582ffbccd43e3b9cb551b98 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 11:00:21 -0400 Subject: Commit without test data in migration. --- .../versions/042_update_instance_states.py | 43 +++------------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py index e27b84176..10704d0da 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py @@ -17,15 +17,13 @@ import sqlalchemy from sqlalchemy import MetaData, Table, Column, String -from nova import log from nova.compute import task_states from nova.compute import vm_states -from nova.db.sqlalchemy import models -LOG = log.getLogger("farts") meta = MetaData() + c_task_state = Column('task_state', String(length=255, convert_unicode=False, assert_unicode=None, unicode_error=None, @@ -94,37 +92,12 @@ _downgrade_translations = { } -def _insert_test_data(instance_table): - running_instance = models.Instance() - running_instance.state_description = "running" - stopped_instance = models.Instance() - stopped_instance.state_description = "stopped" - terminated_instance = models.Instance() - terminated_instance.state_description = "terminated" - migrating_instance = models.Instance() - migrating_instance.state_description = "migrating" - scheduling_instance = models.Instance() - scheduling_instance.state_description = "scheduling" - bad_instance = models.Instance() - bad_instance.state_description = "bad_state_description" - - instance_table.insert(running_instance).execute() - instance_table.insert(stopped_instance).execute() - instance_table.insert(terminated_instance).execute() - instance_table.insert(migrating_instance).execute() - instance_table.insert(scheduling_instance).execute() - instance_table.insert(bad_instance).execute() - - def upgrade(migrate_engine): - #migrate_engine.echo = True meta.bind = migrate_engine instance_table = Table('instances', meta, autoload=True, autoload_with=migrate_engine) - _insert_test_data(instance_table) - for instance in instance_table.select().execute(): - LOG.info(instance) + c_state = instance_table.c.state c_state.alter(name='power_state') @@ -148,9 +121,8 @@ def upgrade(migrate_engine): "WHERE vm_state=:old_state") migrate_engine.execute(update, **new_values) - for instance in instance_table.select().execute(): - LOG.info(instance) +def downgrade(migrate_engine): meta.bind = migrate_engine instance_table = Table('instances', meta, autoload=True, @@ -170,15 +142,10 @@ def upgrade(migrate_engine): "WHERE vm_state=:old_vm_state") migrate_engine.execute(update, locals()) - #c_state = instance_table.c.power_state + c_state = instance_table.c.power_state c_state.alter(name='state') - #c_vm_state = instance_table.c.vm_state + c_vm_state = instance_table.c.vm_state c_vm_state.alter(name='state_description') instance_table.drop_column('task_state') - - for instance in instance_table.select().execute(): - LOG.info(instance) - - raise Exception() -- cgit From 1ee1bda6cd164bd1e3cc400838830a747371ce9e Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 11:27:02 -0400 Subject: Conversion to SQLAlchemy-style. --- .../versions/042_update_instance_states.py | 55 +++++++++------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py index 10704d0da..1005ee8a4 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py @@ -33,35 +33,35 @@ c_task_state = Column('task_state', _upgrade_translations = { "stopping": { - "vm_state": vm_states.ACTIVE, + "state_description": vm_states.ACTIVE, "task_state": task_states.STOPPING, }, "stopped": { - "vm_state": vm_states.STOPPED, + "state_description": vm_states.STOPPED, "task_state": None, }, "terminated": { - "vm_state": vm_states.DELETED, + "state_description": vm_states.DELETED, "task_state": None, }, "terminating": { - "vm_state": vm_states.ACTIVE, + "state_description": vm_states.ACTIVE, "task_state": task_states.DELETING, }, "running": { - "vm_state": vm_states.ACTIVE, + "state_description": vm_states.ACTIVE, "task_state": None, }, "scheduling": { - "vm_state": vm_states.BUILDING, + "state_description": vm_states.BUILDING, "task_state": task_states.SCHEDULING, }, "migrating": { - "vm_state": vm_states.MIGRATING, + "state_description": vm_states.MIGRATING, "task_state": None, }, "pending": { - "vm_state": vm_states.BUILDING, + "state_description": vm_states.BUILDING, "task_state": task_states.SCHEDULING, }, } @@ -107,19 +107,10 @@ def upgrade(migrate_engine): instance_table.create_column(c_task_state) for old_state, values in _upgrade_translations.iteritems(): - new_values = { - "old_state": old_state, - "vm_state": values["vm_state"], - "task_state": values["task_state"], - } - - update = sqlalchemy.text("UPDATE instances SET task_state=:task_state " - "WHERE vm_state=:old_state") - migrate_engine.execute(update, **new_values) - - update = sqlalchemy.text("UPDATE instances SET vm_state=:vm_state " - "WHERE vm_state=:old_state") - migrate_engine.execute(update, **new_values) + instance_table.update().\ + values(**values).\ + where(c_vm_state == old_state).\ + execute() def downgrade(migrate_engine): @@ -128,19 +119,7 @@ def downgrade(migrate_engine): instance_table = Table('instances', meta, autoload=True, autoload_with=migrate_engine) - for old_vm_state, old_task_states in _downgrade_translations.iteritems(): - for old_task_state, new_state_desc in old_task_states.iteritems(): - if old_task_state: - update = sqlalchemy.text("UPDATE instances " - "SET vm_state=:new_state_desc " - "WHERE task_state=:old_task_state " - "AND vm_state=:old_vm_state") - migrate_engine.execute(update, locals()) - else: - update = sqlalchemy.text("UPDATE instances " - "SET vm_state=:new_state_desc " - "WHERE vm_state=:old_vm_state") - migrate_engine.execute(update, locals()) + c_task_state = instance_table.c.task_state c_state = instance_table.c.power_state c_state.alter(name='state') @@ -148,4 +127,12 @@ def downgrade(migrate_engine): c_vm_state = instance_table.c.vm_state c_vm_state.alter(name='state_description') + for old_vm_state, old_task_states in _downgrade_translations.iteritems(): + for old_task_state, new_state_desc in old_task_states.iteritems(): + instance_table.update().\ + where(c_task_state == old_task_state).\ + where(c_vm_state == old_vm_state).\ + values(state_description=new_state_desc).\ + execute() + instance_table.drop_column('task_state') -- cgit From 53b0a2ea13e148fc5f461211ca9056b30db6c43d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 11:32:58 -0400 Subject: Fix for migrations. --- .../versions/042_update_instance_states.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py index 1005ee8a4..65bdf601d 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py @@ -33,35 +33,35 @@ c_task_state = Column('task_state', _upgrade_translations = { "stopping": { - "state_description": vm_states.ACTIVE, + "vm_state": vm_states.ACTIVE, "task_state": task_states.STOPPING, }, "stopped": { - "state_description": vm_states.STOPPED, + "vm_state": vm_states.STOPPED, "task_state": None, }, "terminated": { - "state_description": vm_states.DELETED, + "vm_state": vm_states.DELETED, "task_state": None, }, "terminating": { - "state_description": vm_states.ACTIVE, + "vm_state": vm_states.ACTIVE, "task_state": task_states.DELETING, }, "running": { - "state_description": vm_states.ACTIVE, + "vm_state": vm_states.ACTIVE, "task_state": None, }, "scheduling": { - "state_description": vm_states.BUILDING, + "vm_state": vm_states.BUILDING, "task_state": task_states.SCHEDULING, }, "migrating": { - "state_description": vm_states.MIGRATING, + "vm_state": vm_states.MIGRATING, "task_state": None, }, "pending": { - "state_description": vm_states.BUILDING, + "vm_state": vm_states.BUILDING, "task_state": task_states.SCHEDULING, }, } @@ -106,6 +106,9 @@ def upgrade(migrate_engine): instance_table.create_column(c_task_state) + instance_table = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + for old_state, values in _upgrade_translations.iteritems(): instance_table.update().\ values(**values).\ -- cgit From 007efcab4b668e7a4b1d26ff274693824f6d7445 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 12:26:05 -0400 Subject: Attempt to fix issue when deleting an instance when it's still in BUILD. --- nova/compute/manager.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0a1dc13be..802d141ef 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -429,10 +429,15 @@ class ComputeManager(manager.SchedulerDependentManager): return current_power_state = self._get_power_state(context, instance) + if current_power_state == power_state.RUNNING: + vm_state = vm_states.ACTIVE + else: + vm_state = vm_states.BUILDING + self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_states.ACTIVE, + vm_state=vm_state, task_state=None, launched_at=utils.utcnow()) -- cgit From ca8b3c7635208ab0776f51661708ecea1bfc222a Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 12:42:42 -0400 Subject: Fixed rebuild naming issue and reverted other fix which didn't fix anythin. --- nova/compute/api.py | 2 ++ nova/compute/manager.py | 7 +------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index eeb8f47d9..02bae7262 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1045,6 +1045,8 @@ class API(base.Base): metadata=None, files_to_inject=None): """Rebuild the given instance with the provided metadata.""" instance = db.api.instance_get(context, instance_id) + name = name or instance["display_name"] + invalid_rebuild_states = [ vm_states.BUILDING, vm_states.REBUILDING, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 802d141ef..0a1dc13be 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -429,15 +429,10 @@ class ComputeManager(manager.SchedulerDependentManager): return current_power_state = self._get_power_state(context, instance) - if current_power_state == power_state.RUNNING: - vm_state = vm_states.ACTIVE - else: - vm_state = vm_states.BUILDING - self._instance_update(context, instance_id, power_state=current_power_state, - vm_state=vm_state, + vm_state=vm_states.ACTIVE, task_state=None, launched_at=utils.utcnow()) -- cgit From 64f946a6a0a6e08d7046ab98776928abe24f8d93 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 13:01:20 -0400 Subject: Fix for trying rebuilds when instance is not active. --- nova/api/openstack/servers.py | 10 ++++------ nova/compute/api.py | 11 +++-------- nova/exception.py | 2 +- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4ff9264a6..c5fdda1cf 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -604,9 +604,8 @@ class ControllerV10(Controller): try: self.compute_api.rebuild(context, instance_id, image_id) - except exception.BuildInProgress: - msg = _("Instance %s is currently being rebuilt.") % instance_id - LOG.debug(msg) + except exception.RebuildRequiresActiveInstance: + msg = _("Instance %s must be active to rebuild.") % instance_id raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) @@ -742,9 +741,8 @@ class ControllerV11(Controller): try: self.compute_api.rebuild(context, instance_id, image_href, name, metadata, personalities) - except exception.BuildInProgress: - msg = _("Instance %s is currently being rebuilt.") % instance_id - LOG.debug(msg) + except exception.RebuildRequiresActiveInstance: + msg = _("Instance %s must be active to rebuild.") % instance_id raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) diff --git a/nova/compute/api.py b/nova/compute/api.py index 02bae7262..7ed41fbbc 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1047,14 +1047,9 @@ class API(base.Base): instance = db.api.instance_get(context, instance_id) name = name or instance["display_name"] - invalid_rebuild_states = [ - vm_states.BUILDING, - vm_states.REBUILDING, - ] - - if instance["vm_state"] in invalid_rebuild_states: - msg = _("Instance already building") - raise exception.BuildInProgress(msg) + if instance["vm_state"] != vm_states.ACTIVE: + msg = _("Instance must be active to rebuild.") + raise exception.RebuildRequiresActiveInstance(msg) files_to_inject = files_to_inject or [] metadata = metadata or {} diff --git a/nova/exception.py b/nova/exception.py index 66740019b..889d36c96 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -61,7 +61,7 @@ class ApiError(Error): super(ApiError, self).__init__(outstr) -class BuildInProgress(Error): +class RebuildRequiresActiveInstance(Error): pass -- cgit From 1c7002db8be430cded6efb7378103e17c8df21b4 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 13:24:37 -0400 Subject: Fixed issue where we were setting the state to DELETED before it's actually deleted. --- nova/compute/manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7697e8e6c..924799dc4 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -490,15 +490,15 @@ class ComputeManager(manager.SchedulerDependentManager): def terminate_instance(self, context, instance_id): """Terminate an instance on this host.""" self._shutdown_instance(context, instance_id, 'Terminating') - instance = self.db.instance_get(context.elevated(), instance_id) + self.db.instance_destroy(context, instance_id) + self._instance_update(context, instance_id, vm_state=vm_states.DELETED, task_state=None, terminated_at=utils.utcnow()) - # TODO(ja): should we keep it in a terminated state for a bit? - self.db.instance_destroy(context, instance_id) + instance = self.db.instance_get(context.elevated(), instance_id) usage_info = utils.usage_from_instance(instance) notifier.notify('compute.%s' % self.host, 'compute.instance.delete', -- cgit From a6bf7c0b2522509dda8dd5e537fad49665aa2af2 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 13:27:23 -0400 Subject: Added DELETED status to OSAPI just in case. --- nova/api/openstack/common.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 90b2095b8..07c6fbd11 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -76,6 +76,9 @@ _STATE_MAP = { vm_states.ERROR: { 'default': 'ERROR', }, + vm_states.DELETED: { + 'default': 'DELETED', + }, } -- cgit From 3d17d22926e2f589648fdac26302a58e8c4e9069 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 24 Aug 2011 11:36:41 -0700 Subject: fix more tests --- nova/tests/api/openstack/contrib/test_createserverext.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index e5eed14fe..01c7587e3 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -89,6 +89,9 @@ class CreateserverextTest(test.TestCase): self.networks = None return [{'id': '1234', 'display_name': 'fakeinstance', 'uuid': FAKE_UUID, + 'user_id': 'fake', + 'project_id': 'fake', + 'display_description': 'fakedescription', 'created_at': "", 'updated_at': ""}] -- cgit From 0571c86d18c242f46e44e380b257cfc40598d31b Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 24 Aug 2011 12:17:58 -0700 Subject: use dict.get for user_id, project_id, and display_description in servers view as suggested by ed leaf, so that not all tests require these fields --- nova/api/openstack/views/servers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index fd7c040d4..6fd09aae6 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -65,9 +65,9 @@ class ViewBuilder(object): inst_dict = { 'id': inst['id'], 'name': inst['display_name'], - 'user_id': inst['user_id'], - 'tenant_id': inst['project_id'], - 'description': inst['display_description'], + 'user_id': inst.get('user_id', ''), + 'tenant_id': inst.get('project_id', ''), + 'description': inst.get('display_description', ''), 'status': common.status_from_power_state(inst.get('state'))} ctxt = nova.context.get_admin_context() -- cgit From 0343a328e66557abda9d0817558ad09a73962eb9 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 24 Aug 2011 14:39:47 -0700 Subject: change snapshot msg too --- nova/api/ec2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 363dad7cd..1e176e52d 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -405,7 +405,7 @@ class Executor(wsgi.Application): LOG.info(_('SnapshotNotFound raised: %s'), unicode(ex), context=context) ec2_id = ec2utils.id_to_ec2_snap_id(ex.kwargs['snapshot_id']) - message = _('Snapshot %s not found') % ec2_id + message = ex.message % {'snapshot_id': ec2_id} return self._error(req, context, type(ex).__name__, message) except exception.NotFound as ex: LOG.info(_('NotFound raised: %s'), unicode(ex), context=context) -- cgit From 575f72693fa20c7c4157c8ce9702751cd54f1a82 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 18:00:03 -0400 Subject: Fixed silly ordering issue which was causing tons of test failures. --- nova/compute/manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 924799dc4..c29eef07f 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -490,15 +490,15 @@ class ComputeManager(manager.SchedulerDependentManager): def terminate_instance(self, context, instance_id): """Terminate an instance on this host.""" self._shutdown_instance(context, instance_id, 'Terminating') - self.db.instance_destroy(context, instance_id) - + instance = self.db.instance_get(context.elevated(), instance_id) self._instance_update(context, instance_id, vm_state=vm_states.DELETED, task_state=None, terminated_at=utils.utcnow()) - instance = self.db.instance_get(context.elevated(), instance_id) + self.db.instance_destroy(context, instance_id) + usage_info = utils.usage_from_instance(instance) notifier.notify('compute.%s' % self.host, 'compute.instance.delete', -- cgit From 5f1380bfc69913f6aeb2a64e3501f77973493bc3 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 18:14:13 -0400 Subject: Added fix for parallel build test. --- nova/tests/api/openstack/test_server_actions.py | 2 +- run_tests.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 0cbbe271d..eae74e75e 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -35,7 +35,7 @@ def return_server_with_attributes(**kwargs): return _return_server -def return_server_with_state(vm_state, task_state=None) +def return_server_with_state(vm_state, task_state=None): return return_server_with_attributes(vm_state=vm_state, task_state=task_state) diff --git a/run_tests.py b/run_tests.py index fd836967e..b9a74769e 100644 --- a/run_tests.py +++ b/run_tests.py @@ -55,6 +55,7 @@ To run a single test module: """ +import eventlet import gettext import heapq import os @@ -62,6 +63,7 @@ import unittest import sys import time +eventlet.monkey_patch() gettext.install('nova', unicode=1) from nose import config -- cgit From 6c4329f846685ee54c5265e5cc56e58e6fbd55e9 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 18:25:21 -0400 Subject: stub_instance fix from merge conflict --- nova/tests/api/openstack/test_server_actions.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index eae74e75e..6fb21fad3 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -46,7 +46,8 @@ def return_server_with_uuid_and_state(vm_state, task_state=None): return _return_server -def stub_instance(id, metadata=None, image_ref="10", flavor_id="1", name=None): +def stub_instance(id, metadata=None, image_ref="10", flavor_id="1", + name=None, vm_state=None, task_state=None): if metadata is not None: metadata_items = [{'key':k, 'value':v} for k, v in metadata.items()] else: @@ -67,8 +68,8 @@ def stub_instance(id, metadata=None, image_ref="10", flavor_id="1", name=None): "launch_index": 0, "key_name": "", "key_data": "", - "vm_state": vm_states.ACTIVE, - "task_state": None, + "vm_state": vm_state or vm_states.ACTIVE, + "task_state": task_state, "memory_mb": 0, "vcpus": 0, "local_gb": 0, -- cgit From f0fcc4ba61b4658b1e28bd69cfcf395cc408496a Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 09:04:04 -0400 Subject: Another attempt at fixing hanging test. --- nova/tests/test_xenapi.py | 2 ++ run_tests.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 2f0559366..061e9ffea 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -24,6 +24,8 @@ import re import stubout import ast +eventlet.monkey_patch() + from nova import db from nova import context from nova import flags diff --git a/run_tests.py b/run_tests.py index b9a74769e..fd836967e 100644 --- a/run_tests.py +++ b/run_tests.py @@ -55,7 +55,6 @@ To run a single test module: """ -import eventlet import gettext import heapq import os @@ -63,7 +62,6 @@ import unittest import sys import time -eventlet.monkey_patch() gettext.install('nova', unicode=1) from nose import config -- cgit From 881fb85c9a74fc3436d07d3cf3876c2f815b5618 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 10:59:04 -0400 Subject: Set state to RESIZING during resizing... --- 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 960d28bd0..89e44258c 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1165,7 +1165,7 @@ class API(base.Base): self.update(context, instance_id, - vm_state=vm_states.ACTIVE, + vm_state=vm_states.RESIZING, task_state=task_states.RESIZE_PREP) instance_ref = self._get_instance(context, instance_id, 'resize') -- cgit From 6e14007c09a465374d1b50cd00549c1be6dc536c Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 11:11:51 -0400 Subject: Removed RESIZE-CONFIRM hack. --- nova/api/openstack/views/servers.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 8f8d04ca0..b0daeb7a8 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -21,8 +21,6 @@ import hashlib import os from nova import exception -import nova.compute -import nova.context from nova.api.openstack import common from nova.api.openstack.views import addresses as addresses_view from nova.api.openstack.views import flavors as flavors_view @@ -70,12 +68,6 @@ class ViewBuilder(object): 'name': inst['display_name'], 'status': common.status_from_state(vm_state, task_state)} - ctxt = nova.context.get_admin_context() - compute_api = nova.compute.API() - - if compute_api.has_finished_migration(ctxt, inst['uuid']): - inst_dict['status'] = 'RESIZE-CONFIRM' - # Return the metadata as a dictionary metadata = {} for item in inst.get('metadata', []): -- cgit From 0e3986e71f4bbc848e81f18d6c3e6ad33ab3684c Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 11:12:53 -0400 Subject: Removed invalid test. --- nova/tests/api/openstack/test_server_actions.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 6fb21fad3..b9ef41465 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -244,19 +244,6 @@ class ServerActionsTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 500) - def test_resized_server_has_correct_status(self): - req = self.webreq('/1', 'GET') - - def fake_migration_get(*args): - return {} - - self.stubs.Set(nova.db, 'migration_get_by_instance_and_status', - fake_migration_get) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - body = json.loads(res.body) - self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM') - def test_confirm_resize_server(self): req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) -- cgit From 423a29ff347d3911ba1a98aa224e2a29bdbb8d4c Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 11:17:31 -0400 Subject: Set error state when migration prep fails. --- nova/compute/manager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c29eef07f..de43a5ced 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -929,8 +929,11 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref = self.db.instance_get_by_uuid(context, instance_id) if instance_ref['host'] == FLAGS.host: - raise exception.Error(_( - 'Migration error: destination same as source!')) + self._instance_update(context, + instance_id, + vm_state=vm_states.ERROR) + msg = _('Migration error: destination same as source!') + raise exception.Error(msg) old_instance_type = self.db.instance_type_get(context, instance_ref['instance_type_id']) -- cgit From a14e2b1d8cbc87d5bcb31b9127035160fde4acc5 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 11:44:28 -0400 Subject: Verify resize needs to be set. --- nova/compute/api.py | 8 ++++---- nova/compute/manager.py | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 89e44258c..47ad04930 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1089,8 +1089,8 @@ class API(base.Base): self.update(context, instance_id, - vm_state=vm_states.VERIFY_RESIZE, - task_state=task_states.RESIZE_REVERTING) + vm_state=vm_states.ACTIVE, + task_state=None) params = {'migration_id': migration_ref['id']} self._cast_compute_message('revert_resize', context, @@ -1115,8 +1115,8 @@ class API(base.Base): self.update(context, instance_id, - vm_state=vm_states.VERIFY_RESIZE, - task_state=task_states.RESIZE_CONFIRMING) + vm_state=vm_states.ACTIVE, + task_state=None) params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index de43a5ced..5427b896e 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1027,6 +1027,11 @@ class ComputeManager(manager.SchedulerDependentManager): self.driver.finish_migration(context, instance_ref, disk_info, network_info, resize_instance) + self._instance_update(context, + instance_id, + vm_state=vm_states.VERIFY_RESIZE, + task_state=None) + self.db.migration_update(context, migration_id, {'status': 'finished', }) -- cgit From 6758779249d490fd21bfdeae6d40adfc33d8cd17 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 12:50:36 -0400 Subject: Reverted two mistakes when looking over full diff. --- nova/api/ec2/cloud.py | 10 ++++------ nova/compute/manager.py | 2 -- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index c5a360426..cf9437b08 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -104,12 +104,10 @@ def state_description_from_vm_state(vm_state): # TODO(yamahata): hypervisor dependent default device name _DEFAULT_ROOT_DEVICE_NAME = '/dev/sda1' -_DEFAULT_MAPPINGS = { - 'ami': 'sda1', - 'ephemeral0': 'sda2', - 'root': _DEFAULT_ROOT_DEVICE_NAME, - 'swap': 'sda3', -} +_DEFAULT_MAPPINGS = {'ami': 'sda1', + 'ephemeral0': 'sda2', + 'root': _DEFAULT_ROOT_DEVICE_NAME, + 'swap': 'sda3'} def _parse_block_device_mapping(bdm): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 5427b896e..a3ced1279 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -547,8 +547,6 @@ class ComputeManager(manager.SchedulerDependentManager): vm_state=vm_states.REBUILDING, task_state=task_states.BLOCK_DEVICE_MAPPING) - bd_mapping = self._setup_block_device_mapping(context, instance_id) - image_ref = kwargs.get('image_ref') instance_ref.image_ref = image_ref instance_ref.injected_files = kwargs.get('injected_files', []) -- cgit From d8d4aff908925b2f351e77291f4a8f394994063d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 16:38:38 -0400 Subject: Review feedback. --- nova/api/ec2/cloud.py | 8 +++++--- nova/api/openstack/common.py | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index cf9437b08..ac247a0ef 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -79,7 +79,9 @@ def _gen_key(context, user_id, key_name): return {'private_key': private_key, 'fingerprint': fingerprint} -# EC2 API: Valid Values: +# EC2 API can return the following values as documented in the EC2 API +# http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ +# ApiReference-ItemType-InstanceStateType.html # pending | running | shutting-down | terminated | stopping | stopped _STATE_DESCRIPTION_MAP = { None: 'pending', @@ -1065,8 +1067,8 @@ class CloudController(object): result): vm_state = instance['vm_state'] state_to_value = { - vm_states.STOPPED: 'stop', - vm_states.DELETED: 'terminate', + vm_states.STOPPED: 'stopped', + vm_states.DELETED: 'terminated', } value = state_to_value.get(vm_state) if value: diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 07c6fbd11..bdbae0271 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -84,10 +84,11 @@ _STATE_MAP = { def status_from_state(vm_state, task_state='default'): """Given vm_state and task_state, return a status string.""" - LOG.debug("Generating status for vm_state=%(vm_state)s " - "task_state=%(task_state)s." % locals()) task_map = _STATE_MAP.get(vm_state, dict(default='UNKNOWN_STATE')) - return task_map.get(task_state, task_map['default']) + status = task_map.get(task_state, task_map['default']) + LOG.debug("Generated %(status)s from vm_state=%(vm_state)s " + "task_state=%(task_state)s." % locals()) + return status def vm_state_from_status(status): -- cgit From ae1ac682673648f2a2f364eabd525985f3d16a9d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 16:40:15 -0400 Subject: Bumped migration number. --- .../versions/042_update_instance_states.py | 141 --------------------- .../versions/043_update_instance_states.py | 141 +++++++++++++++++++++ 2 files changed, 141 insertions(+), 141 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py deleted file mode 100644 index 65bdf601d..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/042_update_instance_states.py +++ /dev/null @@ -1,141 +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. - -import sqlalchemy -from sqlalchemy import MetaData, Table, Column, String - -from nova.compute import task_states -from nova.compute import vm_states - - -meta = MetaData() - - -c_task_state = Column('task_state', - String(length=255, convert_unicode=False, - assert_unicode=None, unicode_error=None, - _warn_on_bytestring=False), - nullable=True) - - -_upgrade_translations = { - "stopping": { - "vm_state": vm_states.ACTIVE, - "task_state": task_states.STOPPING, - }, - "stopped": { - "vm_state": vm_states.STOPPED, - "task_state": None, - }, - "terminated": { - "vm_state": vm_states.DELETED, - "task_state": None, - }, - "terminating": { - "vm_state": vm_states.ACTIVE, - "task_state": task_states.DELETING, - }, - "running": { - "vm_state": vm_states.ACTIVE, - "task_state": None, - }, - "scheduling": { - "vm_state": vm_states.BUILDING, - "task_state": task_states.SCHEDULING, - }, - "migrating": { - "vm_state": vm_states.MIGRATING, - "task_state": None, - }, - "pending": { - "vm_state": vm_states.BUILDING, - "task_state": task_states.SCHEDULING, - }, -} - - -_downgrade_translations = { - vm_states.ACTIVE: { - None: "running", - task_states.DELETING: "terminating", - task_states.STOPPING: "stopping", - }, - vm_states.BUILDING: { - None: "pending", - task_states.SCHEDULING: "scheduling", - }, - vm_states.STOPPED: { - None: "stopped", - }, - vm_states.REBUILDING: { - None: "pending", - }, - vm_states.DELETED: { - None: "terminated", - }, - vm_states.MIGRATING: { - None: "migrating", - }, -} - - -def upgrade(migrate_engine): - meta.bind = migrate_engine - - instance_table = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - c_state = instance_table.c.state - c_state.alter(name='power_state') - - c_vm_state = instance_table.c.state_description - c_vm_state.alter(name='vm_state') - - instance_table.create_column(c_task_state) - - instance_table = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - for old_state, values in _upgrade_translations.iteritems(): - instance_table.update().\ - values(**values).\ - where(c_vm_state == old_state).\ - execute() - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - - instance_table = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - c_task_state = instance_table.c.task_state - - c_state = instance_table.c.power_state - c_state.alter(name='state') - - c_vm_state = instance_table.c.vm_state - c_vm_state.alter(name='state_description') - - for old_vm_state, old_task_states in _downgrade_translations.iteritems(): - for old_task_state, new_state_desc in old_task_states.iteritems(): - instance_table.update().\ - where(c_task_state == old_task_state).\ - where(c_vm_state == old_vm_state).\ - values(state_description=new_state_desc).\ - execute() - - instance_table.drop_column('task_state') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py new file mode 100644 index 000000000..65bdf601d --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py @@ -0,0 +1,141 @@ +# 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. + +import sqlalchemy +from sqlalchemy import MetaData, Table, Column, String + +from nova.compute import task_states +from nova.compute import vm_states + + +meta = MetaData() + + +c_task_state = Column('task_state', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + + +_upgrade_translations = { + "stopping": { + "vm_state": vm_states.ACTIVE, + "task_state": task_states.STOPPING, + }, + "stopped": { + "vm_state": vm_states.STOPPED, + "task_state": None, + }, + "terminated": { + "vm_state": vm_states.DELETED, + "task_state": None, + }, + "terminating": { + "vm_state": vm_states.ACTIVE, + "task_state": task_states.DELETING, + }, + "running": { + "vm_state": vm_states.ACTIVE, + "task_state": None, + }, + "scheduling": { + "vm_state": vm_states.BUILDING, + "task_state": task_states.SCHEDULING, + }, + "migrating": { + "vm_state": vm_states.MIGRATING, + "task_state": None, + }, + "pending": { + "vm_state": vm_states.BUILDING, + "task_state": task_states.SCHEDULING, + }, +} + + +_downgrade_translations = { + vm_states.ACTIVE: { + None: "running", + task_states.DELETING: "terminating", + task_states.STOPPING: "stopping", + }, + vm_states.BUILDING: { + None: "pending", + task_states.SCHEDULING: "scheduling", + }, + vm_states.STOPPED: { + None: "stopped", + }, + vm_states.REBUILDING: { + None: "pending", + }, + vm_states.DELETED: { + None: "terminated", + }, + vm_states.MIGRATING: { + None: "migrating", + }, +} + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + instance_table = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_state = instance_table.c.state + c_state.alter(name='power_state') + + c_vm_state = instance_table.c.state_description + c_vm_state.alter(name='vm_state') + + instance_table.create_column(c_task_state) + + instance_table = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + for old_state, values in _upgrade_translations.iteritems(): + instance_table.update().\ + values(**values).\ + where(c_vm_state == old_state).\ + execute() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + instance_table = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_task_state = instance_table.c.task_state + + c_state = instance_table.c.power_state + c_state.alter(name='state') + + c_vm_state = instance_table.c.vm_state + c_vm_state.alter(name='state_description') + + for old_vm_state, old_task_states in _downgrade_translations.iteritems(): + for old_task_state, new_state_desc in old_task_states.iteritems(): + instance_table.update().\ + where(c_task_state == old_task_state).\ + where(c_vm_state == old_vm_state).\ + values(state_description=new_state_desc).\ + execute() + + instance_table.drop_column('task_state') -- cgit From 358c346a7d01336f23b21a18f33c3213b3672f0a Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 25 Aug 2011 15:53:13 -0500 Subject: updated libvirt tests to use fake_network_info --- nova/network/manager.py | 2 +- nova/tests/fake_network_info.py | 30 ++++++++++++++++++++----- nova/tests/test_libvirt.py | 49 +++++++++++++++++++++++------------------ 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index dcd5aad9a..9a4b09ad8 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -483,7 +483,6 @@ class NetworkManager(manager.SchedulerDependentManager): 'bridge': network['bridge'], 'id': network['id'], 'cidr': network['cidr'], - 'cidr_v6': network['cidr_v6'], 'injected': network['injected'], 'vlan': network['vlan'], 'bridge_interface': network['bridge_interface'], @@ -509,6 +508,7 @@ class NetworkManager(manager.SchedulerDependentManager): if network['cidr_v6']: info['ip6s'] = [ip6_dict()] + network_dict['cidr_v6'] = network['cidr_v6'], # TODO(tr3buchet): handle ip6 routes here as well if network['gateway_v6']: info['gateway6'] = network['gateway_v6'] diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 072585195..0bf117a90 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -16,11 +16,13 @@ # under the License. from nova import db +from nova import flags from nova import test from nova.network import manager as network_manager HOST = "testhost" +FLAGS = flags.FLAGS class FakeModel(dict): @@ -32,15 +34,14 @@ class FakeModel(dict): return self[name] -def fake_network(n): - return {'id': n, +def fake_network(n, ipv6=None): + if ipv6 = None: + ipv6 = Flags.use_ipv6 + rval = {'id': n, 'label': 'test%d' % n, 'injected': False, 'multi_host': False, 'cidr': '192.168.%d.0/24' % n, - 'cidr_v6': '2001:db8:0:%x::/64' % n, - 'gateway_v6': '2001:db8:0:%x::1' % n, - 'netmask_v6': '64', 'netmask': '255.255.255.0', 'bridge': 'fake_br%d' % n, 'bridge_interface': 'fake_eth%d' % n, @@ -52,6 +53,10 @@ def fake_network(n): 'host': None, 'project_id': 'fake_project', 'vpn_public_address': '192.168.%d.2' % n} + if ipv6: + rval['cidr_v6'] = '2001:db8:0:%x::/64' % n + rval['gateway_v6'] = '2001:db8:0:%x::1' % n + rval['netmask_v6'] = '64' def fixed_ips(num_networks, num_ips, num_floating_ips=0): @@ -91,7 +96,22 @@ def vifs(n): 'instance_id': 0} +def ipv4_like(ip, s): + ip = ip.split('.') + s = s.split('.') + + for i, octet in enumerate(s): + if octet == '*': + continue + if octet != ip[i]: + return False + return True + + def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): + # stubs is the self.stubs from the test + # ips_per_vif is the number of ips each vif will have + # num_floating_ips is number of float ips for each fixed ip network = network_manager.FlatManager(host=HOST) network.db = db diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 6a213b4f0..1b5a2d55d 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -36,6 +36,7 @@ from nova.api.ec2 import cloud from nova.compute import power_state from nova.virt.libvirt import connection from nova.virt.libvirt import firewall +from nova.tests import fake_network_info libvirt = None FLAGS = flags.FLAGS @@ -45,7 +46,8 @@ def _concurrency(wait, done, target): wait.wait() done.send() - +_fake_network_info = fake_network_info.fake_get_instance_nw_info +_ipv4_like = fake_network_info.ipv4_like def _create_network_info(count=1, ipv6=None): if ipv6 is None: ipv6 = FLAGS.use_ipv6 @@ -276,12 +278,12 @@ class LibvirtConnTestCase(test.TestCase): instance_ref = db.instance_create(self.context, self.test_instance) result = conn._prepare_xml_info(instance_ref, - _create_network_info(), + _fake_network_info(self.stubs, 1), False) self.assertTrue(len(result['nics']) == 1) result = conn._prepare_xml_info(instance_ref, - _create_network_info(2), + _fake_network_info(self.stubs, 2), False) self.assertTrue(len(result['nics']) == 2) @@ -406,7 +408,7 @@ class LibvirtConnTestCase(test.TestCase): def test_multi_nic(self): instance_data = dict(self.test_instance) - network_info = _create_network_info(2) + network_info = _fake_network_info(self.stubs, 2) conn = connection.LibvirtConnection(True) instance_ref = db.instance_create(self.context, instance_data) xml = conn.to_xml(instance_ref, network_info, False) @@ -416,9 +418,9 @@ class LibvirtConnTestCase(test.TestCase): 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'), '10.11.12.13') + self.assertTrue(_ipv4_like(parameters[0].get('value'), '192.168') self.assertEquals(parameters[1].get('name'), 'DHCPSERVER') - self.assertEquals(parameters[1].get('value'), '10.0.0.1') + self.assertTrue(_ipv4_like(parameters[1].get('value'), '192.168.*.1') def _check_xml_and_container(self, instance): user_context = context.RequestContext(self.user_id, @@ -432,7 +434,7 @@ class LibvirtConnTestCase(test.TestCase): uri = conn.get_uri() self.assertEquals(uri, 'lxc:///') - network_info = _create_network_info() + network_info = _fake_network_info(self.stubs, 1) xml = conn.to_xml(instance_ref, network_info) tree = xml_to_tree(xml) @@ -503,9 +505,11 @@ class LibvirtConnTestCase(test.TestCase): common_checks = [ (lambda t: t.find('.').tag, 'domain'), (lambda t: t.find(parameter).get('name'), 'IP'), - (lambda t: t.find(parameter).get('value'), '10.11.12.13'), + (lambda t: _ipv4_like(t.find(parameter).get('value'), '192.168'), + True), (lambda t: t.findall(parameter)[1].get('name'), 'DHCPSERVER'), - (lambda t: t.findall(parameter)[1].get('value'), '10.0.0.1'), + (lambda t: _ipv4_like(t.findall(parameter)[1].get('value'), + '192.168.*.1'), True), (lambda t: t.find('./devices/serial/source').get( 'path').split('/')[1], 'console.log'), (lambda t: t.find('./memory').text, '2097152')] @@ -530,7 +534,7 @@ class LibvirtConnTestCase(test.TestCase): uri = conn.get_uri() self.assertEquals(uri, expected_uri) - network_info = _create_network_info() + network_info = _fake_network_info(self.stubs, 1) xml = conn.to_xml(instance_ref, network_info, rescue) tree = xml_to_tree(xml) for i, (check, expected_result) in enumerate(checks): @@ -645,7 +649,7 @@ class LibvirtConnTestCase(test.TestCase): self.create_fake_libvirt_mock() instance_ref = db.instance_create(self.context, self.test_instance) - network_info = _create_network_info() + network_info = _fake_network_info(self.stubs, 1) # Start test self.mox.ReplayAll() @@ -828,7 +832,7 @@ class LibvirtConnTestCase(test.TestCase): conn.firewall_driver.setattr('setup_basic_filtering', fake_none) conn.firewall_driver.setattr('prepare_instance_filter', fake_none) - network_info = _create_network_info() + network_info = _fake_network_info(self.stubs, 1) try: conn.spawn(self.context, instance, network_info) @@ -1062,7 +1066,7 @@ class IptablesFirewallTestCase(test.TestCase): from nova.network import linux_net linux_net.iptables_manager.execute = fake_iptables_execute - network_info = _create_network_info() + network_info = _fake_network_info(self.stubs, 1) self.fw.prepare_instance_filter(instance_ref, network_info) self.fw.apply_instance_filter(instance_ref, network_info) @@ -1076,7 +1080,8 @@ class IptablesFirewallTestCase(test.TestCase): instance_chain = None for rule in self.out_rules: # This is pretty crude, but it'll do for now - if '-d 10.11.12.13 -j' in rule: + # last two octets change + if re.search('-d 192.168.[0-9]{1,3}.[0-9]{1,3} -j', rule): instance_chain = rule.split(' ')[-1] break self.assertTrue(instance_chain, "The instance chain wasn't added") @@ -1112,14 +1117,14 @@ class IptablesFirewallTestCase(test.TestCase): def test_filters_for_instance_with_ip_v6(self): self.flags(use_ipv6=True) - network_info = _create_network_info() + network_info = _fake_network_info(self.stubs, 1) 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() + network_info = _fake_network_info(self.stubs, 1) rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) self.assertEquals(len(rulesv4), 2) self.assertEquals(len(rulesv6), 0) @@ -1129,7 +1134,7 @@ class IptablesFirewallTestCase(test.TestCase): ipv6_rules_per_network = 3 networks_count = 5 instance_ref = self._create_instance_ref() - network_info = _create_network_info(networks_count) + network_info = _fake_network_info(self.stubs, 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, @@ -1169,7 +1174,7 @@ class IptablesFirewallTestCase(test.TestCase): instance_ref = self._create_instance_ref() _setup_networking(instance_ref['id'], self.test_ip) - network_info = _create_network_info() + network_info = _fake_network_info(self.stubs, 1) self.fw.setup_basic_filtering(instance_ref, network_info) self.fw.prepare_instance_filter(instance_ref, network_info) self.fw.apply_instance_filter(instance_ref, network_info) @@ -1190,7 +1195,7 @@ class IptablesFirewallTestCase(test.TestCase): # create a firewall via setup_basic_filtering like libvirt_conn.spawn # should have a chain with 0 rules - network_info = _create_network_info(1) + network_info = _fake_network_info(self.stubs, 1) self.fw.setup_basic_filtering(instance_ref, network_info) self.assertTrue('provider' in self.fw.iptables.ipv4['filter'].chains) rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules @@ -1390,7 +1395,7 @@ class NWFilterTestCase(test.TestCase): self.security_group.id) instance = db.instance_get(self.context, inst_id) - network_info = _create_network_info() + network_info = _fake_network_info(self.stubs, 1) self.fw.setup_basic_filtering(instance, network_info) self.fw.prepare_instance_filter(instance, network_info) self.fw.apply_instance_filter(instance, network_info) @@ -1400,7 +1405,7 @@ class NWFilterTestCase(test.TestCase): def test_create_network_filters(self): instance_ref = self._create_instance() - network_info = _create_network_info(3) + network_info = _fake_network_info(self.stubs, 3) result = self.fw._create_network_filters(instance_ref, network_info, "fake") @@ -1424,7 +1429,7 @@ class NWFilterTestCase(test.TestCase): instance = db.instance_get(self.context, inst_id) _setup_networking(instance_ref['id'], self.test_ip) - network_info = _create_network_info() + network_info = _fake_network_info(self.stubs, 1) self.fw.setup_basic_filtering(instance, network_info) self.fw.prepare_instance_filter(instance, network_info) self.fw.apply_instance_filter(instance, network_info) -- cgit From 416b270374d64f976c91a3efb74dcb467bfbcc8c Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 25 Aug 2011 16:02:52 -0500 Subject: fixed a couple of syntax errors --- nova/tests/fake_network_info.py | 2 +- nova/tests/test_libvirt.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 0bf117a90..113647d7f 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -35,7 +35,7 @@ class FakeModel(dict): def fake_network(n, ipv6=None): - if ipv6 = None: + if ipv6 == None: ipv6 = Flags.use_ipv6 rval = {'id': n, 'label': 'test%d' % n, diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 1b5a2d55d..06ad7d13a 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -418,9 +418,9 @@ class LibvirtConnTestCase(test.TestCase): parameters = interfaces[0].findall('./filterref/parameter') self.assertEquals(interfaces[0].get('type'), 'bridge') self.assertEquals(parameters[0].get('name'), 'IP') - self.assertTrue(_ipv4_like(parameters[0].get('value'), '192.168') + self.assertTrue(_ipv4_like(parameters[0].get('value'), '192.168')) self.assertEquals(parameters[1].get('name'), 'DHCPSERVER') - self.assertTrue(_ipv4_like(parameters[1].get('value'), '192.168.*.1') + self.assertTrue(_ipv4_like(parameters[1].get('value'), '192.168.*.1')) def _check_xml_and_container(self, instance): user_context = context.RequestContext(self.user_id, -- cgit From 5daa66cb91f95ff341ce5ec762fb1060f22e587f Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 25 Aug 2011 16:10:25 -0500 Subject: typo --- nova/tests/fake_network_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 113647d7f..295ac8a97 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -36,7 +36,7 @@ class FakeModel(dict): def fake_network(n, ipv6=None): if ipv6 == None: - ipv6 = Flags.use_ipv6 + ipv6 = FLAGS.use_ipv6 rval = {'id': n, 'label': 'test%d' % n, 'injected': False, -- cgit From c316782f8879ef321c4545b04bc9d24e11bb4ee6 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 17:27:10 -0400 Subject: review feedback --- nova/api/ec2/cloud.py | 3 +-- nova/api/openstack/common.py | 7 ++----- nova/compute/manager.py | 12 ++++++------ nova/compute/task_states.py | 21 +++++++++++++++------ nova/compute/vm_states.py | 9 +++++++-- nova/db/sqlalchemy/api.py | 40 ---------------------------------------- 6 files changed, 31 insertions(+), 61 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ac247a0ef..fe44191c8 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -92,7 +92,6 @@ _STATE_DESCRIPTION_MAP = { vm_states.STOPPED: 'stopped', vm_states.MIGRATING: 'migrate', vm_states.RESIZING: 'resize', - vm_states.VERIFY_RESIZE: 'verify_resize', vm_states.PAUSED: 'pause', vm_states.SUSPENDED: 'suspend', vm_states.RESCUED: 'rescue', @@ -101,7 +100,7 @@ _STATE_DESCRIPTION_MAP = { def state_description_from_vm_state(vm_state): """Map the vm state to the server status string""" - return _STATE_DESCRIPTION_MAP[vm_state] + return _STATE_DESCRIPTION_MAP.get(vm_state, vm_state) # TODO(yamahata): hypervisor dependent default device name diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index bdbae0271..d743a66ef 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -43,8 +43,8 @@ _STATE_MAP = { vm_states.ACTIVE: { 'default': 'ACTIVE', task_states.REBOOTING: 'REBOOT', - task_states.HARD_REBOOTING: 'HARD_REBOOT', - task_states.PASSWORD: 'PASSWORD', + task_states.UPDATING_PASSWORD: 'PASSWORD', + task_states.RESIZE_VERIFY: 'VERIFY_RESIZE', }, vm_states.BUILDING: { 'default': 'BUILD', @@ -61,9 +61,6 @@ _STATE_MAP = { vm_states.RESIZING: { 'default': 'RESIZE', }, - vm_states.VERIFY_RESIZE: { - 'default': 'VERIFY_RESIZE', - }, vm_states.PAUSED: { 'default': 'PAUSED', }, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a3ced1279..b4c6abae0 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -415,7 +415,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.BUILDING, - task_state=task_states.SPAWN) + task_state=task_states.SPAWNING) # TODO(vish) check to make sure the availability zone matches try: @@ -557,7 +557,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, vm_state=vm_states.REBUILDING, - task_state=task_states.SPAWN) + task_state=task_states.SPAWNING) # pull in new password here since the original password isn't in the db instance_ref.admin_pass = kwargs.get('new_pass', @@ -629,9 +629,9 @@ class ComputeManager(manager.SchedulerDependentManager): None if rotation shouldn't be used (as in the case of snapshots) """ if image_type == "snapshot": - task_state = task_states.SNAPSHOTTING + task_state = task_states.IMAGE_SNAPSHOT elif image_type == "backup": - task_state = task_states.BACKING_UP + task_state = task_states.IMAGE_BACKUP else: raise Exception(_('Image type not recognized %s') % image_type) @@ -1027,8 +1027,8 @@ class ComputeManager(manager.SchedulerDependentManager): self._instance_update(context, instance_id, - vm_state=vm_states.VERIFY_RESIZE, - task_state=None) + vm_state=vm_states.ACTIVE, + task_state=task_states.RESIZE_VERIFY) self.db.migration_update(context, migration_id, {'status': 'finished', }) diff --git a/nova/compute/task_states.py b/nova/compute/task_states.py index 5f78495ea..e3315a542 100644 --- a/nova/compute/task_states.py +++ b/nova/compute/task_states.py @@ -15,16 +15,25 @@ # License for the specific language governing permissions and limitations # under the License. -"""Possible task states for instances""" +"""Possible task states for instances. + +Compute instance task states represent what is happening to the instance at the +current moment. These tasks can be generic, such as 'spawning', or specific, +such as 'block_device_mapping'. These task states allow for a better view into +what an instance is doing and should be displayed to users/administrators as +necessary. + +""" SCHEDULING = 'scheduling' BLOCK_DEVICE_MAPPING = 'block_device_mapping' NETWORKING = 'networking' -SPAWN = 'spawn' +SPAWNING = 'spawning' + +IMAGE_SNAPSHOT = 'image_snapshot' +IMAGE_BACKUP = 'image_backup' -SNAPSHOTTING = 'snapshotting' -BACKING_UP = 'backing_up' -PASSWORD = 'password' +UPDATING_PASSWORD = 'updating_password' RESIZE_PREP = 'resize_prep' RESIZE_MIGRATING = 'resize_migrating' @@ -32,11 +41,11 @@ RESIZE_MIGRATED = 'resize_migrated' RESIZE_FINISH = 'resize_finish' RESIZE_REVERTING = 'resize_reverting' RESIZE_CONFIRMING = 'resize_confirming' +RESIZE_VERIFY = 'resize_verify' REBUILDING = 'rebuilding' REBOOTING = 'rebooting' -HARD_REBOOTING = 'hard_rebooting' PAUSING = 'pausing' UNPAUSING = 'unpausing' SUSPENDING = 'suspending' diff --git a/nova/compute/vm_states.py b/nova/compute/vm_states.py index 560e6d688..6f16c1f09 100644 --- a/nova/compute/vm_states.py +++ b/nova/compute/vm_states.py @@ -15,7 +15,13 @@ # License for the specific language governing permissions and limitations # under the License. -"""Possible vm states for instances""" +"""Possible vm states for instances. + +Compute instance vm states represent the state of an instance as it pertains to +a user or administrator. When combined with task states (task_states.py), a +better picture can be formed regarding the instance's health. + +""" ACTIVE = 'active' BUILDING = 'building' @@ -29,6 +35,5 @@ STOPPED = 'stopped' MIGRATING = 'migrating' RESIZING = 'resizing' -VERIFY_RESIZE = 'verify_resize' ERROR = 'error' diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 08fc81759..7b78e286d 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1484,46 +1484,6 @@ def instance_get_floating_address(context, instance_id): return fixed_ip_refs[0].floating_ips[0]['address'] -@require_admin_context -def instance_set_power_state(context, instance_id, power_state): - session = get_session() - partial = session.query(models.Instance) - - if utils.is_uuid_like(instance_id): - result = partial.filter_by(uuid=instance_id) - else: - result = partial.filter_by(id=instance_id) - - result.update({'power_state': power_state}) - - -@require_admin_context -def instance_set_vm_state(context, instance_id, vm_state): - # vm_state = running, halted, suspended, paused - session = get_session() - partial = session.query(models.Instance) - - if utils.is_uuid_like(instance_id): - result = partial.filter_by(uuid=instance_id) - else: - result = partial.filter_by(id=instance_id) - - result.update({'vm_state': vm_state}) - - -def instance_set_task_state(context, instance_id, task_state): - # task_state = running, halted, suspended, paused - session = get_session() - partial = session.query(models.Instance) - - if utils.is_uuid_like(instance_id): - result = partial.filter_by(uuid=instance_id) - else: - result = partial.filter_by(id=instance_id) - - result.update({'task_state': task_state}) - - @require_context def instance_update(context, instance_id, values): session = get_session() -- cgit From 590e7445d937485280e60743318000d0bfc80236 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 25 Aug 2011 16:49:09 -0500 Subject: forget a return --- nova/tests/fake_network_info.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 295ac8a97..b05cf7c95 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -58,6 +58,8 @@ def fake_network(n, ipv6=None): rval['gateway_v6'] = '2001:db8:0:%x::1' % n rval['netmask_v6'] = '64' + return rval + def fixed_ips(num_networks, num_ips, num_floating_ips=0): for network in xrange(num_networks): -- cgit From 2cf0b67e08e1608bd717ffadd41d5029db2b4a3a Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Thu, 25 Aug 2011 21:56:45 +0000 Subject: Fix glance image authorization check now that glance can do authorization checks on its own; use correct image service when looking for ramdisk, etc.; fix a couple of PEP8 errors --- nova/api/openstack/create_instance_helper.py | 6 +++--- nova/image/glance.py | 14 ++++++++++++++ nova/ipv6/account_identifier.py | 3 ++- nova/tests/test_ipv6.py | 2 +- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 483ff4985..c428a8209 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -98,7 +98,7 @@ class CreateInstanceHelper(object): try: image_service, image_id = nova.image.get_image_service(image_href) kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( - req, image_id) + req, image_service, image_id) images = set([str(x['id']) for x in image_service.index(context)]) assert str(image_id) in images except Exception, e: @@ -248,12 +248,12 @@ class CreateInstanceHelper(object): msg = _("Server name is an empty string") raise exc.HTTPBadRequest(explanation=msg) - def _get_kernel_ramdisk_from_image(self, req, image_id): + def _get_kernel_ramdisk_from_image(self, req, image_service, image_id): """Fetch an image from the ImageService, then if present, return the associated kernel and ramdisk image IDs. """ context = req.environ['nova.context'] - image_meta = self._image_service.show(context, image_id) + image_meta = image_service.show(context, image_id) # NOTE(sirp): extracted to a separate method to aid unit-testing, the # new method doesn't need a request obj or an ImageService stub kernel_id, ramdisk_id = self._do_get_kernel_ramdisk_from_image( diff --git a/nova/image/glance.py b/nova/image/glance.py index 9060f6a91..16f803218 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -269,6 +269,20 @@ class GlanceImageService(service.BaseImageService): image_meta = _convert_from_string(image_meta) return image_meta + @staticmethod + def _is_image_available(context, image_meta): + """Check image availability. + + Under Glance, images are always available if the context has + an auth_token. Otherwise, we fall back to the superclass + method. + + """ + if hasattr(context, 'auth_token') and context.auth_token: + return True + return service.BaseImageService._is_image_available(context, + image_meta) + # utility functions def _convert_timestamps_to_datetimes(image_meta): diff --git a/nova/ipv6/account_identifier.py b/nova/ipv6/account_identifier.py index 27bb01988..8a08510ac 100644 --- a/nova/ipv6/account_identifier.py +++ b/nova/ipv6/account_identifier.py @@ -39,7 +39,8 @@ def to_global(prefix, mac, project_id): except TypeError: raise TypeError(_('Bad prefix for to_global_ipv6: %s') % prefix) except NameError: - raise TypeError(_('Bad project_id for to_global_ipv6: %s') % project_id) + raise TypeError(_('Bad project_id for to_global_ipv6: %s') % + project_id) def to_mac(ipv6_address): diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py index 04c1b5598..e1ba4aafb 100644 --- a/nova/tests/test_ipv6.py +++ b/nova/tests/test_ipv6.py @@ -48,7 +48,7 @@ class IPv6RFC2462TestCase(test.TestCase): def test_to_global_with_bad_prefix(self): bad_prefix = '82' self.assertRaises(TypeError, ipv6.to_global, - bad_prefix, + bad_prefix, '2001:db8::216:3eff:fe33:4455', 'test') -- cgit From 63b26178407423524390b2a47425b6953c910e00 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 18:00:32 -0400 Subject: Test fixup after last review feedback commit. --- nova/tests/api/openstack/test_servers.py | 11 ++++------- nova/tests/test_cloud.py | 2 +- nova/tests/test_xenapi.py | 2 -- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 3905e4f7a..f75263c45 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2488,11 +2488,6 @@ class TestServerStatus(test.TestCase): task_states.REBOOTING) self.assertEqual(response['server']['status'], 'REBOOT') - def test_hard_reboot(self): - response = self._get_with_state(vm_states.ACTIVE, - task_states.HARD_REBOOTING) - self.assertEqual(response['server']['status'], 'HARD_REBOOT') - def test_rebuild(self): response = self._get_with_state(vm_states.REBUILDING) self.assertEqual(response['server']['status'], 'REBUILD') @@ -2506,11 +2501,13 @@ class TestServerStatus(test.TestCase): self.assertEqual(response['server']['status'], 'RESIZE') def test_verify_resize(self): - response = self._get_with_state(vm_states.VERIFY_RESIZE) + response = self._get_with_state(vm_states.ACTIVE, + task_states.RESIZE_VERIFY) self.assertEqual(response['server']['status'], 'VERIFY_RESIZE') def test_password_update(self): - response = self._get_with_state(vm_states.ACTIVE, task_states.PASSWORD) + response = self._get_with_state(vm_states.ACTIVE, + task_states.UPDATING_PASSWORD) self.assertEqual(response['server']['status'], 'PASSWORD') def test_stopped(self): diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 9d58b7341..9deb5c011 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -1611,7 +1611,7 @@ class CloudTestCase(test.TestCase): self.assertEqual(groupSet, expected_groupSet) self.assertEqual(get_attribute('instanceInitiatedShutdownBehavior'), {'instance_id': 'i-12345678', - 'instanceInitiatedShutdownBehavior': 'stop'}) + 'instanceInitiatedShutdownBehavior': 'stopped'}) self.assertEqual(get_attribute('instanceType'), {'instance_id': 'i-12345678', 'instanceType': 'fake_type'}) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 061e9ffea..2f0559366 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -24,8 +24,6 @@ import re import stubout import ast -eventlet.monkey_patch() - from nova import db from nova import context from nova import flags -- cgit From 9df1fe10f39d4b1b4753a5c5366e68dd1efa9e77 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 25 Aug 2011 17:04:26 -0500 Subject: altered fake network model --- nova/tests/fake_network_info.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index b05cf7c95..47f9abd05 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -42,10 +42,13 @@ def fake_network(n, ipv6=None): 'injected': False, 'multi_host': False, 'cidr': '192.168.%d.0/24' % n, + 'cidr_v6': None, 'netmask': '255.255.255.0', + 'netmask_v6': None, 'bridge': 'fake_br%d' % n, 'bridge_interface': 'fake_eth%d' % n, 'gateway': '192.168.%d.1' % n, + 'gateway_v6': None, 'broadcast': '192.168.%d.255' % n, 'dns1': '192.168.%d.3' % n, 'dns2': '192.168.%d.4' % n, -- cgit From 2e7447a9ca180b68b49984d57e224e7fdd6d27e2 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Thu, 25 Aug 2011 17:13:09 -0700 Subject: Minor fixes --- nova/network/quantum/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index f712a93c4..f46eaf2f3 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -59,7 +59,7 @@ class QuantumManager(manager.FlatManager): FLAGS.quantum_default_tenant_id quantum_net_id = bridge if quantum_net_id: - if not q_conn.network_exists(q_tenant_id, quantum_net_id): + if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): raise Exception("Unable to find existing quantum " \ " network for tenant '%s' with net-id '%s'" % \ (q_tenant_id, quantum_net_id)) @@ -105,7 +105,7 @@ class QuantumManager(manager.FlatManager): # Create a port via quantum and attach the vif for (net_id, project_id) in net_proj_pairs: vif_rec = manager.FlatManager.add_virtual_interface(self, - context, instance, None) + context, instance_id, None) q_tenant_id = project_id or FLAGS.quantum_default_tenant_id self.q_conn.create_and_attach_port(q_tenant_id, net_id, -- cgit From 5823a72690155d9d69e4d23a81be2ea0945809dc Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 25 Aug 2011 21:09:15 -0700 Subject: add priority for static networks --- nova/network/quantum/fake.py | 13 ++++++----- nova/network/quantum/manager.py | 9 ++++---- nova/network/quantum/melange_ipam_lib.py | 30 ++++++++++++++++++++++--- nova/network/quantum/nova_ipam_lib.py | 22 ++++++++++-------- nova/tests/test_quantum.py | 38 ++++++++++++++------------------ 5 files changed, 69 insertions(+), 43 deletions(-) diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index ff2b1e9d5..a923bbf1a 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -102,8 +102,8 @@ class FakeQuantumIPAMLib(): self.subnets = {} def create_subnet(self, context, label, tenant_id, quantum_net_id, - cidr=None, gateway_v6=None, cidr_v6=None, - dns1=None, dns2=None): + priority, cidr=None, gateway_v6=None, + cidr_v6=None, dns1=None, dns2=None): if int(cidr.split("/")[1]) != 24: raise Exception("fake ipam_lib only supports /24s") v4_ips = [] @@ -116,6 +116,7 @@ class FakeQuantumIPAMLib(): "instance_id": None}) self.subnets[quantum_net_id] = {\ "label": label, + "priority": priority, "gateway": net_start + "1", "netmask": "255.255.255.0", "broadcast": net_start + "255", @@ -138,12 +139,14 @@ class FakeQuantumIPAMLib(): del self.subnets[net_id] def get_project_and_global_net_ids(self, context, project_id): - net_ids = [] + net_list = [] + id_priority_map = {} for nid, s in self.subnets.items(): if s['project_id'] == project_id or \ s['project_id'] == None: - net_ids.append((nid, s['project_id'])) - return net_ids + net_list.append((nid, s['project_id'])) + id_priority_map[nid] = s['priority'] + return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): subnet = self.subnets[quantum_net_id] diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index f712a93c4..2c2fd4dd7 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -59,7 +59,7 @@ class QuantumManager(manager.FlatManager): FLAGS.quantum_default_tenant_id quantum_net_id = bridge if quantum_net_id: - if not q_conn.network_exists(q_tenant_id, quantum_net_id): + if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): raise Exception("Unable to find existing quantum " \ " network for tenant '%s' with net-id '%s'" % \ (q_tenant_id, quantum_net_id)) @@ -68,8 +68,9 @@ class QuantumManager(manager.FlatManager): quantum_net_id = self.q_conn.create_network(q_tenant_id, label) ipam_tenant_id = kwargs.get("project_id", None) + priority = kwargs.get("priority", 0) self.ipam.create_subnet(context, label, ipam_tenant_id, quantum_net_id, - cidr, gateway_v6, cidr_v6, dns1, dns2) + priority, cidr, gateway_v6, cidr_v6, dns1, dns2) def delete_network(self, context, fixed_range): project_id = context.project_id @@ -105,7 +106,7 @@ class QuantumManager(manager.FlatManager): # Create a port via quantum and attach the vif for (net_id, project_id) in net_proj_pairs: vif_rec = manager.FlatManager.add_virtual_interface(self, - context, instance, None) + context, instance_id, None) q_tenant_id = project_id or FLAGS.quantum_default_tenant_id self.q_conn.create_and_attach_port(q_tenant_id, net_id, @@ -212,7 +213,7 @@ class QuantumManager(manager.FlatManager): self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id, net_id, vif_ref) - self.net_manager.db.virtual_interface_delete_by_instance(admin_context, + db.virtual_interface_delete_by_instance(admin_context, instance_id) self._do_trigger_security_group_members_refresh_for_instance( instance_id) diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 46038c349..526be6327 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -36,7 +36,7 @@ class QuantumMelangeIPAMLib: self.m_conn = melange_connection.MelangeConnection() def create_subnet(self, context, label, project_id, - quantum_net_id, cidr=None, + quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): tenant_id = project_id or FLAGS.quantum_default_tenant_id @@ -49,6 +49,13 @@ class QuantumMelangeIPAMLib: project_id=tenant_id, dns1=dns1, dns2=dns2) + # create a entry in the network table just to store + # the priority order for this network + net = {"bridge": quantum_net_id, + "project_id": project_id, + "priority": priority} + network = self.db.network_create_safe(context, net) + def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.allocate_ip(quantum_net_id, @@ -61,19 +68,26 @@ class QuantumMelangeIPAMLib: for b in all_blocks['ip_blocks']: if b['cidr'] == cidr: return b['network_id'] + raise Exception("No network found for cidr %s" % cidr) def delete_subnets_by_net_id(self, context, net_id, project_id): + admin_context = context.elevated() tenant_id = project_id or FLAGS.quantum_default_tenant_id all_blocks = self.m_conn.get_blocks(tenant_id) for b in all_blocks['ip_blocks']: if b['network_id'] == net_id: self.m_conn.delete_block(b['id'], tenant_id) + network = db.network_get_by_bridge(admin_context, net_id) + if network is not None: + db.network_delete_safe(context, network['id']) + # get all networks with this project_id, as well as all networks # where the project-id is not set (these are shared networks) def get_project_and_global_net_ids(self, context, project_id): + admin_context = context.elevated() id_proj_map = {} - if not project_id: + if project_id is None: raise Exception("get_project_and_global_net_ids must be called" \ " with a non-null project_id") tenant_id = project_id @@ -84,7 +98,17 @@ class QuantumMelangeIPAMLib: all_provider_blocks = self.m_conn.get_blocks(tenant_id) for b in all_provider_blocks['ip_blocks']: id_proj_map[b['network_id']] = tenant_id - return id_proj_map.items() + + id_priority_map = {} + network = db.network_get_by_bridge(admin_context, net_id) + for net_id, project_id in id_project_map.item(): + network = db.network_get_by_bridge(admin_context, net_id) + if network is None: + del id_proj_map[net_id] + else: + id_priority_map[net_id] = network['priority'] + return sorted(id_priority_map.items(), + key=lambda x: id_priority_map[x[0]]) # FIXME: there must be a more efficient way to do this, # talk to the melange folks diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 0fc74fa49..a9a6dcb29 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -42,10 +42,9 @@ class QuantumNovaIPAMLib: self.net_manager = net_manager def create_subnet(self, context, label, tenant_id, - quantum_net_id, cidr=None, + quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): - print "creating subnet %s" % cidr admin_context = context.elevated() subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) manager.FlatManager.create_networks(self.net_manager, @@ -53,9 +52,10 @@ class QuantumNovaIPAMLib: False, 1, subnet_size, cidr_v6, gateway_v6, quantum_net_id, None, dns1, dns2) - # now grab the network and update project_id + # now grab the network and update project_id + priority network = db.network_get_by_bridge(admin_context, quantum_net_id) - net = {"project_id": tenant_id} + net = {"project_id": tenant_id, + "priority": priority} db.network_update(admin_context, network['id'], net) def get_network_id_by_cidr(self, context, cidr, project_id): @@ -82,7 +82,13 @@ class QuantumNovaIPAMLib: admin_context = context.elevated() networks = db.project_get_networks(admin_context, project_id, False) networks.extend(db.project_get_networks(admin_context, None, False)) - return [(n['bridge'], n['project_id']) for n in networks] + id_priority_map = {} + net_list = [] + for n in networks: + net_id = n['bridge'] + net_list.append((net_id, n["project_id"])) + id_priority_map[net_id] = n['priority'] + return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): admin_context = context.elevated() @@ -104,8 +110,7 @@ class QuantumNovaIPAMLib: 'broadcast': n['broadcast'], 'netmask': n['netmask'], 'dns1': n['dns1'], - 'dns2': n['dns2'] - } + 'dns2': n['dns2']} subnet_data_v6 = { 'network_id': n['bridge'], 'cidr': n['cidr_v6'], @@ -113,8 +118,7 @@ class QuantumNovaIPAMLib: 'broadcast': None, 'netmask': None, 'dns1': None, - 'dns2': None - } + 'dns2': None} return (subnet_data_v4, subnet_data_v6) def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 378beb6ed..80cab950e 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -41,7 +41,8 @@ networks = [{'label': 'project1-net1', 'vlan': None, 'host': None, 'vpn_public_address': None, - 'project_id': 'fake_project1'}, + 'project_id': 'fake_project1', + 'priority': 1}, {'label': 'project2-net1', 'injected': False, 'multi_host': False, @@ -59,7 +60,7 @@ networks = [{'label': 'project1-net1', 'vlan': None, 'host': None, 'project_id': 'fake_project2', - 'vpn_public_address': '192.168.1.2'}, + 'priority': 1}, {'label': "public", 'injected': False, 'multi_host': False, @@ -76,8 +77,8 @@ networks = [{'label': 'project1-net1', 'dns2': '10.0.0.2', 'vlan': None, 'host': None, - 'vpn_public_address': None, - 'project_id': None}, + 'project_id': None, + 'priority': 0}, {'label': "project2-net2", 'injected': False, 'multi_host': False, @@ -94,8 +95,8 @@ networks = [{'label': 'project1-net1', 'dns2': '9.0.0.2', 'vlan': None, 'host': None, - 'vpn_public_address': None, - 'project_id': "fake_project2"}] + 'project_id': "fake_project2", + 'priority': 2}] # this is a base class to be used by all other Quantum Test classes @@ -114,7 +115,8 @@ class QuantumTestCaseBase(object): num_networks=1, network_size=256, cidr_v6=n['cidr_v6'], gateway_v6=n['gateway_v6'], bridge=None, bridge_interface=None, dns1=n['dns1'], - dns2=n['dns2'], project_id=n['project_id']) + dns2=n['dns2'], project_id=n['project_id'], + priority=n['priority']) def _delete_nets(self): for n in networks: @@ -138,29 +140,21 @@ class QuantumTestCaseBase(object): # we don't know which order the NICs will be in until we # introduce the notion of priority # v4 cidr - self.assertTrue(nw_info[0][0]['cidr'].startswith("10.") or \ - nw_info[1][0]['cidr'].startswith("10.")) - self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or \ - nw_info[1][0]['cidr'].startswith("192.")) + self.assertTrue(nw_info[0][0]['cidr'].startswith("10.")) + self.assertTrue(nw_info[1][0]['cidr'].startswith("192.")) # v4 address - self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("10.") or \ - nw_info[1][1]['ips'][0]['ip'].startswith("10.")) - self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or \ - nw_info[1][1]['ips'][0]['ip'].startswith("192.")) + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("10.")) + self.assertTrue(nw_info[1][1]['ips'][0]['ip'].startswith("192.")) # v6 cidr - self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dba:") or \ - nw_info[1][0]['cidr_v6'].startswith("2001:1dba:")) - self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db8:") or \ - nw_info[1][0]['cidr_v6'].startswith("2001:1db8:")) + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dba:")) + self.assertTrue(nw_info[1][0]['cidr_v6'].startswith("2001:1db8:")) # v6 address self.assertTrue(\ - nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dba:") or \ - nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dba:")) + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dba:")) self.assertTrue(\ - nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db8:") or \ nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db8:")) self.net_man.deallocate_for_instance(ctx, -- cgit From 16290908eaca73caa6b3f2ce36fb8add0b7d3615 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 25 Aug 2011 22:45:24 -0700 Subject: fix for quantum api changes, change nova-mange to have quantum_list command --- bin/nova-manage | 23 +++++++++++++++++------ nova/network/quantum/client.py | 2 +- nova/network/quantum/quantum_connection.py | 4 ++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 9819ef206..4628e93b2 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -773,7 +773,7 @@ class NetworkCommands(object): def list(self): """List all created networks""" _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s"\ - "\t%-15s\t%-15s\t-15s\t-15s" + "\t%-15s\t%-15s" print _fmt % (_('id'), _('IPv4'), _('IPv6'), @@ -782,9 +782,7 @@ class NetworkCommands(object): _('DNS2'), _('VlanID'), _('project'), - _("uuid"), - _('priority'), - _('bridge')) + _("uuid")) for network in db.network_get_all(context.get_admin_context()): print _fmt % (network.id, network.cidr, @@ -794,9 +792,22 @@ class NetworkCommands(object): network.dns2, network.vlan, network.project_id, - network.uuid, + network.uuid) + + def quantum_list(self): + """List all created networks with Quantum-relevant fields""" + _fmt = "%-32s\t%-10s\t%-10s\t%s , %s" + print _fmt % ( _('bridge / quantum-id'), + _('project'), + _('priority'), + _('cidr_v4'), + _('cidr_v6')) + for network in db.network_get_all(context.get_admin_context()): + print _fmt % (network.bridge, + network.project_id, network.priority, - network.bridge) + network.cidr, + network.cidr_v6) @args('--network', dest="fixed_range", metavar='', help='Network to delete') diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index a0c7dc6d8..b57294c55 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -63,7 +63,7 @@ class Client(object): """A base client class - derived from Glance.BaseClient""" - action_prefix = '/v0.1/tenants/{tenant_id}' + action_prefix = '/v1.0/tenants/{tenant_id}' """Action query strings""" networks_path = "/networks" diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index 3aa017bcd..d6f749bf2 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -46,9 +46,9 @@ class QuantumClientConnection: logger=LOG) def create_network(self, tenant_id, network_name): - data = {'network': {'net-name': network_name}} + data = {'network': {'name': network_name}} resdict = self.client.create_network(data, tenant=tenant_id) - return resdict["networks"]["network"]["id"] + return resdict["network"]["id"] def delete_network(self, tenant_id, net_id): self.client.delete_network(net_id, tenant=tenant_id) -- cgit From e8d02ac1b5e9a45cc19992d232d4148f9db720ca Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 25 Aug 2011 23:02:46 -0700 Subject: rearrange imports --- bin/nova-manage | 2 +- .../migrate_repo/versions/041_add_network_priority.py | 5 ++--- nova/network/quantum/fake.py | 5 +++-- nova/network/quantum/manager.py | 2 +- nova/network/quantum/melange_connection.py | 1 + nova/network/quantum/melange_ipam_lib.py | 1 + nova/network/quantum/nova_ipam_lib.py | 4 ++-- nova/network/quantum/quantum_connection.py | 10 +++++----- nova/tests/test_quantum.py | 2 +- 9 files changed, 17 insertions(+), 15 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 4628e93b2..3dac2963c 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -1,4 +1,4 @@ -#!/usr/bin/env python +!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the diff --git a/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py index e619b1fcd..e69380199 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py @@ -1,4 +1,4 @@ -# Copyright 2011 OpenStack LLC. +# Copyright 2011 Nicira, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -13,14 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. -import datetime - from sqlalchemy import * from migrate import * from nova import log as logging from nova import utils + meta = MetaData() # Add priority column to networks table diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index a923bbf1a..3f6996e63 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -15,12 +15,13 @@ # License for the specific language governing permissions and limitations # under the License. +import math +from netaddr import IPNetwork + from nova import exception from nova import ipv6 from nova import log as logging from nova import utils -import math -from netaddr import IPNetwork LOG = logging.getLogger("network.quantum.fake") diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 2c2fd4dd7..e49d0cfdc 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -20,10 +20,10 @@ from nova import exception from nova import flags from nova import log as logging from nova import manager -from nova import utils from nova.network import manager from nova.network.quantum import quantum_connection from nova.network.quantum import fake +from nova import utils LOG = logging.getLogger("quantum_manager") diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index b3955138d..2d884fa60 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -19,6 +19,7 @@ import httplib import socket import urllib import json + from nova import flags diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 526be6327..5b58520c2 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -21,6 +21,7 @@ from nova import flags from nova import log as logging from nova.network.quantum import melange_connection + LOG = logging.getLogger("quantum_melange_ipam") FLAGS = flags.FLAGS diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index a9a6dcb29..b72edeeff 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -17,15 +17,15 @@ import math -#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.network import manager from nova.network.quantum import melange_connection as melange +from nova import utils + LOG = logging.getLogger("quantum_nova_ipam_lib") diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index d6f749bf2..7a96d23fd 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -17,9 +17,9 @@ from nova import flags from nova import log as logging +from nova.network.quantum import client as quantum_client from nova import utils -from nova.network.quantum.client import Client LOG = logging.getLogger("nova.network.quantum") FLAGS = flags.FLAGS @@ -40,10 +40,10 @@ flags.DEFINE_string('quantum_default_tenant_id', class QuantumClientConnection: def __init__(self): - self.client = Client(FLAGS.quantum_connection_host, - FLAGS.quantum_connection_port, - format="json", - logger=LOG) + self.client = quantum_client.Client(FLAGS.quantum_connection_host, + FLAGS.quantum_connection_port, + format="json", + logger=LOG) def create_network(self, tenant_id, network_name): data = {'network': {'name': network_name}} diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 80cab950e..8ef22ac7e 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -19,8 +19,8 @@ from nova import context from nova import db from nova import exception from nova import log as logging -from nova import test from nova.network.quantum import manager as quantum_manager +from nova import test LOG = logging.getLogger('nova.tests.quantum_network') -- cgit From b66840327ad183619995bc9e88a0d4ea01ab0b59 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 25 Aug 2011 23:08:15 -0700 Subject: replace accidental deletion in nova-mange --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 3dac2963c..4628e93b2 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -1,4 +1,4 @@ -!/usr/bin/env python +#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the -- cgit From 87a5fefe5b0e3379ef93fede0750ddd76dd3c20d Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 25 Aug 2011 23:18:38 -0700 Subject: Add brad to Authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 864679929..112791cfc 100644 --- a/Authors +++ b/Authors @@ -11,6 +11,7 @@ Antony Messerli Armando Migliaccio Arvind Somya Bilal Akhtar +Brad Hall Brian Lamar Brian Schott Brian Waldon -- cgit From a7e7be67a79fab4348f68c5738656f1ac401c39a Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 12:24:35 -0500 Subject: misplaced comma... --- nova/network/manager.py | 2 +- nova/tests/fake_network_info.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index 9a4b09ad8..74e7bbf26 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -508,7 +508,7 @@ class NetworkManager(manager.SchedulerDependentManager): if network['cidr_v6']: info['ip6s'] = [ip6_dict()] - network_dict['cidr_v6'] = network['cidr_v6'], + network_dict['cidr_v6'] = network['cidr_v6'] # TODO(tr3buchet): handle ip6 routes here as well if network['gateway_v6']: info['gateway6'] = network['gateway_v6'] diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 47f9abd05..81edd998b 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -60,6 +60,8 @@ def fake_network(n, ipv6=None): rval['cidr_v6'] = '2001:db8:0:%x::/64' % n rval['gateway_v6'] = '2001:db8:0:%x::1' % n rval['netmask_v6'] = '64' + print 'asdf %s' % rval['cidr_v6'] + print type(rval['cidr_v6']) return rval -- cgit From 79602b6d9d7f3ef9777cc0e5f7a0476e1f71ffa0 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 12:26:09 -0500 Subject: forgot test print statements --- nova/tests/fake_network_info.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 81edd998b..47f9abd05 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -60,8 +60,6 @@ def fake_network(n, ipv6=None): rval['cidr_v6'] = '2001:db8:0:%x::/64' % n rval['gateway_v6'] = '2001:db8:0:%x::1' % n rval['netmask_v6'] = '64' - print 'asdf %s' % rval['cidr_v6'] - print type(rval['cidr_v6']) return rval -- cgit From 2d3c516f5943efac0ab836818a759ba9291910b4 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 12:28:14 -0500 Subject: added memory_mb to instance flavor test model --- nova/tests/fake_network_info.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 47f9abd05..6feb1e8f8 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -80,7 +80,8 @@ def fixed_ips(num_networks, num_ips, num_floating_ips=0): flavor = {'id': 0, - 'rxtx_cap': 3} + 'rxtx_cap': 3, + 'memory_mb': 512} def floating_ips(fixed_ip_id): for i in xrange(154): -- cgit From f684e293b02a168e3e45d915645142229fdc7561 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 12:29:44 -0500 Subject: added vcpus to instance flavor test model --- nova/tests/fake_network_info.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 6feb1e8f8..d89ddbe9f 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -81,7 +81,8 @@ def fixed_ips(num_networks, num_ips, num_floating_ips=0): flavor = {'id': 0, 'rxtx_cap': 3, - 'memory_mb': 512} + 'memory_mb': 512, + 'vcpus': 2} def floating_ips(fixed_ip_id): for i in xrange(154): -- cgit From 9f0015c727986d17a14f905a1779cc31397a071f Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 12:33:08 -0500 Subject: updated instance type fake model --- nova/tests/fake_network_info.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index d89ddbe9f..a8e36c06b 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -80,9 +80,14 @@ def fixed_ips(num_networks, num_ips, num_floating_ips=0): flavor = {'id': 0, - 'rxtx_cap': 3, + 'name': 'fake_flavor', 'memory_mb': 512, - 'vcpus': 2} + 'vcpus': 2, + 'local_gb': 10, + 'flavor_id': 0, + 'swap': 0, + 'rxtx_quota': 0, + 'rxtx_cap': 3} def floating_ips(fixed_ip_id): for i in xrange(154): -- cgit From 49d24725031d7ebfb1a90517bcead02c09a4ebaf Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 12:38:20 -0500 Subject: update libvirt --- nova/virt/libvirt/firewall.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py index c2f4f91e8..0db10c7ce 100644 --- a/nova/virt/libvirt/firewall.py +++ b/nova/virt/libvirt/firewall.py @@ -338,8 +338,8 @@ class NWFilterFirewall(FirewallDriver): 'nova-allow-dhcp-server'] if FLAGS.use_ipv6: - networks = [network for (network, _m) in network_info if - network['gateway_v6']] + networks = [network for (network, info) in network_info if + info['gateway6']] if networks: instance_secgroup_filter_children.\ -- cgit From fe74d65f7a8ccba26a03c72edbadf4b00e9a5294 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 13:26:33 -0500 Subject: update libvirt tests --- nova/tests/test_libvirt.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 06ad7d13a..02b2f0cfe 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -1377,9 +1377,9 @@ class NWFilterTestCase(test.TestCase): _setup_networking(instance_ref['id'], self.test_ip) - def _ensure_all_called(): + def _ensure_all_called(mac): instance_filter = 'nova-instance-%s-%s' % (instance_ref['name'], - 'fake') + mac.translate(None, ':') secgroup_filter = 'nova-secgroup-%s' % self.security_group['id'] for required in [secgroup_filter, 'allow-dhcp-server', 'no-arp-spoofing', 'no-ip-spoofing', @@ -1396,10 +1396,15 @@ class NWFilterTestCase(test.TestCase): instance = db.instance_get(self.context, inst_id) network_info = _fake_network_info(self.stubs, 1) + # since there is one (network_info) there is one vif + # pass this vif's mac to _ensure_all_called() + # to set the instance_filter properly + mac = network_info[0][1]['mac'] + self.fw.setup_basic_filtering(instance, network_info) self.fw.prepare_instance_filter(instance, network_info) self.fw.apply_instance_filter(instance, network_info) - _ensure_all_called() + _ensure_all_called(mac) self.teardown_security_group() db.instance_destroy(context.get_admin_context(), instance_ref['id']) -- cgit From 2fdf2d3fb05f58d1778671830eab111328f624fc Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 13:28:17 -0500 Subject: forgot ) --- nova/tests/test_libvirt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 02b2f0cfe..ded5a99ab 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -1379,7 +1379,7 @@ class NWFilterTestCase(test.TestCase): def _ensure_all_called(mac): instance_filter = 'nova-instance-%s-%s' % (instance_ref['name'], - mac.translate(None, ':') + mac.translate(None, ':')) secgroup_filter = 'nova-secgroup-%s' % self.security_group['id'] for required in [secgroup_filter, 'allow-dhcp-server', 'no-arp-spoofing', 'no-ip-spoofing', -- cgit From 3ae6943354a8c22e65233bca38f38c3521b1cce4 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 13:32:16 -0500 Subject: updated fake values --- nova/tests/fake_network_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index a8e36c06b..079cf539b 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -81,7 +81,7 @@ def fixed_ips(num_networks, num_ips, num_floating_ips=0): flavor = {'id': 0, 'name': 'fake_flavor', - 'memory_mb': 512, + 'memory_mb': 2048, 'vcpus': 2, 'local_gb': 10, 'flavor_id': 0, -- cgit From 698e6052d70bd6da4b6b1d2cfcf096ea576c36a6 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 13:37:25 -0500 Subject: updated fake values --- nova/tests/test_libvirt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index ded5a99ab..6947a70b0 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -1120,7 +1120,7 @@ class IptablesFirewallTestCase(test.TestCase): network_info = _fake_network_info(self.stubs, 1) rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) self.assertEquals(len(rulesv4), 2) - self.assertEquals(len(rulesv6), 3) + self.assertEquals(len(rulesv6), 1) def test_filters_for_instance_without_ip_v6(self): self.flags(use_ipv6=False) -- cgit From 02c501391ad9f6bb55771053418f10301e644c8f Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 13:44:26 -0500 Subject: updated fake values --- nova/tests/test_libvirt.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 6947a70b0..1b09a3302 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -1130,11 +1130,14 @@ class IptablesFirewallTestCase(test.TestCase): self.assertEquals(len(rulesv6), 0) def test_multinic_iptables(self): - ipv4_rules_per_network = 2 - ipv6_rules_per_network = 3 + ipv4_rules_per_addr = 1 + ipv4_addr_per_network = 2 + ipv6_rules_per_addr = 1 + ipv6_addr_per_network = 1 networks_count = 5 instance_ref = self._create_instance_ref() - network_info = _fake_network_info(self.stubs, networks_count) + network_info = _fake_network_info(self.stubs, networks_count, + ipv4_addr_per_network) 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, @@ -1145,9 +1148,9 @@ class IptablesFirewallTestCase(test.TestCase): 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) + ipv4_rules_per_addr * ipv4_addr_per_network * networks_count) self.assertEquals(ipv6_network_rules, - ipv6_rules_per_network * networks_count) + ipv6_rules_per_addr * ipv4_addr_per_network * networks_count) def test_do_refresh_security_group_rules(self): instance_ref = self._create_instance_ref() -- cgit From adf03e7274636cd58d9c04461896a50f43934e15 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 13:47:54 -0500 Subject: updated fake values --- nova/tests/test_libvirt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 1b09a3302..d8bc9575b 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -1150,7 +1150,7 @@ class IptablesFirewallTestCase(test.TestCase): self.assertEquals(ipv4_network_rules, ipv4_rules_per_addr * ipv4_addr_per_network * networks_count) self.assertEquals(ipv6_network_rules, - ipv6_rules_per_addr * ipv4_addr_per_network * networks_count) + ipv6_rules_per_addr * ipv6_addr_per_network * networks_count) def test_do_refresh_security_group_rules(self): instance_ref = self._create_instance_ref() -- cgit From b846d22937ac62549832e16105ed06a21a3e34d0 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 26 Aug 2011 15:36:33 -0400 Subject: Tiny tweaks to the migration script. --- .../versions/043_update_instance_states.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py index 65bdf601d..e58ae5362 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py @@ -33,35 +33,35 @@ c_task_state = Column('task_state', _upgrade_translations = { "stopping": { - "vm_state": vm_states.ACTIVE, + "state_description": vm_states.ACTIVE, "task_state": task_states.STOPPING, }, "stopped": { - "vm_state": vm_states.STOPPED, + "state_description": vm_states.STOPPED, "task_state": None, }, "terminated": { - "vm_state": vm_states.DELETED, + "state_description": vm_states.DELETED, "task_state": None, }, "terminating": { - "vm_state": vm_states.ACTIVE, + "state_description": vm_states.ACTIVE, "task_state": task_states.DELETING, }, "running": { - "vm_state": vm_states.ACTIVE, + "state_description": vm_states.ACTIVE, "task_state": None, }, "scheduling": { - "vm_state": vm_states.BUILDING, + "state_description": vm_states.BUILDING, "task_state": task_states.SCHEDULING, }, "migrating": { - "vm_state": vm_states.MIGRATING, + "state_description": vm_states.MIGRATING, "task_state": None, }, "pending": { - "vm_state": vm_states.BUILDING, + "state_description": vm_states.BUILDING, "task_state": task_states.SCHEDULING, }, } @@ -106,9 +106,6 @@ def upgrade(migrate_engine): instance_table.create_column(c_task_state) - instance_table = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - for old_state, values in _upgrade_translations.iteritems(): instance_table.update().\ values(**values).\ @@ -135,7 +132,7 @@ def downgrade(migrate_engine): instance_table.update().\ where(c_task_state == old_task_state).\ where(c_vm_state == old_vm_state).\ - values(state_description=new_state_desc).\ + values(vm_state=new_state_desc).\ execute() instance_table.drop_column('task_state') -- cgit From 477510214e80be21a40bff6c053090793653f6a1 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 15:09:39 -0500 Subject: couple of pep8s --- nova/tests/fake_network_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py index 079cf539b..2e8cf60ec 100644 --- a/nova/tests/fake_network_info.py +++ b/nova/tests/fake_network_info.py @@ -89,6 +89,7 @@ flavor = {'id': 0, 'rxtx_quota': 0, 'rxtx_cap': 3} + def floating_ips(fixed_ip_id): for i in xrange(154): yield {'id': 0, -- cgit From d0f42b4bf6e60485c7bebe1c60dccce48a0ddcb3 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 16:55:52 -0500 Subject: stubbed some stuff in test_libvirt --- nova/tests/test_libvirt.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index d8bc9575b..58e78daf4 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -1063,10 +1063,17 @@ class IptablesFirewallTestCase(test.TestCase): return '', '' print cmd, kwargs + def get_fixed_ips(*args, **kwargs): + ips = [] + for network, info in network_info: + ips.extend(info['ips']) + return [ip['ip'] for ip in ips] + from nova.network import linux_net linux_net.iptables_manager.execute = fake_iptables_execute network_info = _fake_network_info(self.stubs, 1) + self.stubs.Set(self.db, 'instance_get_fixed_addresses', get_fixed_ips) self.fw.prepare_instance_filter(instance_ref, network_info) self.fw.apply_instance_filter(instance_ref, network_info) -- cgit From 9737731fff7f6d4d19213b453315610bd095a0e6 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 17:00:06 -0500 Subject: updated libvirt test --- nova/tests/test_libvirt.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 58e78daf4..2ff6b3953 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -1073,7 +1073,7 @@ class IptablesFirewallTestCase(test.TestCase): linux_net.iptables_manager.execute = fake_iptables_execute network_info = _fake_network_info(self.stubs, 1) - self.stubs.Set(self.db, 'instance_get_fixed_addresses', get_fixed_ips) + self.stubs.Set(db, 'instance_get_fixed_addresses', get_fixed_ips) self.fw.prepare_instance_filter(instance_ref, network_info) self.fw.apply_instance_filter(instance_ref, network_info) @@ -1111,10 +1111,11 @@ class IptablesFirewallTestCase(test.TestCase): self.assertTrue(len(filter(regex.match, self.out_rules)) > 0, "ICMP Echo Request acceptance rule wasn't added") - regex = re.compile('-A .* -j ACCEPT -p tcp -m multiport ' - '--dports 80:81 -s %s' % (src_ip,)) - self.assertTrue(len(filter(regex.match, self.out_rules)) > 0, - "TCP port 80/81 acceptance rule wasn't added") + for ip in get_fixed_ips(): + regex = re.compile('-A .* -j ACCEPT -p tcp -m multiport ' + '--dports 80:81 -s %s' % ip) + self.assertTrue(len(filter(regex.match, self.out_rules)) > 0, + "TCP port 80/81 acceptance rule wasn't added") regex = re.compile('-A .* -j ACCEPT -p tcp ' '-m multiport --dports 80:81 -s 192.168.10.0/24') -- cgit From 156430b2d795833245c069f83c435a8a240556fd Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 26 Aug 2011 17:04:30 -0500 Subject: removed self.test ip and _setup_networking from libvirt --- nova/tests/test_libvirt.py | 64 +++------------------------------------------- 1 file changed, 3 insertions(+), 61 deletions(-) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 2ff6b3953..fe5470a6f 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -41,57 +41,14 @@ from nova.tests import fake_network_info libvirt = None FLAGS = flags.FLAGS +_fake_network_info = fake_network_info.fake_get_instance_nw_info +_ipv4_like = fake_network_info.ipv4_like + def _concurrency(wait, done, target): wait.wait() done.send() -_fake_network_info = fake_network_info.fake_get_instance_nw_info -_ipv4_like = fake_network_info.ipv4_like -def _create_network_info(count=1, ipv6=None): - if ipv6 is None: - ipv6 = FLAGS.use_ipv6 - fake = 'fake' - fake_ip = '10.11.12.13' - fake_ip_2 = '0.0.0.1' - fake_ip_3 = '0.0.0.1' - fake_vlan = 100 - fake_bridge_interface = 'eth0' - network = {'bridge': fake, - 'cidr': fake_ip, - 'cidr_v6': fake_ip, - 'gateway_v6': fake, - 'vlan': fake_vlan, - 'bridge_interface': fake_bridge_interface} - mapping = {'mac': fake, - 'dhcp_server': '10.0.0.1', - 'gateway': fake, - 'gateway6': fake, - '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)] - - -def _setup_networking(instance_id, ip='1.2.3.4', mac='56:12:12:12:12:12'): - ctxt = context.get_admin_context() - network_ref = db.project_get_networks(ctxt, - 'fake', - associate=True)[0] - vif = {'address': mac, - 'network_id': network_ref['id'], - 'instance_id': instance_id} - vif_ref = db.virtual_interface_create(ctxt, vif) - - fixed_ip = {'address': ip, - 'network_id': network_ref['id'], - 'virtual_interface_id': vif_ref['id']} - db.fixed_ip_create(ctxt, fixed_ip) - db.fixed_ip_update(ctxt, ip, {'allocated': True, - 'instance_id': instance_id}) - class CacheConcurrencyTestCase(test.TestCase): def setUp(self): @@ -164,7 +121,6 @@ class LibvirtConnTestCase(test.TestCase): self.context = context.get_admin_context() self.flags(instances_path='') self.call_libvirt_dependant_setup = False - self.test_ip = '10.11.12.13' test_instance = {'memory_kb': '1024000', 'basepath': '/some/path', @@ -426,7 +382,6 @@ class LibvirtConnTestCase(test.TestCase): user_context = context.RequestContext(self.user_id, self.project_id) instance_ref = db.instance_create(user_context, instance) - _setup_networking(instance_ref['id'], self.test_ip) self.flags(libvirt_type='lxc') conn = connection.LibvirtConnection(True) @@ -458,8 +413,6 @@ class LibvirtConnTestCase(test.TestCase): network_ref = db.project_get_networks(context.get_admin_context(), self.project_id)[0] - _setup_networking(instance_ref['id'], self.test_ip) - type_uri_map = {'qemu': ('qemu:///system', [(lambda t: t.find('.').get('type'), 'qemu'), (lambda t: t.find('./os/type').text, 'hvm'), @@ -925,7 +878,6 @@ class IptablesFirewallTestCase(test.TestCase): """setup_basic_rules in nwfilter calls this.""" pass self.fake_libvirt_connection = FakeLibvirtConnection() - self.test_ip = '10.11.12.13' self.fw = firewall.IptablesFirewallDriver( get_connection=lambda: self.fake_libvirt_connection) @@ -989,10 +941,6 @@ class IptablesFirewallTestCase(test.TestCase): def test_static_filters(self): instance_ref = self._create_instance_ref() src_instance_ref = self._create_instance_ref() - src_ip = '10.11.12.14' - src_mac = '56:12:12:12:12:13' - _setup_networking(instance_ref['id'], self.test_ip, src_mac) - _setup_networking(src_instance_ref['id'], src_ip) admin_ctxt = context.get_admin_context() secgroup = db.security_group_create(admin_ctxt, @@ -1184,7 +1132,6 @@ class IptablesFirewallTestCase(test.TestCase): fakefilter.nwfilterLookupByName instance_ref = self._create_instance_ref() - _setup_networking(instance_ref['id'], self.test_ip) network_info = _fake_network_info(self.stubs, 1) self.fw.setup_basic_filtering(instance_ref, network_info) self.fw.prepare_instance_filter(instance_ref, network_info) @@ -1200,7 +1147,6 @@ class IptablesFirewallTestCase(test.TestCase): def test_provider_firewall_rules(self): # setup basic instance data instance_ref = self._create_instance_ref() - _setup_networking(instance_ref['id'], self.test_ip) # FRAGILE: peeks at how the firewall names chains chain_name = 'inst-%s' % instance_ref['id'] @@ -1270,7 +1216,6 @@ class NWFilterTestCase(test.TestCase): self.fake_libvirt_connection = Mock() - self.test_ip = '10.11.12.13' self.fw = firewall.NWFilterFirewall( lambda: self.fake_libvirt_connection) @@ -1386,8 +1331,6 @@ class NWFilterTestCase(test.TestCase): instance_ref = self._create_instance() inst_id = instance_ref['id'] - _setup_networking(instance_ref['id'], self.test_ip) - def _ensure_all_called(mac): instance_filter = 'nova-instance-%s-%s' % (instance_ref['name'], mac.translate(None, ':')) @@ -1444,7 +1387,6 @@ class NWFilterTestCase(test.TestCase): instance = db.instance_get(self.context, inst_id) - _setup_networking(instance_ref['id'], self.test_ip) network_info = _fake_network_info(self.stubs, 1) self.fw.setup_basic_filtering(instance, network_info) self.fw.prepare_instance_filter(instance, network_info) -- cgit From 8b44cedcc099542e6485a33764cece4c141fd4ab Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 26 Aug 2011 15:40:04 -0700 Subject: start of kombu implementation, keeping the same RPC interfaces --- nova/rpc/__init__.py | 25 +- nova/rpc/amqp.py | 593 ---------------------------------------------- nova/rpc/impl_carrot.py | 607 ++++++++++++++++++++++++++++++++++++++++++++++++ nova/rpc/impl_kombu.py | 426 +++++++++++++++++++++++++++++++++ 4 files changed, 1045 insertions(+), 606 deletions(-) delete mode 100644 nova/rpc/amqp.py create mode 100644 nova/rpc/impl_carrot.py create mode 100644 nova/rpc/impl_kombu.py diff --git a/nova/rpc/__init__.py b/nova/rpc/__init__.py index bdf7f705b..f102cf0fa 100644 --- a/nova/rpc/__init__.py +++ b/nova/rpc/__init__.py @@ -23,10 +23,18 @@ from nova import flags FLAGS = flags.FLAGS flags.DEFINE_string('rpc_backend', - 'nova.rpc.amqp', - "The messaging module to use, defaults to AMQP.") + 'carrot', + "The messaging module to use, defaults to carrot.") -RPCIMPL = import_object(FLAGS.rpc_backend) +impl_table = {'kombu': 'nova.rpc.impl_kombu', + 'amqp': 'nova.rpc.impl_kombu'} + 'carrot': 'nova.rpc.impl_carrot'} + + +# rpc_backend can be a short name like 'kombu', or it can be the full +# module name +RPCIMPL = import_object(impl_table.get(FLAGS.rpc_backend, + FLAGS.rpc_backend)) def create_connection(new=True): @@ -34,16 +42,7 @@ def create_connection(new=True): def create_consumer(conn, topic, proxy, fanout=False): - if fanout: - return RPCIMPL.FanoutAdapterConsumer( - connection=conn, - topic=topic, - proxy=proxy) - else: - return RPCIMPL.TopicAdapterConsumer( - connection=conn, - topic=topic, - proxy=proxy) + return RPCIMPL.create_consumer(conn, topic, proxy, fanout) def create_consumer_set(conn, consumers): diff --git a/nova/rpc/amqp.py b/nova/rpc/amqp.py deleted file mode 100644 index fe429b266..000000000 --- a/nova/rpc/amqp.py +++ /dev/null @@ -1,593 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""AMQP-based RPC. - -Queues have consumers and publishers. - -No fan-out support yet. - -""" - -import json -import sys -import time -import traceback -import types -import uuid - -from carrot import connection as carrot_connection -from carrot import messaging -from eventlet import greenpool -from eventlet import pools -from eventlet import queue -import greenlet - -from nova import context -from nova import exception -from nova import fakerabbit -from nova import flags -from nova import log as logging -from nova import utils -from nova.rpc.common import RemoteError, LOG - - -FLAGS = flags.FLAGS -flags.DEFINE_integer('rpc_thread_pool_size', 1024, - 'Size of RPC thread pool') -flags.DEFINE_integer('rpc_conn_pool_size', 30, - 'Size of RPC connection pool') - - -class Connection(carrot_connection.BrokerConnection): - """Connection instance object.""" - - @classmethod - def instance(cls, new=True): - """Returns the instance.""" - if new or not hasattr(cls, '_instance'): - params = dict(hostname=FLAGS.rabbit_host, - port=FLAGS.rabbit_port, - ssl=FLAGS.rabbit_use_ssl, - userid=FLAGS.rabbit_userid, - password=FLAGS.rabbit_password, - virtual_host=FLAGS.rabbit_virtual_host) - - if FLAGS.fake_rabbit: - params['backend_cls'] = fakerabbit.Backend - - # NOTE(vish): magic is fun! - # pylint: disable=W0142 - if new: - return cls(**params) - else: - cls._instance = cls(**params) - return cls._instance - - @classmethod - def recreate(cls): - """Recreates the connection instance. - - This is necessary to recover from some network errors/disconnects. - - """ - try: - del cls._instance - except AttributeError, e: - # The _instance stuff is for testing purposes. Usually we don't use - # it. So don't freak out if it doesn't exist. - pass - return cls.instance() - - -class Pool(pools.Pool): - """Class that implements a Pool of Connections.""" - - # TODO(comstud): Timeout connections not used in a while - def create(self): - LOG.debug('Creating new connection') - return Connection.instance(new=True) - -# Create a ConnectionPool to use for RPC calls. We'll order the -# pool as a stack (LIFO), so that we can potentially loop through and -# timeout old unused connections at some point -ConnectionPool = Pool( - max_size=FLAGS.rpc_conn_pool_size, - order_as_stack=True) - - -class Consumer(messaging.Consumer): - """Consumer base class. - - Contains methods for connecting the fetch method to async loops. - - """ - - def __init__(self, *args, **kwargs): - for i in xrange(FLAGS.rabbit_max_retries): - if i > 0: - time.sleep(FLAGS.rabbit_retry_interval) - try: - super(Consumer, self).__init__(*args, **kwargs) - self.failed_connection = False - break - except Exception as e: # Catching all because carrot sucks - fl_host = FLAGS.rabbit_host - fl_port = FLAGS.rabbit_port - fl_intv = FLAGS.rabbit_retry_interval - LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is' - ' unreachable: %(e)s. Trying again in %(fl_intv)d' - ' seconds.') % locals()) - self.failed_connection = True - if self.failed_connection: - LOG.error(_('Unable to connect to AMQP server ' - 'after %d tries. Shutting down.'), - FLAGS.rabbit_max_retries) - sys.exit(1) - - def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): - """Wraps the parent fetch with some logic for failed connection.""" - # TODO(vish): the logic for failed connections and logging should be - # refactored into some sort of connection manager object - try: - if self.failed_connection: - # NOTE(vish): connection is defined in the parent class, we can - # recreate it as long as we create the backend too - # pylint: disable=W0201 - self.connection = Connection.recreate() - self.backend = self.connection.create_backend() - self.declare() - return super(Consumer, self).fetch(no_ack, - auto_ack, - enable_callbacks) - if self.failed_connection: - LOG.error(_('Reconnected to queue')) - self.failed_connection = False - # NOTE(vish): This is catching all errors because we really don't - # want exceptions to be logged 10 times a second if some - # persistent failure occurs. - except Exception, e: # pylint: disable=W0703 - if not self.failed_connection: - LOG.exception(_('Failed to fetch message from queue: %s' % e)) - self.failed_connection = True - - def attach_to_eventlet(self): - """Only needed for unit tests!""" - timer = utils.LoopingCall(self.fetch, enable_callbacks=True) - timer.start(0.1) - return timer - - -class AdapterConsumer(Consumer): - """Calls methods on a proxy object based on method and args.""" - - def __init__(self, connection=None, topic='broadcast', proxy=None): - LOG.debug(_('Initing the Adapter Consumer for %s') % topic) - self.proxy = proxy - self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size) - super(AdapterConsumer, self).__init__(connection=connection, - topic=topic) - self.register_callback(self.process_data) - - def process_data(self, message_data, message): - """Consumer callback to call a method on a proxy object. - - Parses the message for validity and fires off a thread to call the - proxy object method. - - Message data should be a dictionary with two keys: - method: string representing the method to call - args: dictionary of arg: value - - Example: {'method': 'echo', 'args': {'value': 42}} - - """ - LOG.debug(_('received %s') % message_data) - # This will be popped off in _unpack_context - msg_id = message_data.get('_msg_id', None) - ctxt = _unpack_context(message_data) - - method = message_data.get('method') - args = message_data.get('args', {}) - message.ack() - if not method: - # NOTE(vish): we may not want to ack here, but that means that bad - # messages stay in the queue indefinitely, so for now - # we just log the message and send an error string - # back to the caller - LOG.warn(_('no method for message: %s') % message_data) - if msg_id: - msg_reply(msg_id, - _('No method for message: %s') % message_data) - return - self.pool.spawn_n(self._process_data, msg_id, ctxt, method, args) - - @exception.wrap_exception() - def _process_data(self, msg_id, ctxt, method, args): - """Thread that maigcally looks for a method on the proxy - object and calls it. - """ - - node_func = getattr(self.proxy, str(method)) - node_args = dict((str(k), v) for k, v in args.iteritems()) - # NOTE(vish): magic is fun! - try: - rval = node_func(context=ctxt, **node_args) - if msg_id: - # Check if the result was a generator - if isinstance(rval, types.GeneratorType): - for x in rval: - msg_reply(msg_id, x, None) - else: - msg_reply(msg_id, rval, None) - - # This final None tells multicall that it is done. - msg_reply(msg_id, None, None) - elif isinstance(rval, types.GeneratorType): - # NOTE(vish): this iterates through the generator - list(rval) - except Exception as e: - logging.exception('Exception during message handling') - if msg_id: - msg_reply(msg_id, None, sys.exc_info()) - return - - -class TopicAdapterConsumer(AdapterConsumer): - """Consumes messages on a specific topic.""" - - exchange_type = 'topic' - - def __init__(self, connection=None, topic='broadcast', proxy=None): - self.queue = topic - self.routing_key = topic - self.exchange = FLAGS.control_exchange - self.durable = FLAGS.rabbit_durable_queues - super(TopicAdapterConsumer, self).__init__(connection=connection, - topic=topic, proxy=proxy) - - -class FanoutAdapterConsumer(AdapterConsumer): - """Consumes messages from a fanout exchange.""" - - exchange_type = 'fanout' - - def __init__(self, connection=None, topic='broadcast', proxy=None): - self.exchange = '%s_fanout' % topic - self.routing_key = topic - unique = uuid.uuid4().hex - self.queue = '%s_fanout_%s' % (topic, unique) - self.durable = False - # Fanout creates unique queue names, so we should auto-remove - # them when done, so they're not left around on restart. - # Also, we're the only one that should be consuming. exclusive - # implies auto_delete, so we'll just set that.. - self.exclusive = True - LOG.info(_('Created "%(exchange)s" fanout exchange ' - 'with "%(key)s" routing key'), - dict(exchange=self.exchange, key=self.routing_key)) - super(FanoutAdapterConsumer, self).__init__(connection=connection, - topic=topic, proxy=proxy) - - -class ConsumerSet(object): - """Groups consumers to listen on together on a single connection.""" - - def __init__(self, connection, consumer_list): - self.consumer_list = set(consumer_list) - self.consumer_set = None - self.enabled = True - self.init(connection) - - def init(self, conn): - if not conn: - conn = Connection.instance(new=True) - if self.consumer_set: - self.consumer_set.close() - self.consumer_set = messaging.ConsumerSet(conn) - for consumer in self.consumer_list: - consumer.connection = conn - # consumer.backend is set for us - self.consumer_set.add_consumer(consumer) - - def reconnect(self): - self.init(None) - - def wait(self, limit=None): - running = True - while running: - it = self.consumer_set.iterconsume(limit=limit) - if not it: - break - while True: - try: - it.next() - except StopIteration: - return - except greenlet.GreenletExit: - running = False - break - except Exception as e: - LOG.exception(_("Exception while processing consumer")) - self.reconnect() - # Break to outer loop - break - - def close(self): - self.consumer_set.close() - - -class Publisher(messaging.Publisher): - """Publisher base class.""" - pass - - -class TopicPublisher(Publisher): - """Publishes messages on a specific topic.""" - - exchange_type = 'topic' - - def __init__(self, connection=None, topic='broadcast'): - self.routing_key = topic - self.exchange = FLAGS.control_exchange - self.durable = FLAGS.rabbit_durable_queues - super(TopicPublisher, self).__init__(connection=connection) - - -class FanoutPublisher(Publisher): - """Publishes messages to a fanout exchange.""" - - exchange_type = 'fanout' - - def __init__(self, topic, connection=None): - self.exchange = '%s_fanout' % topic - self.queue = '%s_fanout' % topic - self.durable = False - self.auto_delete = True - LOG.info(_('Creating "%(exchange)s" fanout exchange'), - dict(exchange=self.exchange)) - super(FanoutPublisher, self).__init__(connection=connection) - - -class DirectConsumer(Consumer): - """Consumes messages directly on a channel specified by msg_id.""" - - exchange_type = 'direct' - - def __init__(self, connection=None, msg_id=None): - self.queue = msg_id - self.routing_key = msg_id - self.exchange = msg_id - self.durable = False - self.auto_delete = True - self.exclusive = True - super(DirectConsumer, self).__init__(connection=connection) - - -class DirectPublisher(Publisher): - """Publishes messages directly on a channel specified by msg_id.""" - - exchange_type = 'direct' - - def __init__(self, connection=None, msg_id=None): - self.routing_key = msg_id - self.exchange = msg_id - self.durable = False - self.auto_delete = True - super(DirectPublisher, self).__init__(connection=connection) - - -def msg_reply(msg_id, reply=None, failure=None): - """Sends a reply or an error on the channel signified by msg_id. - - Failure should be a sys.exc_info() tuple. - - """ - if failure: - message = str(failure[1]) - tb = traceback.format_exception(*failure) - LOG.error(_("Returning exception %s to caller"), message) - LOG.error(tb) - failure = (failure[0].__name__, str(failure[1]), tb) - - with ConnectionPool.item() as conn: - publisher = DirectPublisher(connection=conn, msg_id=msg_id) - try: - publisher.send({'result': reply, 'failure': failure}) - except TypeError: - publisher.send( - {'result': dict((k, repr(v)) - for k, v in reply.__dict__.iteritems()), - 'failure': failure}) - - publisher.close() - - -def _unpack_context(msg): - """Unpack context from msg.""" - context_dict = {} - for key in list(msg.keys()): - # NOTE(vish): Some versions of python don't like unicode keys - # in kwargs. - key = str(key) - if key.startswith('_context_'): - value = msg.pop(key) - context_dict[key[9:]] = value - context_dict['msg_id'] = msg.pop('_msg_id', None) - LOG.debug(_('unpacked context: %s'), context_dict) - return RpcContext.from_dict(context_dict) - - -def _pack_context(msg, context): - """Pack context into msg. - - Values for message keys need to be less than 255 chars, so we pull - context out into a bunch of separate keys. If we want to support - more arguments in rabbit messages, we may want to do the same - for args at some point. - - """ - context_d = dict([('_context_%s' % key, value) - for (key, value) in context.to_dict().iteritems()]) - msg.update(context_d) - - -class RpcContext(context.RequestContext): - def __init__(self, *args, **kwargs): - msg_id = kwargs.pop('msg_id', None) - self.msg_id = msg_id - super(RpcContext, self).__init__(*args, **kwargs) - - def reply(self, *args, **kwargs): - msg_reply(self.msg_id, *args, **kwargs) - - -def multicall(context, topic, msg): - """Make a call that returns multiple times.""" - LOG.debug(_('Making asynchronous call on %s ...'), topic) - msg_id = uuid.uuid4().hex - msg.update({'_msg_id': msg_id}) - LOG.debug(_('MSG_ID is %s') % (msg_id)) - _pack_context(msg, context) - - con_conn = ConnectionPool.get() - consumer = DirectConsumer(connection=con_conn, msg_id=msg_id) - wait_msg = MulticallWaiter(consumer) - consumer.register_callback(wait_msg) - - publisher = TopicPublisher(connection=con_conn, topic=topic) - publisher.send(msg) - publisher.close() - - return wait_msg - - -class MulticallWaiter(object): - def __init__(self, consumer): - self._consumer = consumer - self._results = queue.Queue() - self._closed = False - - def close(self): - self._closed = True - self._consumer.close() - ConnectionPool.put(self._consumer.connection) - - def __call__(self, data, message): - """Acks message and sets result.""" - message.ack() - if data['failure']: - self._results.put(RemoteError(*data['failure'])) - else: - self._results.put(data['result']) - - def __iter__(self): - return self.wait() - - def wait(self): - while True: - rv = None - while rv is None and not self._closed: - try: - rv = self._consumer.fetch(enable_callbacks=True) - except Exception: - self.close() - raise - time.sleep(0.01) - - result = self._results.get() - if isinstance(result, Exception): - self.close() - raise result - if result == None: - self.close() - raise StopIteration - yield result - - -def call(context, topic, msg): - """Sends a message on a topic and wait for a response.""" - rv = multicall(context, topic, msg) - # NOTE(vish): return the last result from the multicall - rv = list(rv) - if not rv: - return - return rv[-1] - - -def cast(context, topic, msg): - """Sends a message on a topic without waiting for a response.""" - LOG.debug(_('Making asynchronous cast on %s...'), topic) - _pack_context(msg, context) - with ConnectionPool.item() as conn: - publisher = TopicPublisher(connection=conn, topic=topic) - publisher.send(msg) - publisher.close() - - -def fanout_cast(context, topic, msg): - """Sends a message on a fanout exchange without waiting for a response.""" - LOG.debug(_('Making asynchronous fanout cast...')) - _pack_context(msg, context) - with ConnectionPool.item() as conn: - publisher = FanoutPublisher(topic, connection=conn) - publisher.send(msg) - publisher.close() - - -def generic_response(message_data, message): - """Logs a result and exits.""" - LOG.debug(_('response %s'), message_data) - message.ack() - sys.exit(0) - - -def send_message(topic, message, wait=True): - """Sends a message for testing.""" - msg_id = uuid.uuid4().hex - message.update({'_msg_id': msg_id}) - LOG.debug(_('topic is %s'), topic) - LOG.debug(_('message %s'), message) - - if wait: - consumer = messaging.Consumer(connection=Connection.instance(), - queue=msg_id, - exchange=msg_id, - auto_delete=True, - exchange_type='direct', - routing_key=msg_id) - consumer.register_callback(generic_response) - - publisher = messaging.Publisher(connection=Connection.instance(), - exchange=FLAGS.control_exchange, - durable=FLAGS.rabbit_durable_queues, - exchange_type='topic', - routing_key=topic) - publisher.send(message) - publisher.close() - - if wait: - consumer.wait() - consumer.close() - - -if __name__ == '__main__': - # You can send messages from the command line using - # topic and a json string representing a dictionary - # for the method - send_message(sys.argv[1], json.loads(sys.argv[2])) diff --git a/nova/rpc/impl_carrot.py b/nova/rpc/impl_carrot.py new file mode 100644 index 000000000..529f98722 --- /dev/null +++ b/nova/rpc/impl_carrot.py @@ -0,0 +1,607 @@ +# 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. + +"""AMQP-based RPC. + +Queues have consumers and publishers. + +No fan-out support yet. + +""" + +import json +import sys +import time +import traceback +import types +import uuid + +from carrot import connection as carrot_connection +from carrot import messaging +from eventlet import greenpool +from eventlet import pools +from eventlet import queue +import greenlet + +from nova import context +from nova import exception +from nova import fakerabbit +from nova import flags +from nova import log as logging +from nova import utils +from nova.rpc.common import RemoteError, LOG + + +FLAGS = flags.FLAGS +flags.DEFINE_integer('rpc_thread_pool_size', 1024, + 'Size of RPC thread pool') +flags.DEFINE_integer('rpc_conn_pool_size', 30, + 'Size of RPC connection pool') + + +class Connection(carrot_connection.BrokerConnection): + """Connection instance object.""" + + @classmethod + def instance(cls, new=True): + """Returns the instance.""" + if new or not hasattr(cls, '_instance'): + params = dict(hostname=FLAGS.rabbit_host, + port=FLAGS.rabbit_port, + ssl=FLAGS.rabbit_use_ssl, + userid=FLAGS.rabbit_userid, + password=FLAGS.rabbit_password, + virtual_host=FLAGS.rabbit_virtual_host) + + if FLAGS.fake_rabbit: + params['backend_cls'] = fakerabbit.Backend + + # NOTE(vish): magic is fun! + # pylint: disable=W0142 + if new: + return cls(**params) + else: + cls._instance = cls(**params) + return cls._instance + + @classmethod + def recreate(cls): + """Recreates the connection instance. + + This is necessary to recover from some network errors/disconnects. + + """ + try: + del cls._instance + except AttributeError, e: + # The _instance stuff is for testing purposes. Usually we don't use + # it. So don't freak out if it doesn't exist. + pass + return cls.instance() + + +class Pool(pools.Pool): + """Class that implements a Pool of Connections.""" + + # TODO(comstud): Timeout connections not used in a while + def create(self): + LOG.debug('Creating new connection') + return Connection.instance(new=True) + +# Create a ConnectionPool to use for RPC calls. We'll order the +# pool as a stack (LIFO), so that we can potentially loop through and +# timeout old unused connections at some point +ConnectionPool = Pool( + max_size=FLAGS.rpc_conn_pool_size, + order_as_stack=True) + + +class Consumer(messaging.Consumer): + """Consumer base class. + + Contains methods for connecting the fetch method to async loops. + + """ + + def __init__(self, *args, **kwargs): + for i in xrange(FLAGS.rabbit_max_retries): + if i > 0: + time.sleep(FLAGS.rabbit_retry_interval) + try: + super(Consumer, self).__init__(*args, **kwargs) + self.failed_connection = False + break + except Exception as e: # Catching all because carrot sucks + fl_host = FLAGS.rabbit_host + fl_port = FLAGS.rabbit_port + fl_intv = FLAGS.rabbit_retry_interval + LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is' + ' unreachable: %(e)s. Trying again in %(fl_intv)d' + ' seconds.') % locals()) + self.failed_connection = True + if self.failed_connection: + LOG.error(_('Unable to connect to AMQP server ' + 'after %d tries. Shutting down.'), + FLAGS.rabbit_max_retries) + sys.exit(1) + + def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): + """Wraps the parent fetch with some logic for failed connection.""" + # TODO(vish): the logic for failed connections and logging should be + # refactored into some sort of connection manager object + try: + if self.failed_connection: + # NOTE(vish): connection is defined in the parent class, we can + # recreate it as long as we create the backend too + # pylint: disable=W0201 + self.connection = Connection.recreate() + self.backend = self.connection.create_backend() + self.declare() + return super(Consumer, self).fetch(no_ack, + auto_ack, + enable_callbacks) + if self.failed_connection: + LOG.error(_('Reconnected to queue')) + self.failed_connection = False + # NOTE(vish): This is catching all errors because we really don't + # want exceptions to be logged 10 times a second if some + # persistent failure occurs. + except Exception, e: # pylint: disable=W0703 + if not self.failed_connection: + LOG.exception(_('Failed to fetch message from queue: %s' % e)) + self.failed_connection = True + + def attach_to_eventlet(self): + """Only needed for unit tests!""" + timer = utils.LoopingCall(self.fetch, enable_callbacks=True) + timer.start(0.1) + return timer + + +class AdapterConsumer(Consumer): + """Calls methods on a proxy object based on method and args.""" + + def __init__(self, connection=None, topic='broadcast', proxy=None): + LOG.debug(_('Initing the Adapter Consumer for %s') % topic) + self.proxy = proxy + self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size) + super(AdapterConsumer, self).__init__(connection=connection, + topic=topic) + self.register_callback(self.process_data) + + def process_data(self, message_data, message): + """Consumer callback to call a method on a proxy object. + + Parses the message for validity and fires off a thread to call the + proxy object method. + + Message data should be a dictionary with two keys: + method: string representing the method to call + args: dictionary of arg: value + + Example: {'method': 'echo', 'args': {'value': 42}} + + """ + LOG.debug(_('received %s') % message_data) + # This will be popped off in _unpack_context + msg_id = message_data.get('_msg_id', None) + ctxt = _unpack_context(message_data) + + method = message_data.get('method') + args = message_data.get('args', {}) + message.ack() + if not method: + # NOTE(vish): we may not want to ack here, but that means that bad + # messages stay in the queue indefinitely, so for now + # we just log the message and send an error string + # back to the caller + LOG.warn(_('no method for message: %s') % message_data) + if msg_id: + msg_reply(msg_id, + _('No method for message: %s') % message_data) + return + self.pool.spawn_n(self._process_data, msg_id, ctxt, method, args) + + @exception.wrap_exception() + def _process_data(self, msg_id, ctxt, method, args): + """Thread that maigcally looks for a method on the proxy + object and calls it. + """ + + node_func = getattr(self.proxy, str(method)) + node_args = dict((str(k), v) for k, v in args.iteritems()) + # NOTE(vish): magic is fun! + try: + rval = node_func(context=ctxt, **node_args) + if msg_id: + # Check if the result was a generator + if isinstance(rval, types.GeneratorType): + for x in rval: + msg_reply(msg_id, x, None) + else: + msg_reply(msg_id, rval, None) + + # This final None tells multicall that it is done. + msg_reply(msg_id, None, None) + elif isinstance(rval, types.GeneratorType): + # NOTE(vish): this iterates through the generator + list(rval) + except Exception as e: + logging.exception('Exception during message handling') + if msg_id: + msg_reply(msg_id, None, sys.exc_info()) + return + + +class TopicAdapterConsumer(AdapterConsumer): + """Consumes messages on a specific topic.""" + + exchange_type = 'topic' + + def __init__(self, connection=None, topic='broadcast', proxy=None): + self.queue = topic + self.routing_key = topic + self.exchange = FLAGS.control_exchange + self.durable = FLAGS.rabbit_durable_queues + super(TopicAdapterConsumer, self).__init__(connection=connection, + topic=topic, proxy=proxy) + + +class FanoutAdapterConsumer(AdapterConsumer): + """Consumes messages from a fanout exchange.""" + + exchange_type = 'fanout' + + def __init__(self, connection=None, topic='broadcast', proxy=None): + self.exchange = '%s_fanout' % topic + self.routing_key = topic + unique = uuid.uuid4().hex + self.queue = '%s_fanout_%s' % (topic, unique) + self.durable = False + # Fanout creates unique queue names, so we should auto-remove + # them when done, so they're not left around on restart. + # Also, we're the only one that should be consuming. exclusive + # implies auto_delete, so we'll just set that.. + self.exclusive = True + LOG.info(_('Created "%(exchange)s" fanout exchange ' + 'with "%(key)s" routing key'), + dict(exchange=self.exchange, key=self.routing_key)) + super(FanoutAdapterConsumer, self).__init__(connection=connection, + topic=topic, proxy=proxy) + + +class ConsumerSet(object): + """Groups consumers to listen on together on a single connection.""" + + def __init__(self, connection, consumer_list): + self.consumer_list = set(consumer_list) + self.consumer_set = None + self.enabled = True + self.init(connection) + + def init(self, conn): + if not conn: + conn = Connection.instance(new=True) + if self.consumer_set: + self.consumer_set.close() + self.consumer_set = messaging.ConsumerSet(conn) + for consumer in self.consumer_list: + consumer.connection = conn + # consumer.backend is set for us + self.consumer_set.add_consumer(consumer) + + def reconnect(self): + self.init(None) + + def wait(self, limit=None): + running = True + while running: + it = self.consumer_set.iterconsume(limit=limit) + if not it: + break + while True: + try: + it.next() + except StopIteration: + return + except greenlet.GreenletExit: + running = False + break + except Exception as e: + LOG.exception(_("Exception while processing consumer")) + self.reconnect() + # Break to outer loop + break + + def close(self): + self.consumer_set.close() + + +class Publisher(messaging.Publisher): + """Publisher base class.""" + pass + + +class TopicPublisher(Publisher): + """Publishes messages on a specific topic.""" + + exchange_type = 'topic' + + def __init__(self, connection=None, topic='broadcast'): + self.routing_key = topic + self.exchange = FLAGS.control_exchange + self.durable = FLAGS.rabbit_durable_queues + super(TopicPublisher, self).__init__(connection=connection) + + +class FanoutPublisher(Publisher): + """Publishes messages to a fanout exchange.""" + + exchange_type = 'fanout' + + def __init__(self, topic, connection=None): + self.exchange = '%s_fanout' % topic + self.queue = '%s_fanout' % topic + self.durable = False + self.auto_delete = True + LOG.info(_('Creating "%(exchange)s" fanout exchange'), + dict(exchange=self.exchange)) + super(FanoutPublisher, self).__init__(connection=connection) + + +class DirectConsumer(Consumer): + """Consumes messages directly on a channel specified by msg_id.""" + + exchange_type = 'direct' + + def __init__(self, connection=None, msg_id=None): + self.queue = msg_id + self.routing_key = msg_id + self.exchange = msg_id + self.durable = False + self.auto_delete = True + self.exclusive = True + super(DirectConsumer, self).__init__(connection=connection) + + +class DirectPublisher(Publisher): + """Publishes messages directly on a channel specified by msg_id.""" + + exchange_type = 'direct' + + def __init__(self, connection=None, msg_id=None): + self.routing_key = msg_id + self.exchange = msg_id + self.durable = False + self.auto_delete = True + super(DirectPublisher, self).__init__(connection=connection) + + +def msg_reply(msg_id, reply=None, failure=None): + """Sends a reply or an error on the channel signified by msg_id. + + Failure should be a sys.exc_info() tuple. + + """ + if failure: + message = str(failure[1]) + tb = traceback.format_exception(*failure) + LOG.error(_("Returning exception %s to caller"), message) + LOG.error(tb) + failure = (failure[0].__name__, str(failure[1]), tb) + + with ConnectionPool.item() as conn: + publisher = DirectPublisher(connection=conn, msg_id=msg_id) + try: + publisher.send({'result': reply, 'failure': failure}) + except TypeError: + publisher.send( + {'result': dict((k, repr(v)) + for k, v in reply.__dict__.iteritems()), + 'failure': failure}) + + publisher.close() + + +def _unpack_context(msg): + """Unpack context from msg.""" + context_dict = {} + for key in list(msg.keys()): + # NOTE(vish): Some versions of python don't like unicode keys + # in kwargs. + key = str(key) + if key.startswith('_context_'): + value = msg.pop(key) + context_dict[key[9:]] = value + context_dict['msg_id'] = msg.pop('_msg_id', None) + LOG.debug(_('unpacked context: %s'), context_dict) + return RpcContext.from_dict(context_dict) + + +def _pack_context(msg, context): + """Pack context into msg. + + Values for message keys need to be less than 255 chars, so we pull + context out into a bunch of separate keys. If we want to support + more arguments in rabbit messages, we may want to do the same + for args at some point. + + """ + context_d = dict([('_context_%s' % key, value) + for (key, value) in context.to_dict().iteritems()]) + msg.update(context_d) + + +class RpcContext(context.RequestContext): + def __init__(self, *args, **kwargs): + msg_id = kwargs.pop('msg_id', None) + self.msg_id = msg_id + super(RpcContext, self).__init__(*args, **kwargs) + + def reply(self, *args, **kwargs): + msg_reply(self.msg_id, *args, **kwargs) + + +def multicall(context, topic, msg): + """Make a call that returns multiple times.""" + LOG.debug(_('Making asynchronous call on %s ...'), topic) + msg_id = uuid.uuid4().hex + msg.update({'_msg_id': msg_id}) + LOG.debug(_('MSG_ID is %s') % (msg_id)) + _pack_context(msg, context) + + con_conn = ConnectionPool.get() + consumer = DirectConsumer(connection=con_conn, msg_id=msg_id) + wait_msg = MulticallWaiter(consumer) + consumer.register_callback(wait_msg) + + publisher = TopicPublisher(connection=con_conn, topic=topic) + publisher.send(msg) + publisher.close() + + return wait_msg + + +class MulticallWaiter(object): + def __init__(self, consumer): + self._consumer = consumer + self._results = queue.Queue() + self._closed = False + + def close(self): + self._closed = True + self._consumer.close() + ConnectionPool.put(self._consumer.connection) + + def __call__(self, data, message): + """Acks message and sets result.""" + message.ack() + if data['failure']: + self._results.put(RemoteError(*data['failure'])) + else: + self._results.put(data['result']) + + def __iter__(self): + return self.wait() + + def wait(self): + while True: + rv = None + while rv is None and not self._closed: + try: + rv = self._consumer.fetch(enable_callbacks=True) + except Exception: + self.close() + raise + time.sleep(0.01) + + result = self._results.get() + if isinstance(result, Exception): + self.close() + raise result + if result == None: + self.close() + raise StopIteration + yield result + + +def create_consumer(conn, topic, proxy, fanout=False): + """Create a consumer that calls methods in the proxy""" + if fanout: + return FanoutAdapterConsumer( + connection=conn, + topic=topic, + proxy=proxy) + else: + return TopicAdapterConsumer( + connection=conn, + topic=topic, + proxy=proxy) + + +def call(context, topic, msg): + """Sends a message on a topic and wait for a response.""" + rv = multicall(context, topic, msg) + # NOTE(vish): return the last result from the multicall + rv = list(rv) + if not rv: + return + return rv[-1] + + +def cast(context, topic, msg): + """Sends a message on a topic without waiting for a response.""" + LOG.debug(_('Making asynchronous cast on %s...'), topic) + _pack_context(msg, context) + with ConnectionPool.item() as conn: + publisher = TopicPublisher(connection=conn, topic=topic) + publisher.send(msg) + publisher.close() + + +def fanout_cast(context, topic, msg): + """Sends a message on a fanout exchange without waiting for a response.""" + LOG.debug(_('Making asynchronous fanout cast...')) + _pack_context(msg, context) + with ConnectionPool.item() as conn: + publisher = FanoutPublisher(topic, connection=conn) + publisher.send(msg) + publisher.close() + + +def generic_response(message_data, message): + """Logs a result and exits.""" + LOG.debug(_('response %s'), message_data) + message.ack() + sys.exit(0) + + +def send_message(topic, message, wait=True): + """Sends a message for testing.""" + msg_id = uuid.uuid4().hex + message.update({'_msg_id': msg_id}) + LOG.debug(_('topic is %s'), topic) + LOG.debug(_('message %s'), message) + + if wait: + consumer = messaging.Consumer(connection=Connection.instance(), + queue=msg_id, + exchange=msg_id, + auto_delete=True, + exchange_type='direct', + routing_key=msg_id) + consumer.register_callback(generic_response) + + publisher = messaging.Publisher(connection=Connection.instance(), + exchange=FLAGS.control_exchange, + durable=FLAGS.rabbit_durable_queues, + exchange_type='topic', + routing_key=topic) + publisher.send(message) + publisher.close() + + if wait: + consumer.wait() + consumer.close() + + +if __name__ == '__main__': + # You can send messages from the command line using + # topic and a json string representing a dictionary + # for the method + send_message(sys.argv[1], json.loads(sys.argv[2])) diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py new file mode 100644 index 000000000..e609227c9 --- /dev/null +++ b/nova/rpc/impl_kombu.py @@ -0,0 +1,426 @@ +# 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 nova import flags +from nova import log as logging + +import kombu +import kombu.entity +import kombu.messaging +import kombu.connection +import itertools +import sys +import time +import uuid + + +FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.rpc') + + +class QueueBase(object): + """Queue base class.""" + + def __init__(self, channel, callback, tag, **kwargs): + """Init the queue. + + 'channel' is the amqp channel to use + 'callback' is the callback to call when messages are received + 'tag' is a unique ID for the consumer on the channel + + queue name, exchange name, and other kombu options are + passed in here as a dictionary. + """ + self.callback = callback + self.tag = str(tag) + self.kwargs = kwargs + self.queue = None + self.reconnect(channel) + + def reconnect(self, channel): + """Re-create the queue after a rabbit reconnect""" + self.channel = channel + self.kwargs['channel'] = channel + self.queue = kombu.entity.Queue(**self.kwargs) + self.queue.declare() + + def consume(self, *args, **kwargs): + """Consume from this queue. + If a callback is specified in kwargs, use that. Otherwise, + use the callback passed during __init__() + + The callback will be called if a message was read off of the + queue. + + If kwargs['nowait'] is True, then this call will block until + a message is read. + + Messages will automatically be acked if the callback doesn't + raise an exception + """ + + options = {'consumer_tag': self.tag} + options['nowait'] = kwargs.get('nowait', False) + callback = kwargs.get('callback', self.callback) + if not callback: + raise ValueError("No callback defined") + + def _callback(raw_message): + message = self.channel.message_to_python(raw_message) + callback(message.payload) + message.ack() + + self.queue.consume(*args, callback=_callback, **options) + + def cancel(self): + """Cancel the consuming from the queue, if it has started""" + try: + self.queue.cancel(self.tag) + except KeyError, e: + # NOTE(comstud): Kludge to get around a amqplib bug + if str(e) != "u'%s'" % self.tag: + raise + self.queue = None + + +class DirectQueue(QueueBase): + """Queue/consumer class for 'direct'""" + + def __init__(self, channel, msg_id, callback, tag, **kwargs): + """Init a 'direct' queue. + + 'channel' is the amqp channel to use + 'msg_id' is the msg_id to listen on + 'callback' is the callback to call when messages are received + 'tag' is a unique ID for the consumer on the channel + + Other kombu options may be passed + """ + # Default options + options = {'durable': False, + 'auto_delete': True, + 'exclusive': True} + options.update(kwargs) + exchange = kombu.entity.Exchange( + name=msg_id, + type='direct', + durable=options['durable'], + auto_delete=options['auto_delete']) + super(DirectQueue, self).__init__( + channel, + callback, + tag, + name=msg_id, + exchange=exchange, + routing_key=msg_id, + **options) + + +class TopicQueue(QueueBase): + """Queue/consumer class for 'topic'""" + + def __init__(self, channel, topic, callback, tag, **kwargs): + """Init a 'topic' queue. + + 'channel' is the amqp channel to use + 'topic' is the topic to listen on + 'callback' is the callback to call when messages are received + 'tag' is a unique ID for the consumer on the channel + + Other kombu options may be passed + """ + # Default options + options = {'durable': FLAGS.rabbit_durable_queues, + 'auto_delete': False, + 'exclusive': False} + options.update(kwargs) + exchange = kombu.entity.Exchange( + name=FLAGS.control_exchange, + type='topic', + durable=options['durable'], + auto_delete=options['auto_delete']) + super(TopicQueue, self).__init__( + channel, + callback, + tag, + name=topic, + exchange=exchange, + routing_key=topic, + **options) + + +class FanoutQueue(QueueBase): + """Queue/consumer class for 'fanout'""" + + def __init__(self, channel, topic, callback, tag, **kwargs): + """Init a 'fanout' queue. + + 'channel' is the amqp channel to use + 'topic' is the topic to listen on + 'callback' is the callback to call when messages are received + 'tag' is a unique ID for the consumer on the channel + + Other kombu options may be passed + """ + unique = uuid.uuid4().hex + exchange_name = '%s_fanout' % topic + queue_name = '%s_fanout_%s' % (topic, unique) + + # Default options + options = {'durable': False, + 'auto_delete': True, + 'exclusive': True} + options.update(kwargs) + exchange = kombu.entity.Exchange( + name=exchange_name, + type='fanout', + durable=options['durable'], + auto_delete=options['auto_delete']) + super(FanoutQueue, self).__init__( + channel, + callback, + tag, + name=queue_name, + exchange=exchange, + routing_key=topic, + **options) + + +class Publisher(object): + """Base Publisher class""" + + def __init__(self, channel, exchange_name, routing_key, **kwargs): + """Init the Publisher class with the exchange_name, routing_key, + and other options + """ + self.exchange_name = exchange_name + self.routing_key = routing_key + self.kwargs = kwargs + self.reconnect(channel) + + def reconnect(self, channel): + """Re-establish the Producer after a rabbit reconnection""" + self.exchange = kombu.entity.Exchange(name=self.exchange_name, + **self.kwargs) + self.producer = kombu.messaging.Producer(exchange=self.exchange, + channel=channel, routing_key=self.routing_key) + + def send(self, msg): + """Send a message""" + self.producer.publish(msg) + + +class DirectPublisher(Publisher): + """Publisher class for 'direct'""" + def __init__(self, channel, msg_id, **kwargs): + """init a 'direct' publisher. + + Kombu options may be passed as keyword args to override defaults + """ + + options = {'durable': False, + 'auto_delete': True, + 'exclusive': True} + options.update(kwargs) + super(DirectPublisher, self).__init__(channel, + msg_id, + msg_id, + type='direct', + **options) + + +class TopicPublisher(Publisher): + """Publisher class for 'topic'""" + def __init__(self, channel, topic, **kwargs): + """init a 'topic' publisher. + + Kombu options may be passed as keyword args to override defaults + """ + options = {'durable': FLAGS.rabbit_durable_queues, + 'auto_delete': False, + 'exclusive': False} + options.update(kwargs) + super(TopicPublisher, self).__init__(channel, + FLAGS.control_exchange, + topic, + type='topic', + **options) + + +class FanoutPublisher(Publisher): + """Publisher class for 'fanout'""" + def __init__(self, channel, topic, **kwargs): + """init a 'fanout' publisher. + + Kombu options may be passed as keyword args to override defaults + """ + options = {'durable': False, + 'auto_delete': True, + 'exclusive': True} + options.update(kwargs) + super(FanoutPublisher, self).__init__(channel, + '%s_fanout' % topic, + None, + type='fanout', + **options) + + +class Connection(object): + """Connection instance object.""" + + def __init__(self): + self.queues = [] + self.max_retries = FLAGS.rabbit_max_retries + self.interval_start = FLAGS.rabbit_retry_interval + self.interval_stepping = 0 + self.interval_max = FLAGS.rabbit_retry_interval + + self.params = dict(hostname=FLAGS.rabbit_host, + port=FLAGS.rabbit_port, + userid=FLAGS.rabbit_userid, + password=FLAGS.rabbit_password, + virtual_host=FLAGS.rabbit_virtual_host) + if FLAGS.fake_rabbit: + self.params['transport'] = 'memory' + self.connection = None + self.reconnect() + + def reconnect(self): + """Handles reconnecting and re-estblishing queues""" + if self.connection: + try: + self.connection.close() + except self.connection.connection_errors: + pass + time.sleep(1) + self.connection = kombu.connection.Connection(**self.params) + self.queue_num = itertools.count(1) + + try: + self.connection.ensure_connection(errback=self.connect_error, + max_retries=self.max_retries, + interval_start=self.interval_start, + interval_step=self.interval_stepping, + interval_max=self.interval_max) + except self.connection.connection_errors, e: + err_str = str(e) + max_retries = FLAGS.rabbit_max_retries + LOG.error(_('Unable to connect to AMQP server ' + 'after %(max_retries)d tries: %(err_str)s') % locals()) + # NOTE(comstud): Original carrot code exits after so many + # attempts, but I wonder if we should re-try indefinitely + sys.exit(1) + LOG.info(_('Connected to AMQP server on %(hostname)s:%(port)d' % + self.params)) + self.channel = self.connection.channel() + for consumer in self.queues: + consumer.reconnect(self.channel) + if self.queues: + LOG.debug(_("Re-established AMQP queues")) + + def get_channel(self): + """Convenience call for bin/clear_rabbit_queues""" + return self.channel + + def connect_error(self, exc, interval): + """Callback when there are connection re-tries by kombu""" + info = self.params.copy() + info['intv'] = interval + info['e'] = exc + LOG.error(_('AMQP server on %(hostname)s:%(port)d is' + ' unreachable: %(e)s. Trying again in %(intv)d' + ' seconds.') % info) + + def close(self): + """Close/release this connection""" + self.connection.release() + self.connection = None + + def reset(self): + """Reset a connection so it can be used again""" + self.channel.close() + self.channel = self.connection.channel() + self.queues = [] + + def create_queue(self, queue_cls, topic, callback): + """Create a queue using the class that was passed in and + add it to our list of queues used for consuming + """ + self.queues.append(queue_cls(self.channel, topic, callback, + self.queue_num.next())) + + def consume(self, limit=None): + """Consume from all queues""" + while True: + try: + queues_head = self.queues[:-1] + queues_tail = self.queues[-1] + for queue in queues_head: + queue.consume(nowait=True) + queues_tail.consume(nowait=False) + + for iteration in itertools.count(0): + if limit and iteration >= limit: + raise StopIteration + yield self.connection.drain_events() + except self.connection.connection_errors, e: + LOG.exception(_('Failed to consume message from queue: ' + '%s' % str(e))) + self.reconnect() + + def publisher_send(self, cls, topic, msg): + """Send to a publisher based on the publisher class""" + while True: + publisher = None + try: + publisher = cls(self.channel, topic) + publisher.send(msg) + return + except self.connection.connection_errors, e: + LOG.exception(_('Failed to publish message %s' % str(e))) + try: + self.reconnect() + if publisher: + publisher.reconnect(self.channel) + except self.connection.connection_errors, e: + pass + + def direct_consumer(self, topic, callback): + """Create a 'direct' queue. + In nova's use, this is generally a msg_id queue used for + responses for call/multicall + """ + self.create_queue(DirectQueue, topic, callback) + + def topic_consumer(self, topic, callback=None): + """Create a 'topic' queue.""" + self.create_queue(TopicQueue, topic, callback) + + def fanout_consumer(self, topic, callback): + """Create a 'fanout' queue""" + self.create_queue(FanoutQueue, topic, callback) + + def direct_send(self, msg_id, msg): + """Send a 'direct' message""" + self.publisher_send(DirectPublisher, msg_id, msg) + + def topic_send(self, topic, msg): + """Send a 'topic' message""" + self.publisher_send(TopicPublisher, topic, msg) + + def fanout_send(self, topic, msg): + """Send a 'fanout' message""" + self.publisher_send(FanoutPublisher, topic, msg) -- cgit From 3a91fa89ea23d22cd34336aa9281a439579d4ce0 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Fri, 26 Aug 2011 15:48:53 -0700 Subject: Minor changes based on recent quantum changes --- nova/network/quantum/client.py | 3 ++- nova/network/quantum/manager.py | 3 ++- nova/network/quantum/quantum_connection.py | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index b57294c55..613369c7d 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -169,7 +169,8 @@ class Client(object): httplib.CREATED, httplib.ACCEPTED, httplib.NO_CONTENT): - return self.deserialize(data, status_code) + if data is not None and len(data): + return self.deserialize(data, status_code) else: raise Exception("Server returned error: %s" % res.read()) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index e49d0cfdc..79bb65939 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -119,7 +119,8 @@ class QuantumManager(manager.FlatManager): def get_instance_nw_info(self, context, instance_id, instance_type_id, host): network_info = [] - project_id = context.project_id + instance = db.instance_get(context, instance_id) + project_id = instance.project_id admin_context = context.elevated() vifs = db.virtual_interface_get_by_instance(admin_context, diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index 7a96d23fd..c0eaad1fd 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -67,9 +67,9 @@ class QuantumClientConnection: (interface_id, net_id, tenant_id)) port_data = {'port': {'port-state': 'ACTIVE'}} resdict = self.client.create_port(net_id, port_data, tenant=tenant_id) - port_id = resdict["ports"]["port"]["id"] + port_id = resdict["port"]["id"] - attach_data = {'port': {'attachment-id': interface_id}} + attach_data = {'attachment': {'id': interface_id}} self.client.attach_resource(net_id, port_id, attach_data, tenant=tenant_id) @@ -92,6 +92,6 @@ class QuantumClientConnection: port_id = p["id"] port_get_resdict = self.client.show_port_attachment(net_id, port_id, tenant=tenant_id) - if attachment_id == port_get_resdict["attachment"]: + if attachment_id == port_get_resdict["attachment"]["id"]: return (net_id, port_id) return (None, None) -- cgit From 9cb46c48682657039173447f9689e15ed3ce15af Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 26 Aug 2011 15:59:15 -0700 Subject: more work done to restore original rpc interfaces. --- nova/rpc/FIXME | 2 + nova/rpc/__init__.py | 3 +- nova/rpc/impl_kombu.py | 307 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 306 insertions(+), 6 deletions(-) create mode 100644 nova/rpc/FIXME diff --git a/nova/rpc/FIXME b/nova/rpc/FIXME new file mode 100644 index 000000000..704081802 --- /dev/null +++ b/nova/rpc/FIXME @@ -0,0 +1,2 @@ +Move some code duplication between carrot/kombu into common.py +The other FIXMEs in __init__.py and impl_kombu.py diff --git a/nova/rpc/__init__.py b/nova/rpc/__init__.py index f102cf0fa..9371c2ab3 100644 --- a/nova/rpc/__init__.py +++ b/nova/rpc/__init__.py @@ -27,7 +27,7 @@ flags.DEFINE_string('rpc_backend', "The messaging module to use, defaults to carrot.") impl_table = {'kombu': 'nova.rpc.impl_kombu', - 'amqp': 'nova.rpc.impl_kombu'} + 'amqp': 'nova.rpc.impl_kombu', 'carrot': 'nova.rpc.impl_carrot'} @@ -46,6 +46,7 @@ def create_consumer(conn, topic, proxy, fanout=False): def create_consumer_set(conn, consumers): + # FIXME(comstud): replace however necessary return RPCIMPL.ConsumerSet(connection=conn, consumer_list=consumers) diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index e609227c9..a222bb885 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -30,6 +30,11 @@ import uuid FLAGS = flags.FLAGS LOG = logging.getLogger('nova.rpc') +flags.DEFINE_integer('rpc_conn_pool_size', 30, + 'Size of RPC connection pool') +flags.DEFINE_integer('rpc_thread_pool_size', 1024, + 'Size of RPC thread pool') + class QueueBase(object): """Queue base class.""" @@ -298,6 +303,16 @@ class Connection(object): self.connection = None self.reconnect() + @classmethod + def instance(cls, new=True): + """Returns the instance.""" + if new or not hasattr(cls, '_instance'): + if new: + return cls() + else: + cls._instance = cls() + return cls._instance + def reconnect(self): """Handles reconnecting and re-estblishing queues""" if self.connection: @@ -359,8 +374,10 @@ class Connection(object): """Create a queue using the class that was passed in and add it to our list of queues used for consuming """ - self.queues.append(queue_cls(self.channel, topic, callback, - self.queue_num.next())) + queue = queue_cls(self.channel, topic, callback, + self.queue_num.next()) + self.queues.append(queue) + return queue def consume(self, limit=None): """Consume from all queues""" @@ -403,15 +420,15 @@ class Connection(object): In nova's use, this is generally a msg_id queue used for responses for call/multicall """ - self.create_queue(DirectQueue, topic, callback) + return self.create_queue(DirectQueue, topic, callback) def topic_consumer(self, topic, callback=None): """Create a 'topic' queue.""" - self.create_queue(TopicQueue, topic, callback) + return self.create_queue(TopicQueue, topic, callback) def fanout_consumer(self, topic, callback): """Create a 'fanout' queue""" - self.create_queue(FanoutQueue, topic, callback) + return self.create_queue(FanoutQueue, topic, callback) def direct_send(self, msg_id, msg): """Send a 'direct' message""" @@ -424,3 +441,283 @@ class Connection(object): def fanout_send(self, topic, msg): """Send a 'fanout' message""" self.publisher_send(FanoutPublisher, topic, msg) + + +class Pool(pools.Pool): + """Class that implements a Pool of Connections.""" + + # TODO(comstud): Timeout connections not used in a while + def create(self): + LOG.debug('Creating new connection') + return RPCIMPL.Connection() + +# Create a ConnectionPool to use for RPC calls. We'll order the +# pool as a stack (LIFO), so that we can potentially loop through and +# timeout old unused connections at some point +ConnectionPool = Pool( + max_size=FLAGS.rpc_conn_pool_size, + order_as_stack=True) + + +class ConnectionContext(object): + def __init__(self, pooled=True): + self.connection = None + if pooled: + self.connection = ConnectionPool.get() + else: + self.connection = RPCIMPL.Connection() + self.pooled = pooled + + def __enter__(self): + return self + + def _done(self): + if self.connection: + if self.pooled: + # Reset the connection so it's ready for the next caller + # to grab from the pool + self.connection.reset() + ConnectionPool.put(self.connection) + else: + try: + self.connection.close() + except Exception: + # There's apparently a bug in kombu 'memory' transport + # which causes an assert failure. + # But, we probably want to ignore all exceptions when + # trying to close a connection, anyway... + pass + self.connection = None + + def __exit__(self, t, v, tb): + """end if 'with' statement. We're done here.""" + self._done() + + def __del__(self): + """Put Connection back into the pool if this ConnectionContext + is being deleted + """ + self._done() + + def close(self): + self._done() + + def __getattr__(self, key): + if self.connection: + return getattr(self.connection, key) + else: + raise exception.InvalidRPCConnectionReuse() + + +class ProxyCallback(object): + """Calls methods on a proxy object based on method and args.""" + + def __init__(self, proxy): + self.proxy = proxy + self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size) + + def __call__(self, message_data): + """Consumer callback to call a method on a proxy object. + + Parses the message for validity and fires off a thread to call the + proxy object method. + + Message data should be a dictionary with two keys: + method: string representing the method to call + args: dictionary of arg: value + + Example: {'method': 'echo', 'args': {'value': 42}} + + """ + LOG.debug(_('received %s') % message_data) + ctxt = _unpack_context(message_data) + method = message_data.get('method') + args = message_data.get('args', {}) + if not method: + LOG.warn(_('no method for message: %s') % message_data) + ctxt.reply(_('No method for message: %s') % message_data) + return + self.pool.spawn_n(self._process_data, ctxt, method, args) + + @exception.wrap_exception() + def _process_data(self, ctxt, method, args): + """Thread that maigcally looks for a method on the proxy + object and calls it. + """ + + node_func = getattr(self.proxy, str(method)) + node_args = dict((str(k), v) for k, v in args.iteritems()) + # NOTE(vish): magic is fun! + try: + rval = node_func(context=ctxt, **node_args) + # Check if the result was a generator + if isinstance(rval, types.GeneratorType): + for x in rval: + ctxt.reply(x, None) + else: + ctxt.reply(rval, None) + # This final None tells multicall that it is done. + ctxt.reply(None, None) + except Exception as e: + logging.exception('Exception during message handling') + ctxt.reply(None, sys.exc_info()) + return + + +def _unpack_context(msg): + """Unpack context from msg.""" + context_dict = {} + for key in list(msg.keys()): + # NOTE(vish): Some versions of python don't like unicode keys + # in kwargs. + key = str(key) + if key.startswith('_context_'): + value = msg.pop(key) + context_dict[key[9:]] = value + context_dict['msg_id'] = msg.pop('_msg_id', None) + LOG.debug(_('unpacked context: %s'), context_dict) + return RpcContext.from_dict(context_dict) + + +def _pack_context(msg, context): + """Pack context into msg. + + Values for message keys need to be less than 255 chars, so we pull + context out into a bunch of separate keys. If we want to support + more arguments in rabbit messages, we may want to do the same + for args at some point. + + """ + context_d = dict([('_context_%s' % key, value) + for (key, value) in context.to_dict().iteritems()]) + msg.update(context_d) + + +class RpcContext(context.RequestContext): + def __init__(self, *args, **kwargs): + msg_id = kwargs.pop('msg_id', None) + self.msg_id = msg_id + super(RpcContext, self).__init__(*args, **kwargs) + + def reply(self, *args, **kwargs): + if self.msg_id: + msg_reply(self.msg_id, *args, **kwargs) + + +class MulticallWaiter(object): + def __init__(self, connection): + self._connection = connection + self._iterator = connection.consume() + self._result = None + self._done = False + + def done(self): + self._done = True + self._connection = None + + def __call__(self, data): + """The consume() callback will call this. Store the result.""" + if data['failure']: + self._result = RemoteError(*data['failure']) + else: + self._result = data['result'] + + def __iter__(self): + if self._done: + raise StopIteration + while True: + self._iterator.next() + result = self._result + if isinstance(result, Exception): + self.done() + raise result + if result == None: + self.done() + raise StopIteration + yield result + + +def create_consumer(conn, topic, proxy, fanout=False): + """Create a consumer that calls a method in a proxy object""" + if fanout: + return conn.fanout_consumer(topic, ProxyCallback(proxy)) + else: + return conn.topic_consumer(topic, ProxyCallback(proxy)) + + +def create_consumer_set(conn, consumers): + # FIXME(comstud): Replace this however necessary + # Returns an object that you can call .wait() on to consume + # all queues? + # Needs to have a .close() which will stop consuming? + # Needs to also have an attach_to_eventlet method for tests? + raise NotImplemented + + +def multicall(context, topic, msg): + """Make a call that returns multiple times.""" + # Can't use 'with' for multicall, as it returns an iterator + # that will continue to use the connection. When it's done, + # connection.close() will get called which will put it back into + # the pool + LOG.debug(_('Making asynchronous call on %s ...'), topic) + msg_id = uuid.uuid4().hex + msg.update({'_msg_id': msg_id}) + LOG.debug(_('MSG_ID is %s') % (msg_id)) + _pack_context(msg, context) + + conn = ConnectionContext() + wait_msg = MulticallWaiter(conn) + conn.direct_consumer(msg_id, wait_msg) + conn.topic_send(topic, msg) + + return wait_msg + + +def call(context, topic, msg): + """Sends a message on a topic and wait for a response.""" + rv = multicall(context, topic, msg) + # NOTE(vish): return the last result from the multicall + rv = list(rv) + if not rv: + return + return rv[-1] + + +def cast(context, topic, msg): + """Sends a message on a topic without waiting for a response.""" + LOG.debug(_('Making asynchronous cast on %s...'), topic) + _pack_context(msg, context) + with ConnectionContext() as conn: + conn.topic_send(topic, msg) + + +def fanout_cast(context, topic, msg): + """Sends a message on a fanout exchange without waiting for a response.""" + LOG.debug(_('Making asynchronous fanout cast...')) + _pack_context(msg, context) + with ConnectionContext() as conn: + conn.fanout_send(topic, msg) + + +def msg_reply(msg_id, reply=None, failure=None): + """Sends a reply or an error on the channel signified by msg_id. + + Failure should be a sys.exc_info() tuple. + + """ + with ConnectionContext() as conn: + if failure: + message = str(failure[1]) + tb = traceback.format_exception(*failure) + LOG.error(_("Returning exception %s to caller"), message) + LOG.error(tb) + failure = (failure[0].__name__, str(failure[1]), tb) + + try: + msg = {'result': reply, 'failure': failure} + except TypeError: + msg = {'result': dict((k, repr(v)) + for k, v in reply.__dict__.iteritems()), + 'failure': failure} + conn.direct_send(msg_id, msg) -- cgit From 1c5031ca6f89de4ac8a7dac271aff9942fc9602a Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 26 Aug 2011 16:00:50 -0700 Subject: more fixes --- nova/rpc/impl_kombu.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index a222bb885..cfef421c6 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -15,7 +15,7 @@ # under the License. from nova import flags -from nova import log as logging +from nova.rpc.common import RemoteError, LOG import kombu import kombu.entity @@ -28,7 +28,6 @@ import uuid FLAGS = flags.FLAGS -LOG = logging.getLogger('nova.rpc') flags.DEFINE_integer('rpc_conn_pool_size', 30, 'Size of RPC connection pool') @@ -559,7 +558,7 @@ class ProxyCallback(object): # This final None tells multicall that it is done. ctxt.reply(None, None) except Exception as e: - logging.exception('Exception during message handling') + LOG.exception('Exception during message handling') ctxt.reply(None, sys.exc_info()) return -- cgit From 3c835ebe4bb6e0aa61da2a2e44a6b19bc92fc72a Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 26 Aug 2011 16:04:34 -0700 Subject: flag for kombu connection backoff on retries --- nova/flags.py | 1 + nova/rpc/impl_kombu.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/flags.py b/nova/flags.py index 95000df1b..ac70386e7 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -303,6 +303,7 @@ DEFINE_string('rabbit_userid', 'guest', 'rabbit userid') DEFINE_string('rabbit_password', 'guest', 'rabbit password') DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') +DEFINE_integer('rabbit_interval_stepping', 2, 'rabbit connection retry backoff in seconds') DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_boolean('rabbit_durable_queues', False, 'use durable queues') diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index cfef421c6..65199808e 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -289,7 +289,7 @@ class Connection(object): self.queues = [] self.max_retries = FLAGS.rabbit_max_retries self.interval_start = FLAGS.rabbit_retry_interval - self.interval_stepping = 0 + self.interval_stepping = FLAGS.rabbit_interval_stepping self.interval_max = FLAGS.rabbit_retry_interval self.params = dict(hostname=FLAGS.rabbit_host, -- cgit From 400427ab786779109d49b27eda2fe9e246503dd6 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 26 Aug 2011 16:17:40 -0700 Subject: use db layer for aggregation --- nova/api/openstack/contrib/simple_tenant_usage.py | 72 ++++++-------------- nova/db/api.py | 11 +++- nova/db/sqlalchemy/api.py | 19 ++++-- .../openstack/contrib/test_simple_tenant_usage.py | 77 +++++++++------------- 4 files changed, 70 insertions(+), 109 deletions(-) diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index 5f4218237..16e712815 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -31,35 +31,7 @@ from webob import exc FLAGS = flags.FLAGS -INSTANCE_FIELDS = ['id', - 'image_ref', - 'project_id', - 'user_id', - 'display_name', - 'state_description', - 'instance_type_id', - 'launched_at', - 'terminated_at'] - - class SimpleTenantUsageController(object): - def _get_instances_for_time_period(self, period_start, period_stop, - tenant_id): - tenant_clause = '' - if tenant_id: - tenant_clause = " and project_id='%s'" % tenant_id - - conn = get_session().connection() - rows = conn.execute("select %s from instances where \ - (terminated_at is NULL or terminated_at > '%s') \ - and (launched_at < '%s') %s" %\ - (','.join(INSTANCE_FIELDS), - period_start.isoformat(' '),\ - period_stop.isoformat(' '), - tenant_clause)).fetchall() - - return rows - def _hours_for(self, instance, period_start, period_stop): launched_at = instance['launched_at'] terminated_at = instance['terminated_at'] @@ -99,62 +71,58 @@ class SimpleTenantUsageController(object): def _tenant_usages_for_period(self, context, period_start, period_stop, tenant_id=None, detailed=True): - rows = self._get_instances_for_time_period(period_start, - period_stop, - tenant_id) + instances = db.instance_get_active_by_window(context, + period_start, + period_stop, + tenant_id, + fast=True) + from nova import log as logging + logging.info(instances) rval = {} flavors = {} - for row in rows: + for instance in instances: info = {} - for i in range(len(INSTANCE_FIELDS)): - info[INSTANCE_FIELDS[i]] = row[i] - info['hours'] = self._hours_for(info, period_start, period_stop) - flavor_type = info['instance_type_id'] + info['hours'] = self._hours_for(instance, + period_start, + period_stop) + flavor_type = instance['instance_type_id'] if not flavors.get(flavor_type): try: flavors[flavor_type] = db.instance_type_get(context, - info['instance_type_id']) + flavor_type) except exception.InstanceTypeNotFound: # can't bill if there is no instance type continue flavor = flavors[flavor_type] - info['name'] = info['display_name'] - del(info['display_name']) + info['name'] = instance['display_name'] info['memory_mb'] = flavor['memory_mb'] info['local_gb'] = flavor['local_gb'] info['vcpus'] = flavor['vcpus'] - info['tenant_id'] = info['project_id'] - del(info['project_id']) + info['tenant_id'] = instance['project_id'] info['flavor'] = flavor['name'] - del(info['instance_type_id']) - info['started_at'] = info['launched_at'] - del(info['launched_at']) + info['started_at'] = instance['launched_at'] - info['ended_at'] = info['terminated_at'] - del(info['terminated_at']) + info['ended_at'] = instance['terminated_at'] if info['ended_at']: info['state'] = 'terminated' else: - info['state'] = info['state_description'] - - del(info['state_description']) + info['state'] = instance['state_description'] now = datetime.utcnow() if info['state'] == 'terminated': - delta = self._parse_datetime(info['ended_at'])\ - - self._parse_datetime(info['started_at']) + delta = info['ended_at'] - info['started_at'] else: - delta = now - self._parse_datetime(info['started_at']) + delta = now - info['started_at'] info['uptime'] = delta.days * 24 * 60 + delta.seconds diff --git a/nova/db/api.py b/nova/db/api.py index 3bb9b4970..f443239c1 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -495,9 +495,14 @@ def instance_get_all_by_filters(context, filters): return IMPL.instance_get_all_by_filters(context, filters) -def instance_get_active_by_window(context, begin, end=None): - """Get instances active during a certain time window.""" - return IMPL.instance_get_active_by_window(context, begin, end) +def instance_get_active_by_window(context, begin, end=None, + project_id=None, fast=False): + """Get instances active during a certain time window. + + Setting fast to true will stop all joinedloads. + Specifying a project_id will filter for a certain project.""" + return IMPL.instance_get_active_by_window(context, begin, end, + project_id, fast) def instance_get_all_by_user(context, user_id): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index d1fbf8cab..2d50f458f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1307,20 +1307,25 @@ def instance_get_all_by_filters(context, filters): @require_admin_context -def instance_get_active_by_window(context, begin, end=None): +def instance_get_active_by_window(context, begin, end=None, + project_id=None, fast=False): """Return instances that were continuously active over the given window""" session = get_session() - query = session.query(models.Instance).\ - options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ips.network')).\ - options(joinedload('instance_type')).\ - filter(models.Instance.launched_at < begin) + query = session.query(models.Instance) + if not fast: + query = query.options(joinedload_all('fixed_ips.floating_ips')).\ + options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ips.network')).\ + options(joinedload('instance_type')) + + query = query.filter(models.Instance.launched_at < begin) if end: query = query.filter(or_(models.Instance.terminated_at == None, models.Instance.terminated_at > end)) else: query = query.filter(models.Instance.terminated_at == None) + if project_id: + query = query.filter_by(project_id=project_id) return query.all() diff --git a/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py b/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py index d20e36aaf..2bd619820 100644 --- a/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py +++ b/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py @@ -23,13 +23,8 @@ from nova import context from nova import db from nova import flags from nova import test -from nova.compute import instance_types -from nova.db.sqlalchemy import models -from nova.db.sqlalchemy import session from nova.tests.api.openstack import fakes -from webob import exc -from nova.api.openstack.contrib import simple_tenant_usage FLAGS = flags.FLAGS @@ -44,27 +39,6 @@ STOP = datetime.datetime.utcnow() START = STOP - datetime.timedelta(hours=HOURS) -def fake_get_session(): - class FakeFetcher(object): - def fetchall(fetcher_self): - # return 10 rows, 2 tenants, 5 servers each, each run for 1 day - return [get_fake_db_row(START, - STOP, - x, - "faketenant_%s" % (x / SERVERS)) - for x in xrange(TENANTS * SERVERS)] - - class FakeConn(object): - def execute(self, query): - return FakeFetcher() - - class FakeSession(object): - def connection(self): - return FakeConn() - - return FakeSession() - - def fake_instance_type_get(context, instance_type_id): return {'id': 1, 'vcpus': VCPUS, @@ -74,25 +48,32 @@ def fake_instance_type_get(context, instance_type_id): 'fakeflavor'} -def get_fake_db_row(start, end, instance_id, tenant_id): - return [instance_id, - '1', - tenant_id, - 'fakeuser', - 'name', - 'state', - 1, - start, - None] +def get_fake_db_instance(start, end, instance_id, tenant_id): + return {'id': instance_id, + 'image_ref': '1', + 'project_id': tenant_id, + 'user_id': 'fakeuser', + 'display_name': 'name', + 'state_description': 'state', + 'instance_type_id': 1, + 'launched_at': start, + 'terminated_at': end} + +def fake_instance_get_active_by_window(context, begin, end, project_id, fast): + return [get_fake_db_instance(START, + STOP, + x, + "faketenant_%s" % (x / SERVERS)) + for x in xrange(TENANTS * SERVERS)] class SimpleTenantUsageTest(test.TestCase): def setUp(self): super(SimpleTenantUsageTest, self).setUp() - self.stubs.Set(session, "get_session", - fake_get_session) self.stubs.Set(db, "instance_type_get", fake_instance_type_get) + self.stubs.Set(db, "instance_get_active_by_window", + fake_instance_get_active_by_window) self.admin_context = context.RequestContext('fakeadmin_0', 'faketenant_0', is_admin=True) @@ -104,13 +85,9 @@ class SimpleTenantUsageTest(test.TestCase): is_admin=False) FLAGS.allow_admin_api = True - def test_verify_db_fields_exist_in_instance_model(self): - for field in simple_tenant_usage.INSTANCE_FIELDS: - self.assertTrue(field in models.Instance.__table__.columns) - def test_verify_index(self): req = webob.Request.blank( - '/v1.1/os-simple-tenant-usage?start=%s&end=%s' % + '/v1.1/123/os-simple-tenant-usage?start=%s&end=%s' % (START.isoformat(), STOP.isoformat())) req.method = "GET" req.headers["content-type"] = "application/json" @@ -121,6 +98,8 @@ class SimpleTenantUsageTest(test.TestCase): self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) usages = res_dict['tenant_usages'] + from nova import log as logging + logging.warn(usages) for i in xrange(TENANTS): self.assertEqual(int(usages[i]['total_hours']), SERVERS * HOURS) @@ -134,7 +113,8 @@ class SimpleTenantUsageTest(test.TestCase): def test_verify_detailed_index(self): req = webob.Request.blank( - '/v1.1/os-simple-tenant-usage?detailed=1&start=%s&end=%s' % + '/v1.1/123/os-simple-tenant-usage?' + 'detailed=1&start=%s&end=%s' % (START.isoformat(), STOP.isoformat())) req.method = "GET" req.headers["content-type"] = "application/json" @@ -151,7 +131,8 @@ class SimpleTenantUsageTest(test.TestCase): def test_verify_index_fails_for_nonadmin(self): req = webob.Request.blank( - '/v1.1/os-simple-tenant-usage?detailed=1&start=%s&end=%s' % + '/v1.1/123/os-simple-tenant-usage?' + 'detailed=1&start=%s&end=%s' % (START.isoformat(), STOP.isoformat())) req.method = "GET" req.headers["content-type"] = "application/json" @@ -161,7 +142,8 @@ class SimpleTenantUsageTest(test.TestCase): def test_verify_show(self): req = webob.Request.blank( - '/v1.1/os-simple-tenant-usage/faketenant_0?start=%s&end=%s' % + '/v1.1/faketenant_0/os-simple-tenant-usage/' + 'faketenant_0?start=%s&end=%s' % (START.isoformat(), STOP.isoformat())) req.method = "GET" req.headers["content-type"] = "application/json" @@ -179,7 +161,8 @@ class SimpleTenantUsageTest(test.TestCase): def test_verify_show_cant_view_other_tenant(self): req = webob.Request.blank( - '/v1.1/os-simple-tenant-usage/faketenant_0?start=%s&end=%s' % + '/v1.1/faketenant_1/os-simple-tenant-usage/' + 'faketenant_0?start=%s&end=%s' % (START.isoformat(), STOP.isoformat())) req.method = "GET" req.headers["content-type"] = "application/json" -- cgit From 470b9dc73c5e27ef8716436fe22e9f32dbdffd28 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 26 Aug 2011 17:40:22 -0700 Subject: add tests to verify NotFound exceptions are wrapped with the proper ids --- nova/tests/api/ec2/__init__.py | 19 +++++ nova/tests/api/ec2/test_middleware.py | 130 ++++++++++++++++++++++++++++++++++ nova/tests/test_cloud.py | 7 -- nova/tests/test_middleware.py | 85 ---------------------- 4 files changed, 149 insertions(+), 92 deletions(-) create mode 100644 nova/tests/api/ec2/__init__.py create mode 100644 nova/tests/api/ec2/test_middleware.py delete mode 100644 nova/tests/test_middleware.py diff --git a/nova/tests/api/ec2/__init__.py b/nova/tests/api/ec2/__init__.py new file mode 100644 index 000000000..6dab802f2 --- /dev/null +++ b/nova/tests/api/ec2/__init__.py @@ -0,0 +1,19 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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. + +# NOTE(vish): this forces the fixtures from tests/__init.py:setup() to work +from nova.tests import * diff --git a/nova/tests/api/ec2/test_middleware.py b/nova/tests/api/ec2/test_middleware.py new file mode 100644 index 000000000..295f6c4ea --- /dev/null +++ b/nova/tests/api/ec2/test_middleware.py @@ -0,0 +1,130 @@ +# 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. + +import webob +import webob.dec +import webob.exc + +from nova.api import ec2 +from nova import context +from nova import exception +from nova import flags +from nova import test +from nova import utils + +from xml.etree.ElementTree import fromstring as xml_to_tree + +FLAGS = flags.FLAGS + + +@webob.dec.wsgify +def conditional_forbid(req): + """Helper wsgi app returns 403 if param 'die' is 1.""" + if 'die' in req.params and req.params['die'] == '1': + raise webob.exc.HTTPForbidden() + return 'OK' + + +class LockoutTestCase(test.TestCase): + """Test case for the Lockout middleware.""" + def setUp(self): # pylint: disable=C0103 + super(LockoutTestCase, self).setUp() + utils.set_time_override() + self.lockout = ec2.Lockout(conditional_forbid) + + def tearDown(self): # pylint: disable=C0103 + utils.clear_time_override() + super(LockoutTestCase, self).tearDown() + + def _send_bad_attempts(self, access_key, num_attempts=1): + """Fail x.""" + for i in xrange(num_attempts): + req = webob.Request.blank('/?AWSAccessKeyId=%s&die=1' % access_key) + self.assertEqual(req.get_response(self.lockout).status_int, 403) + + def _is_locked_out(self, access_key): + """Sends a test request to see if key is locked out.""" + req = webob.Request.blank('/?AWSAccessKeyId=%s' % access_key) + return (req.get_response(self.lockout).status_int == 403) + + def test_lockout(self): + self._send_bad_attempts('test', FLAGS.lockout_attempts) + self.assertTrue(self._is_locked_out('test')) + + def test_timeout(self): + self._send_bad_attempts('test', FLAGS.lockout_attempts) + self.assertTrue(self._is_locked_out('test')) + utils.advance_time_seconds(FLAGS.lockout_minutes * 60) + self.assertFalse(self._is_locked_out('test')) + + def test_multiple_keys(self): + self._send_bad_attempts('test1', FLAGS.lockout_attempts) + self.assertTrue(self._is_locked_out('test1')) + self.assertFalse(self._is_locked_out('test2')) + utils.advance_time_seconds(FLAGS.lockout_minutes * 60) + self.assertFalse(self._is_locked_out('test1')) + self.assertFalse(self._is_locked_out('test2')) + + def test_window_timeout(self): + self._send_bad_attempts('test', FLAGS.lockout_attempts - 1) + self.assertFalse(self._is_locked_out('test')) + utils.advance_time_seconds(FLAGS.lockout_window * 60) + self._send_bad_attempts('test', FLAGS.lockout_attempts - 1) + self.assertFalse(self._is_locked_out('test')) + + +class ExecutorTestCase(test.TestCase): + def setUp(self): + super(ExecutorTestCase, self).setUp() + self.executor = ec2.Executor() + + def _execute(self, invoke): + class Fake(object): + pass + fake_ec2_request = Fake() + fake_ec2_request.invoke = invoke + + fake_wsgi_request = Fake() + + fake_wsgi_request.environ = { + 'nova.context': context.get_admin_context(), + 'ec2.request': fake_ec2_request, + } + return self.executor(fake_wsgi_request) + + def _extract_message(self, result): + tree = xml_to_tree(result.body) + return tree.findall('./Errors')[0].find('Error/Message').text + + def test_instance_not_found(self): + def not_found(context): + raise exception.InstanceNotFound(instance_id=5) + result = self._execute(not_found) + self.assertIn('i-00000005', self._extract_message(result)) + + def test_snapshot_not_found(self): + def not_found(context): + raise exception.SnapshotNotFound(snapshot_id=5) + result = self._execute(not_found) + self.assertIn('snap-00000005', self._extract_message(result)) + + def test_volume_not_found(self): + def not_found(context): + raise exception.VolumeNotFound(volume_id=5) + result = self._execute(not_found) + self.assertIn('vol-00000005', self._extract_message(result)) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 0793784f8..1bf12a06f 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -86,13 +86,6 @@ class CloudTestCase(test.TestCase): self.stubs.Set(rpc, 'cast', finish_cast) - def tearDown(self): - networks = db.project_get_networks(self.context, self.project_id, - associate=False) - for network in networks: - db.network_disassociate(self.context, network['id']) - super(CloudTestCase, self).tearDown() - def _create_key(self, name): # NOTE(vish): create depends on pool, so just call helper directly return cloud._gen_key(self.context, self.context.user_id, name) diff --git a/nova/tests/test_middleware.py b/nova/tests/test_middleware.py deleted file mode 100644 index 40d117c45..000000000 --- a/nova/tests/test_middleware.py +++ /dev/null @@ -1,85 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import webob -import webob.dec -import webob.exc - -from nova.api import ec2 -from nova import flags -from nova import test -from nova import utils - - -FLAGS = flags.FLAGS - - -@webob.dec.wsgify -def conditional_forbid(req): - """Helper wsgi app returns 403 if param 'die' is 1.""" - if 'die' in req.params and req.params['die'] == '1': - raise webob.exc.HTTPForbidden() - return 'OK' - - -class LockoutTestCase(test.TestCase): - """Test case for the Lockout middleware.""" - def setUp(self): # pylint: disable=C0103 - super(LockoutTestCase, self).setUp() - utils.set_time_override() - self.lockout = ec2.Lockout(conditional_forbid) - - def tearDown(self): # pylint: disable=C0103 - utils.clear_time_override() - super(LockoutTestCase, self).tearDown() - - def _send_bad_attempts(self, access_key, num_attempts=1): - """Fail x.""" - for i in xrange(num_attempts): - req = webob.Request.blank('/?AWSAccessKeyId=%s&die=1' % access_key) - self.assertEqual(req.get_response(self.lockout).status_int, 403) - - def _is_locked_out(self, access_key): - """Sends a test request to see if key is locked out.""" - req = webob.Request.blank('/?AWSAccessKeyId=%s' % access_key) - return (req.get_response(self.lockout).status_int == 403) - - def test_lockout(self): - self._send_bad_attempts('test', FLAGS.lockout_attempts) - self.assertTrue(self._is_locked_out('test')) - - def test_timeout(self): - self._send_bad_attempts('test', FLAGS.lockout_attempts) - self.assertTrue(self._is_locked_out('test')) - utils.advance_time_seconds(FLAGS.lockout_minutes * 60) - self.assertFalse(self._is_locked_out('test')) - - def test_multiple_keys(self): - self._send_bad_attempts('test1', FLAGS.lockout_attempts) - self.assertTrue(self._is_locked_out('test1')) - self.assertFalse(self._is_locked_out('test2')) - utils.advance_time_seconds(FLAGS.lockout_minutes * 60) - self.assertFalse(self._is_locked_out('test1')) - self.assertFalse(self._is_locked_out('test2')) - - def test_window_timeout(self): - self._send_bad_attempts('test', FLAGS.lockout_attempts - 1) - self.assertFalse(self._is_locked_out('test')) - utils.advance_time_seconds(FLAGS.lockout_window * 60) - self._send_bad_attempts('test', FLAGS.lockout_attempts - 1) - self.assertFalse(self._is_locked_out('test')) -- cgit From 19cb3672f2849fe659173631f7f81ed489f1ea7e Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Fri, 26 Aug 2011 17:46:47 -0700 Subject: v1.0 of server create injects first users keypair --- nova/api/openstack/create_instance_helper.py | 1 - nova/api/openstack/servers.py | 13 +++++++++++++ nova/tests/api/openstack/test_servers.py | 6 ++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index e7428bf41..d82cb534f 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -19,7 +19,6 @@ import base64 from webob import exc from xml.dom import minidom -from nova import db from nova import exception from nova import flags from nova import log as logging diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index ad563b771..f288f2228 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -22,6 +22,7 @@ from xml.dom import minidom import webob from nova import compute +from nova import db from nova import exception from nova import flags from nova import log as logging @@ -563,6 +564,18 @@ class ControllerV10(Controller): raise exc.HTTPNotFound() return webob.Response(status_int=202) + def create(self, req, body): + """ Creates a new server for a given user """ + # note(ja): v1.0 injects the first keypair for the project for testing + if 'server' in body and not 'key_name' in body['server']: + context = req.environ["nova.context"] + keypairs = db.key_pair_get_all_by_user(context.elevated(), + context.user_id) + if keypairs: + body['server']['key_name'] = keypairs[0]['name'] + + return super(ControllerV10, self).create(req, body) + def _image_ref_from_req_data(self, data): return data['server']['imageId'] diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index bb531f462..c54bead49 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1761,7 +1761,7 @@ class ServersTest(test.TestCase): body = dict(server=dict( name='server_test', imageRef=image_href, flavorRef=flavor_ref, key_name='nonexistentkey')) - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1781,7 +1781,7 @@ class ServersTest(test.TestCase): body = dict(server=dict( name='server_test', imageRef=image_href, flavorRef=flavor_ref, key_name='mykey')) - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -3793,6 +3793,7 @@ class ServersViewBuilderV11Test(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "test_server", + "key_name": "", "status": "BUILD", "hostId": '', "image": { @@ -3848,6 +3849,7 @@ class ServersViewBuilderV11Test(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "test_server", + "key_name": "", "status": "BUILD", "hostId": '', "image": { -- cgit From 3a4ee30de0f619b7046e90ce9b6978e3a6dd20a2 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Sat, 27 Aug 2011 14:07:55 -0700 Subject: Default rabbit max_retries to forever Modify carrot code to handle retry backoffs and obey max_retries = forever Fix some kombu issues from cut-n-paste Service should make sure to close the RPC connection --- nova/flags.py | 6 +++--- nova/rpc/impl_carrot.py | 28 +++++++++++++++++++++------- nova/rpc/impl_kombu.py | 35 +++++++++++++++++------------------ nova/service.py | 6 ++++++ 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index ac70386e7..e09b4721a 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -302,9 +302,9 @@ DEFINE_bool('rabbit_use_ssl', False, 'connect over SSL') DEFINE_string('rabbit_userid', 'guest', 'rabbit userid') DEFINE_string('rabbit_password', 'guest', 'rabbit password') DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') -DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') -DEFINE_integer('rabbit_interval_stepping', 2, 'rabbit connection retry backoff in seconds') -DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') +DEFINE_integer('rabbit_retry_interval', 1, 'rabbit connection retry interval to start') +DEFINE_integer('rabbit_retry_backoff', 2, 'rabbit connection retry backoff in seconds') +DEFINE_integer('rabbit_max_retries', 0, 'maximum rabbit connection attempts (0=try forever)') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_boolean('rabbit_durable_queues', False, 'use durable queues') DEFINE_list('enabled_apis', ['ec2', 'osapi'], diff --git a/nova/rpc/impl_carrot.py b/nova/rpc/impl_carrot.py index 529f98722..117489bc6 100644 --- a/nova/rpc/impl_carrot.py +++ b/nova/rpc/impl_carrot.py @@ -119,25 +119,34 @@ class Consumer(messaging.Consumer): """ def __init__(self, *args, **kwargs): - for i in xrange(FLAGS.rabbit_max_retries): - if i > 0: - time.sleep(FLAGS.rabbit_retry_interval) + max_retries = FALGS.rabbit_max_retries + sleep_time = FLAGS.rabbit_retry_interval + tries = 0 + while True: + tries += 1 + if tries > 1: + time.sleep(sleep_time) + # backoff for next retry attempt.. if there is one + sleep_time += FLAGS.rabbit_retry_backoff + if sleep_time > 30: + sleep_time = 30 try: super(Consumer, self).__init__(*args, **kwargs) self.failed_connection = False break except Exception as e: # Catching all because carrot sucks + self.failed_connection = True + if max_retries > 0 and tries == max_retries: + break fl_host = FLAGS.rabbit_host fl_port = FLAGS.rabbit_port - fl_intv = FLAGS.rabbit_retry_interval + fl_intv = sleep_time LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is' ' unreachable: %(e)s. Trying again in %(fl_intv)d' ' seconds.') % locals()) - self.failed_connection = True if self.failed_connection: LOG.error(_('Unable to connect to AMQP server ' - 'after %d tries. Shutting down.'), - FLAGS.rabbit_max_retries) + 'after %(tries)d tries. Shutting down.') % locals()) sys.exit(1) def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): @@ -520,6 +529,11 @@ class MulticallWaiter(object): yield result +def create_connection(new=True): + """Create a connection""" + return Connection.instance(new=new) + + def create_consumer(conn, topic, proxy, fanout=False): """Create a consumer that calls methods in the proxy""" if fanout: diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index 65199808e..db839dd2a 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -288,9 +288,13 @@ class Connection(object): def __init__(self): self.queues = [] self.max_retries = FLAGS.rabbit_max_retries + # Try forever? + if self.max_retries <= 0: + self.max_retries = None self.interval_start = FLAGS.rabbit_retry_interval - self.interval_stepping = FLAGS.rabbit_interval_stepping - self.interval_max = FLAGS.rabbit_retry_interval + self.interval_stepping = FLAGS.rabbit_retry_backoff + # max retry-interval = 30 seconds + self.interval_max = 30 self.params = dict(hostname=FLAGS.rabbit_host, port=FLAGS.rabbit_port, @@ -302,16 +306,6 @@ class Connection(object): self.connection = None self.reconnect() - @classmethod - def instance(cls, new=True): - """Returns the instance.""" - if new or not hasattr(cls, '_instance'): - if new: - return cls() - else: - cls._instance = cls() - return cls._instance - def reconnect(self): """Handles reconnecting and re-estblishing queues""" if self.connection: @@ -330,12 +324,12 @@ class Connection(object): interval_step=self.interval_stepping, interval_max=self.interval_max) except self.connection.connection_errors, e: + # We should only get here if max_retries is set. We'll go + # ahead and exit in this case. err_str = str(e) - max_retries = FLAGS.rabbit_max_retries + max_retries = self.max_retries LOG.error(_('Unable to connect to AMQP server ' 'after %(max_retries)d tries: %(err_str)s') % locals()) - # NOTE(comstud): Original carrot code exits after so many - # attempts, but I wonder if we should re-try indefinitely sys.exit(1) LOG.info(_('Connected to AMQP server on %(hostname)s:%(port)d' % self.params)) @@ -448,7 +442,7 @@ class Pool(pools.Pool): # TODO(comstud): Timeout connections not used in a while def create(self): LOG.debug('Creating new connection') - return RPCIMPL.Connection() + return Connection() # Create a ConnectionPool to use for RPC calls. We'll order the # pool as a stack (LIFO), so that we can potentially loop through and @@ -464,7 +458,7 @@ class ConnectionContext(object): if pooled: self.connection = ConnectionPool.get() else: - self.connection = RPCIMPL.Connection() + self.connection = Connection() self.pooled = pooled def __enter__(self): @@ -636,6 +630,11 @@ class MulticallWaiter(object): yield result +def create_connection(new=True): + """Create a connection""" + return ConnectionContext(pooled=not new) + + def create_consumer(conn, topic, proxy, fanout=False): """Create a consumer that calls a method in a proxy object""" if fanout: @@ -649,7 +648,7 @@ def create_consumer_set(conn, consumers): # Returns an object that you can call .wait() on to consume # all queues? # Needs to have a .close() which will stop consuming? - # Needs to also have an attach_to_eventlet method for tests? + # Needs to also have an method for tests? raise NotImplemented diff --git a/nova/service.py b/nova/service.py index 959e79052..a872a36ee 100644 --- a/nova/service.py +++ b/nova/service.py @@ -242,6 +242,12 @@ class Service(object): self.consumer_set_thread.wait() except greenlet.GreenletExit: pass + # Try to shut the connection down, but if we get any sort of + # errors, go ahead and ignore them.. as we're shutting down anyway + try: + self.conn.close() + except Exception: + pass for x in self.timers: try: x.stop() -- cgit From 4faaf9c37d030d68cfea818d396963e3ed7deeaa Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Sat, 27 Aug 2011 21:33:14 -0700 Subject: fix FALGS typo --- nova/rpc/impl_carrot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/rpc/impl_carrot.py b/nova/rpc/impl_carrot.py index 117489bc6..40097e10e 100644 --- a/nova/rpc/impl_carrot.py +++ b/nova/rpc/impl_carrot.py @@ -119,7 +119,7 @@ class Consumer(messaging.Consumer): """ def __init__(self, *args, **kwargs): - max_retries = FALGS.rabbit_max_retries + max_retries = FLAGS.rabbit_max_retries sleep_time = FLAGS.rabbit_retry_interval tries = 0 while True: -- cgit From d5b489383710605b10067550417a4e62a5f4f3e1 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Sun, 28 Aug 2011 11:37:19 -0700 Subject: use 'uuid' field in networks table rather than 'bridge'. Specify project_id when creating instance in unit test --- bin/nova-manage | 16 +++++++++++----- nova/db/api.py | 5 +++++ nova/db/sqlalchemy/api.py | 13 +++++++++++++ nova/exception.py | 4 ++++ nova/network/quantum/manager.py | 5 +++-- nova/network/quantum/nova_ipam_lib.py | 30 +++++++++++++++++------------- nova/tests/test_quantum.py | 6 ++++-- 7 files changed, 57 insertions(+), 22 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 4628e93b2..0c2cee3ce 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -681,6 +681,8 @@ class NetworkCommands(object): help='Multi host') @args('--dns1', dest="dns1", metavar="", help='First DNS') @args('--dns2', dest="dns2", metavar="", help='Second DNS') + @args('--uuid', dest="net_uuid", metavar="", + help='Network UUID') @args('--project_id', dest="project_id", metavar="", help='Project id') @args('--priority', dest="priority", metavar="", @@ -689,7 +691,7 @@ class NetworkCommands(object): network_size=None, multi_host=None, vlan_start=None, vpn_start=None, fixed_range_v6=None, gateway_v6=None, bridge=None, bridge_interface=None, dns1=None, dns2=None, - project_id=None, priority=None): + project_id=None, priority=None, uuid=None): """Creates fixed ips for host by range""" # check for certain required inputs @@ -768,7 +770,8 @@ class NetworkCommands(object): dns1=dns1, dns2=dns2, project_id=project_id, - priority=priority) + priority=priority, + uuid=uuid) def list(self): """List all created networks""" @@ -797,13 +800,13 @@ class NetworkCommands(object): def quantum_list(self): """List all created networks with Quantum-relevant fields""" _fmt = "%-32s\t%-10s\t%-10s\t%s , %s" - print _fmt % ( _('bridge / quantum-id'), + print _fmt % ( _('uuid'), _('project'), _('priority'), _('cidr_v4'), _('cidr_v6')) for network in db.network_get_all(context.get_admin_context()): - print _fmt % (network.bridge, + print _fmt % (network.uuid, network.project_id, network.priority, network.cidr, @@ -811,12 +814,15 @@ class NetworkCommands(object): @args('--network', dest="fixed_range", metavar='', help='Network to delete') + @args('--uuid', dest="uuid", metavar='', + help='UUID of network to delete') def delete(self, fixed_range): """Deletes a network""" # delete the network net_manager = utils.import_object(FLAGS.network_manager) - net_manager.delete_network(context.get_admin_context(), fixed_range) + net_manager.delete_network(context.get_admin_context(), fixed_range, + uuid=None) diff --git a/nova/db/api.py b/nova/db/api.py index 9ff3a1c74..17ef0bd0b 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -708,6 +708,11 @@ def network_get_by_bridge(context, bridge): return IMPL.network_get_by_bridge(context, bridge) +def network_get_by_uuid(context, uuid): + """Get a network by uuid or raise if it does not exist.""" + return IMPL.network_get_by_uuid(context, uuid) + + def network_get_by_cidr(context, cidr): """Get a network by cidr or raise if it does not exist""" return IMPL.network_get_by_cidr(context, cidr) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index d96b951a1..80ce76e8f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1846,6 +1846,19 @@ def network_get_by_bridge(context, bridge): return result +@require_admin_context +def network_get_by_uuid(context, uuid): + session = get_session() + result = session.query(models.Network).\ + filter_by(uuid=uuid).\ + filter_by(deleted=False).\ + first() + + if not result: + raise exception.NetworkNotFoundForUUID(uuid=uuid) + return result + + @require_admin_context def network_get_by_cidr(context, cidr): session = get_session() diff --git a/nova/exception.py b/nova/exception.py index 66740019b..8d6e84d74 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -411,6 +411,10 @@ class NetworkNotFoundForBridge(NetworkNotFound): message = _("Network could not be found for bridge %(bridge)s") +class NetworkNotFoundForUUID(NetworkNotFound): + message = _("Network could not be found for uuid %(uuid)s") + + class NetworkNotFoundForCidr(NetworkNotFound): message = _("Network could not be found with cidr %(cidr)s.") diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 79bb65939..932bdd82f 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -51,13 +51,14 @@ class QuantumManager(manager.FlatManager): def create_networks(self, context, label, cidr, multi_host, num_networks, network_size, cidr_v6, gateway_v6, bridge, - bridge_interface, dns1=None, dns2=None, **kwargs): + bridge_interface, dns1=None, dns2=None, uuid=None, + **kwargs): if num_networks != 1: raise Exception("QuantumManager requires that only one" " network is created per call") q_tenant_id = kwargs["project_id"] or \ FLAGS.quantum_default_tenant_id - quantum_net_id = bridge + quantum_net_id = uuid if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): raise Exception("Unable to find existing quantum " \ diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index b72edeeff..5bca9d024 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -47,15 +47,19 @@ class QuantumNovaIPAMLib: dns1=None, dns2=None): admin_context = context.elevated() subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) - manager.FlatManager.create_networks(self.net_manager, + networks = manager.FlatManager.create_networks(self.net_manager, admin_context, label, cidr, False, 1, subnet_size, cidr_v6, gateway_v6, quantum_net_id, None, dns1, dns2) - # now grab the network and update project_id + priority - network = db.network_get_by_bridge(admin_context, quantum_net_id) + if len(networks) != 1: + raise Exception("Error creating network entry") + + # now grab the network and update uuid, project_id, priority + network = networks[0] net = {"project_id": tenant_id, - "priority": priority} + "priority": priority, + "uuid": quantum_net_id} db.network_update(admin_context, network['id'], net) def get_network_id_by_cidr(self, context, cidr, project_id): @@ -64,11 +68,11 @@ class QuantumNovaIPAMLib: if not network: raise Exception("No network with fixed_range = %s" \ % fixed_range) - return network['bridge'] + return network['uuid'] def delete_subnets_by_net_id(self, context, net_id, project_id): admin_context = context.elevated() - network = db.network_get_by_bridge(admin_context, net_id) + network = db.network_get_by_uuid(admin_context, net_id) if not network: raise Exception("No network with net_id = %s" % net_id) manager.FlatManager.delete_network(self.net_manager, @@ -85,14 +89,14 @@ class QuantumNovaIPAMLib: id_priority_map = {} net_list = [] for n in networks: - net_id = n['bridge'] + net_id = n['uuid'] net_list.append((net_id, n["project_id"])) id_priority_map[net_id] = n['priority'] return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): admin_context = context.elevated() - network = db.network_get_by_bridge(admin_context, quantum_net_id) + network = db.network_get_by_uuid(admin_context, quantum_net_id) if network['cidr']: address = db.fixed_ip_associate_pool(admin_context, network['id'], @@ -102,9 +106,9 @@ class QuantumNovaIPAMLib: db.fixed_ip_update(admin_context, address, values) def get_subnets_by_net_id(self, context, tenant_id, net_id): - n = db.network_get_by_bridge(context.elevated(), net_id) + n = db.network_get_by_uuid(context.elevated(), net_id) subnet_data_v4 = { - 'network_id': n['bridge'], + 'network_id': n['uuid'], 'cidr': n['cidr'], 'gateway': n['gateway'], 'broadcast': n['broadcast'], @@ -112,7 +116,7 @@ class QuantumNovaIPAMLib: 'dns1': n['dns1'], 'dns2': n['dns2']} subnet_data_v6 = { - 'network_id': n['bridge'], + 'network_id': n['uuid'], 'cidr': n['cidr_v6'], 'gateway': n['gateway_v6'], 'broadcast': None, @@ -129,7 +133,7 @@ class QuantumNovaIPAMLib: def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): admin_context = context.elevated() - network = db.network_get_by_bridge(admin_context, net_id) + network = db.network_get_by_uuid(admin_context, net_id) vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) if network['cidr_v6']: ip = ipv6.to_global(network['cidr_v6'], @@ -140,7 +144,7 @@ class QuantumNovaIPAMLib: def verify_subnet_exists(self, context, tenant_id, quantum_net_id): admin_context = context.elevated() - network = db.network_get_by_bridge(admin_context, quantum_net_id) + network = db.network_get_by_uuid(admin_context, quantum_net_id) def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): try: diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 8ef22ac7e..ae9900b67 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -129,7 +129,8 @@ class QuantumTestCaseBase(object): project_id = "fake_project1" ctx = context.RequestContext('user1', project_id) - instance_ref = db.api.instance_create(ctx, {}) + instance_ref = db.api.instance_create(ctx, + {"project_id": project_id}) nw_info = self.net_man.allocate_for_instance(ctx, instance_id=instance_ref['id'], host="", instance_type_id=instance_ref['instance_type_id'], @@ -173,7 +174,8 @@ class QuantumTestCaseBase(object): self.net_man.validate_networks(ctx, requested_networks) - instance_ref = db.api.instance_create(ctx, {}) + instance_ref = db.api.instance_create(ctx, + {"project_id": project_id}) nw_info = self.net_man.allocate_for_instance(ctx, instance_id=instance_ref['id'], host="", instance_type_id=instance_ref['instance_type_id'], -- cgit From 1bfc7ce80c3936a19434dfc45c44f8a1acfd65ed Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 12:53:32 -0700 Subject: fix issue with setting 'Active' caused by Quantum API changes. Other misc fixes --- nova/network/quantum/quantum_connection.py | 6 +++--- nova/virt/libvirt/vif.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index c0eaad1fd..a13867af2 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -65,7 +65,7 @@ class QuantumClientConnection: def create_and_attach_port(self, tenant_id, net_id, interface_id): LOG.debug("Connecting interface %s to net %s for %s" % \ (interface_id, net_id, tenant_id)) - port_data = {'port': {'port-state': 'ACTIVE'}} + port_data = {'port': {'state': 'ACTIVE'}} resdict = self.client.create_port(net_id, port_data, tenant=tenant_id) port_id = resdict["port"]["id"] @@ -74,13 +74,13 @@ class QuantumClientConnection: tenant=tenant_id) def detach_and_delete_port(self, tenant_id, net_id, port_id): - LOG.debug("Deleteing port %s on net %s for %s" % \ + LOG.debug("Deleting port %s on net %s for %s" % \ (port_id, net_id, tenant_id)) self.client.detach_resource(net_id, port_id, tenant=tenant_id) self.client.delete_port(net_id, port_id, tenant=tenant_id) - # FIXME: this will be inefficient until API implements querying + # FIXME: (danwent) this will be inefficient until API implements querying def get_port_by_attachment(self, tenant_id, attachment_id): net_list_resdict = self.client.list_networks(tenant=tenant_id) diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 0b7438011..077c32474 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -101,7 +101,7 @@ class LibvirtOpenVswitchDriver(VIFDriver): """VIF driver for Open vSwitch.""" def get_dev_name(_self, iface_id): - return "tap-" + iface_id[0:15] + return "tap" + iface_id[0:11] def plug(self, instance, network, mapping): iface_id = mapping['vif_uuid'] -- cgit From 56891283f117997042363aee2e3ce00a5a12d9e0 Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 15:39:27 -0700 Subject: update melange ipam lib to use network uuid, not bridge --- nova/network/quantum/melange_ipam_lib.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 5b58520c2..cfdcde045 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -52,7 +52,7 @@ class QuantumMelangeIPAMLib: # create a entry in the network table just to store # the priority order for this network - net = {"bridge": quantum_net_id, + net = {"uuid": quantum_net_id, "project_id": project_id, "priority": priority} network = self.db.network_create_safe(context, net) @@ -79,7 +79,7 @@ class QuantumMelangeIPAMLib: if b['network_id'] == net_id: self.m_conn.delete_block(b['id'], tenant_id) - network = db.network_get_by_bridge(admin_context, net_id) + network = db.network_get_by_uuid(admin_context, net_id) if network is not None: db.network_delete_safe(context, network['id']) @@ -101,9 +101,8 @@ class QuantumMelangeIPAMLib: id_proj_map[b['network_id']] = tenant_id id_priority_map = {} - network = db.network_get_by_bridge(admin_context, net_id) for net_id, project_id in id_project_map.item(): - network = db.network_get_by_bridge(admin_context, net_id) + network = db.network_get_by_uuid(admin_context, net_id) if network is None: del id_proj_map[net_id] else: @@ -111,8 +110,11 @@ class QuantumMelangeIPAMLib: return sorted(id_priority_map.items(), key=lambda x: id_priority_map[x[0]]) - # FIXME: there must be a more efficient way to do this, - # talk to the melange folks + # FIXME: (danwent) Melange actually returns the subnet info + # when we query for a particular interface. we may want to + # reworks the ipam_manager python API to let us take advantage of + # this, as right now we have to get all blocks and cycle through + # them. def get_subnets_by_net_id(self, context, project_id, net_id): subnet_v4 = None subnet_v6 = None -- cgit From 256cb956abeff85f3cddce499b488dd112c4137d Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Sun, 28 Aug 2011 17:33:11 -0700 Subject: start to rework some consumer stuff --- nova/rpc/impl_kombu.py | 127 +++++++++++++++++++++++++++++-------------------- nova/service.py | 24 ++-------- 2 files changed, 81 insertions(+), 70 deletions(-) diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index db839dd2a..01871606c 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -35,11 +35,11 @@ flags.DEFINE_integer('rpc_thread_pool_size', 1024, 'Size of RPC thread pool') -class QueueBase(object): - """Queue base class.""" +class ConsumerBase(object): + """Consumer base class.""" def __init__(self, channel, callback, tag, **kwargs): - """Init the queue. + """Declare a queue on an amqp channel. 'channel' is the amqp channel to use 'callback' is the callback to call when messages are received @@ -55,20 +55,21 @@ class QueueBase(object): self.reconnect(channel) def reconnect(self, channel): - """Re-create the queue after a rabbit reconnect""" + """Re-declare the queue after a rabbit reconnect""" self.channel = channel self.kwargs['channel'] = channel self.queue = kombu.entity.Queue(**self.kwargs) self.queue.declare() def consume(self, *args, **kwargs): - """Consume from this queue. + """Actually declare the consumer on the amqp channel. This will + start the flow of messages from the queue. Using the + Connection.iterconsume() iterator will process the messages, + calling the appropriate callback. + If a callback is specified in kwargs, use that. Otherwise, use the callback passed during __init__() - The callback will be called if a message was read off of the - queue. - If kwargs['nowait'] is True, then this call will block until a message is read. @@ -100,7 +101,7 @@ class QueueBase(object): self.queue = None -class DirectQueue(QueueBase): +class DirectConsumer(ConsumerBase): """Queue/consumer class for 'direct'""" def __init__(self, channel, msg_id, callback, tag, **kwargs): @@ -123,7 +124,7 @@ class DirectQueue(QueueBase): type='direct', durable=options['durable'], auto_delete=options['auto_delete']) - super(DirectQueue, self).__init__( + super(DirectConsumer, self).__init__( channel, callback, tag, @@ -133,8 +134,8 @@ class DirectQueue(QueueBase): **options) -class TopicQueue(QueueBase): - """Queue/consumer class for 'topic'""" +class TopicConsumer(ConsumerBase): + """Consumer class for 'topic'""" def __init__(self, channel, topic, callback, tag, **kwargs): """Init a 'topic' queue. @@ -156,7 +157,7 @@ class TopicQueue(QueueBase): type='topic', durable=options['durable'], auto_delete=options['auto_delete']) - super(TopicQueue, self).__init__( + super(TopicConsumer, self).__init__( channel, callback, tag, @@ -166,8 +167,8 @@ class TopicQueue(QueueBase): **options) -class FanoutQueue(QueueBase): - """Queue/consumer class for 'fanout'""" +class FanoutConsumer(ConsumerBase): + """Consumer class for 'fanout'""" def __init__(self, channel, topic, callback, tag, **kwargs): """Init a 'fanout' queue. @@ -193,7 +194,7 @@ class FanoutQueue(QueueBase): type='fanout', durable=options['durable'], auto_delete=options['auto_delete']) - super(FanoutQueue, self).__init__( + super(FanoutConsumer, self).__init__( channel, callback, tag, @@ -286,7 +287,8 @@ class Connection(object): """Connection instance object.""" def __init__(self): - self.queues = [] + self.consumers = [] + self.consumer_thread = None self.max_retries = FLAGS.rabbit_max_retries # Try forever? if self.max_retries <= 0: @@ -334,9 +336,9 @@ class Connection(object): LOG.info(_('Connected to AMQP server on %(hostname)s:%(port)d' % self.params)) self.channel = self.connection.channel() - for consumer in self.queues: + for consumer in self.consumers: consumer.reconnect(self.channel) - if self.queues: + if self.consumers: LOG.debug(_("Re-established AMQP queues")) def get_channel(self): @@ -354,30 +356,32 @@ class Connection(object): def close(self): """Close/release this connection""" + self.cancel_consumer_thread() self.connection.release() self.connection = None def reset(self): """Reset a connection so it can be used again""" + self.cancel_consumer_thread() self.channel.close() self.channel = self.connection.channel() - self.queues = [] + self.consumers = [] - def create_queue(self, queue_cls, topic, callback): - """Create a queue using the class that was passed in and - add it to our list of queues used for consuming + def declare_consumer(self, consumer_cls, topic, callback): + """Create a Consumer using the class that was passed in and + add it to our list of consumers """ - queue = queue_cls(self.channel, topic, callback, - self.queue_num.next()) - self.queues.append(queue) - return queue + consumer = consumer_cls(self.channel, topic, callback, + self.consumer_num.next()) + self.consumers.append(consumer) + return consumer - def consume(self, limit=None): - """Consume from all queues""" + def iterconsume(self, limit=None): + """Return an iterator that will consume from all queues/consumers""" while True: try: - queues_head = self.queues[:-1] - queues_tail = self.queues[-1] + queues_head = self.consumers[:-1] + queues_tail = self.consumers[-1] for queue in queues_head: queue.consume(nowait=True) queues_tail.consume(nowait=False) @@ -391,6 +395,36 @@ class Connection(object): '%s' % str(e))) self.reconnect() + def consume(self, limit=None): + """Consume from all queues/consumers""" + it = self.iterconsume(limit=limit) + while True: + try: + it.next() + except StopIteration: + return + + def consume_in_thread(self): + """Consumer from all queues/consumers in a greenthread""" + def _consumer_thread(): + try: + self.consume() + except greenlet.GreenletExit: + return + if not self.consumer_thread: + self.consumer_thread = eventlet.spawn(_consumer_thread) + return self.consumer_thread + + def cancel_consumer_thread(self): + """Cancel a consumer thread""" + if self.consumer_thread: + self.consumer_thread.kill() + try: + self.consumer_thread.wait() + except greenlet.GreenletExit: + pass + self.consumer_thread = None + def publisher_send(self, cls, topic, msg): """Send to a publisher based on the publisher class""" while True: @@ -408,20 +442,20 @@ class Connection(object): except self.connection.connection_errors, e: pass - def direct_consumer(self, topic, callback): + def declare_direct_consumer(self, topic, callback): """Create a 'direct' queue. In nova's use, this is generally a msg_id queue used for responses for call/multicall """ - return self.create_queue(DirectQueue, topic, callback) + self.declare_consumer(DirectConsumer, topic, callback) - def topic_consumer(self, topic, callback=None): - """Create a 'topic' queue.""" - return self.create_queue(TopicQueue, topic, callback) + def declare_topic_consumer(self, topic, callback=None): + """Create a 'topic' consumer.""" + self.declare_consumer(TopicConsumer, topic, callback) - def fanout_consumer(self, topic, callback): - """Create a 'fanout' queue""" - return self.create_queue(FanoutQueue, topic, callback) + def declare_fanout_consumer(self, topic, callback): + """Create a 'fanout' consumer""" + self.declare_consumer(FanoutConsumer, topic, callback) def direct_send(self, msg_id, msg): """Send a 'direct' message""" @@ -638,18 +672,9 @@ def create_connection(new=True): def create_consumer(conn, topic, proxy, fanout=False): """Create a consumer that calls a method in a proxy object""" if fanout: - return conn.fanout_consumer(topic, ProxyCallback(proxy)) + conn.declare_fanout_consumer(topic, ProxyCallback(proxy)) else: - return conn.topic_consumer(topic, ProxyCallback(proxy)) - - -def create_consumer_set(conn, consumers): - # FIXME(comstud): Replace this however necessary - # Returns an object that you can call .wait() on to consume - # all queues? - # Needs to have a .close() which will stop consuming? - # Needs to also have an method for tests? - raise NotImplemented + conn.declare_topic_consumer(topic, ProxyCallback(proxy)) def multicall(context, topic, msg): @@ -666,7 +691,7 @@ def multicall(context, topic, msg): conn = ConnectionContext() wait_msg = MulticallWaiter(conn) - conn.direct_consumer(msg_id, wait_msg) + conn.declare_direct_consumer(msg_id, wait_msg) conn.topic_send(topic, msg) return wait_msg diff --git a/nova/service.py b/nova/service.py index a872a36ee..ab7925eb3 100644 --- a/nova/service.py +++ b/nova/service.py @@ -153,26 +153,17 @@ class Service(object): self.topic) # Share this same connection for these Consumers - consumer_all = rpc.create_consumer(self.conn, self.topic, self, + rpc.create_consumer(self.conn, self.topic, self, fanout=False) node_topic = '%s.%s' % (self.topic, self.host) - consumer_node = rpc.create_consumer(self.conn, node_topic, self, + rpc.create_consumer(self.conn, node_topic, self, fanout=False) - fanout = rpc.create_consumer(self.conn, self.topic, self, fanout=True) + rpc.create_consumer(self.conn, self.topic, self, fanout=True) - consumers = [consumer_all, consumer_node, fanout] - consumer_set = rpc.create_consumer_set(self.conn, consumers) - - # Wait forever, processing these consumers - def _wait(): - try: - consumer_set.wait() - finally: - consumer_set.close() - - self.consumer_set_thread = eventlet.spawn(_wait) + # Consume from all consumers in a thread + self.conn.consume_in_thread() if self.report_interval: pulse = utils.LoopingCall(self.report_state) @@ -237,11 +228,6 @@ class Service(object): logging.warn(_('Service killed that has no database entry')) def stop(self): - self.consumer_set_thread.kill() - try: - self.consumer_set_thread.wait() - except greenlet.GreenletExit: - pass # Try to shut the connection down, but if we get any sort of # errors, go ahead and ignore them.. as we're shutting down anyway try: -- cgit From 431cd5d17780aa7ea9d03b028a78ec4e20b22440 Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 17:37:07 -0700 Subject: always set network_id in virtual_interfaces table, otherwise API commands that show IP addresses get confused --- nova/network/quantum/manager.py | 24 +++++++++++++++++++----- nova/network/quantum/melange_ipam_lib.py | 11 ++++++++--- nova/network/quantum/nova_ipam_lib.py | 2 +- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 932bdd82f..fb13a8496 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -105,14 +105,28 @@ class QuantumManager(manager.FlatManager): project_id) # Create a port via quantum and attach the vif - for (net_id, project_id) in net_proj_pairs: + for (quantum_net_id, project_id) in net_proj_pairs: + + # FIXME: (danwent). We'd like to have the manager be completely + # decoupled from the nova networks table. + # However, other parts of nova sometimes go behind + # our back and access network data directly from the DB. So + # for now, the quantum manager knows that there is a nova + # networks DB table and accesses it here. + # updating the virtual_interfaces table to use UUIDs would + # be one solution, but this would require significant work + # elsewhere. + network_ref = db.network_get_by_uuid(context, quantum_net_id) + vif_rec = manager.FlatManager.add_virtual_interface(self, - context, instance_id, None) + context, instance_id, network_ref['id']) + # talk to Quantum API to create and attach port. q_tenant_id = project_id or FLAGS.quantum_default_tenant_id - self.q_conn.create_and_attach_port(q_tenant_id, net_id, + self.q_conn.create_and_attach_port(q_tenant_id, quantum_net_id, vif_rec['uuid']) - self.ipam.allocate_fixed_ip(context, project_id, net_id, vif_rec) + self.ipam.allocate_fixed_ip(context, project_id, quantum_net_id, + vif_rec) return self.get_instance_nw_info(context, instance_id, instance_type_id, host) @@ -173,7 +187,7 @@ class QuantumManager(manager.FlatManager): if v6_subnet['cidr']: network_dict['cidr_v6'] = v6_subnet['cidr'] info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips] - # TODO(tr3buchet): handle ip6 routes here as well + if v6_subnet['gateway']: info['gateway6'] = v6_subnet['gateway'] diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index cfdcde045..e2e09f139 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -50,11 +50,16 @@ class QuantumMelangeIPAMLib: project_id=tenant_id, dns1=dns1, dns2=dns2) - # create a entry in the network table just to store - # the priority order for this network + # create a entry in the network table, even though + # most data is stored in melange. This is used to + # store data not kept by melange (e.g., priority) + # and to 'fake' other parts of nova (e.g., the API) + # until we get get all accesses to be via the + # network manager API. net = {"uuid": quantum_net_id, "project_id": project_id, - "priority": priority} + "priority": priority, + "label": label} network = self.db.network_create_safe(context, net) def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 5bca9d024..6e7e5d244 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -55,7 +55,7 @@ class QuantumNovaIPAMLib: if len(networks) != 1: raise Exception("Error creating network entry") - # now grab the network and update uuid, project_id, priority + # now grab the network and update additional fields network = networks[0] net = {"project_id": tenant_id, "priority": priority, -- cgit From 32943729861ba4ad562e899a55af77b7974af8db Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Sun, 28 Aug 2011 18:17:21 -0700 Subject: fix test_rpc and kombu stuff --- nova/rpc/FIXME | 2 -- nova/rpc/__init__.py | 7 +------ nova/rpc/impl_carrot.py | 56 +++++++++++++++++++++++++++++++++++++++++-------- nova/rpc/impl_kombu.py | 21 ++++++++++++++----- nova/tests/test_rpc.py | 25 +++++++++++++--------- 5 files changed, 79 insertions(+), 32 deletions(-) delete mode 100644 nova/rpc/FIXME diff --git a/nova/rpc/FIXME b/nova/rpc/FIXME deleted file mode 100644 index 704081802..000000000 --- a/nova/rpc/FIXME +++ /dev/null @@ -1,2 +0,0 @@ -Move some code duplication between carrot/kombu into common.py -The other FIXMEs in __init__.py and impl_kombu.py diff --git a/nova/rpc/__init__.py b/nova/rpc/__init__.py index 9371c2ab3..10b69c8b5 100644 --- a/nova/rpc/__init__.py +++ b/nova/rpc/__init__.py @@ -38,18 +38,13 @@ RPCIMPL = import_object(impl_table.get(FLAGS.rpc_backend, def create_connection(new=True): - return RPCIMPL.Connection.instance(new=True) + return RPCIMPL.create_connection(new=new) def create_consumer(conn, topic, proxy, fanout=False): return RPCIMPL.create_consumer(conn, topic, proxy, fanout) -def create_consumer_set(conn, consumers): - # FIXME(comstud): replace however necessary - return RPCIMPL.ConsumerSet(connection=conn, consumer_list=consumers) - - def call(context, topic, msg): return RPCIMPL.call(context, topic, msg) diff --git a/nova/rpc/impl_carrot.py b/nova/rpc/impl_carrot.py index 40097e10e..efff788a0 100644 --- a/nova/rpc/impl_carrot.py +++ b/nova/rpc/impl_carrot.py @@ -33,6 +33,7 @@ import uuid from carrot import connection as carrot_connection from carrot import messaging +import eventlet from eventlet import greenpool from eventlet import pools from eventlet import queue @@ -42,10 +43,10 @@ from nova import context from nova import exception from nova import fakerabbit from nova import flags -from nova import log as logging -from nova import utils from nova.rpc.common import RemoteError, LOG +# Needed for tests +eventlet.monkey_patch() FLAGS = flags.FLAGS flags.DEFINE_integer('rpc_thread_pool_size', 1024, @@ -57,6 +58,11 @@ flags.DEFINE_integer('rpc_conn_pool_size', 30, class Connection(carrot_connection.BrokerConnection): """Connection instance object.""" + def __init__(self, *args, **kwargs): + super(Connection, self).__init__(*args, **kwargs) + self._rpc_consumers = [] + self._rpc_consumer_thread = None + @classmethod def instance(cls, new=True): """Returns the instance.""" @@ -94,6 +100,42 @@ class Connection(carrot_connection.BrokerConnection): pass return cls.instance() + def close(self): + self.cancel_consumer_thread() + for consumer in self._rpc_consumers: + try: + consumer.close() + except Exception: + # ignore all errors + pass + self._rpc_consumers = [] + super(Connection, self).close() + + def consume_in_thread(self): + """Consumer from all queues/consumers in a greenthread""" + + consumer_set = ConsumerSet(connection=self, + consumer_list=self._rpc_consumers) + + def _consumer_thread(): + try: + consumer_set.wait() + except greenlet.GreenletExit: + return + if not self._rpc_consumer_thread: + self._rpc_consumer_thread = eventlet.spawn(_consumer_thread) + return self._rpc_consumer_thread + + def cancel_consumer_thread(self): + """Cancel a consumer thread""" + if self._rpc_consumer_thread: + self._rpc_consumer_thread.kill() + try: + self._rpc_consumer_thread.wait() + except greenlet.GreenletExit: + pass + self._rpc_consumer_thread = None + class Pool(pools.Pool): """Class that implements a Pool of Connections.""" @@ -119,6 +161,7 @@ class Consumer(messaging.Consumer): """ def __init__(self, *args, **kwargs): + connection = kwargs.get('connection') max_retries = FLAGS.rabbit_max_retries sleep_time = FLAGS.rabbit_retry_interval tries = 0 @@ -148,6 +191,7 @@ class Consumer(messaging.Consumer): LOG.error(_('Unable to connect to AMQP server ' 'after %(tries)d tries. Shutting down.') % locals()) sys.exit(1) + connection._rpc_consumers.append(self) def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): """Wraps the parent fetch with some logic for failed connection.""" @@ -175,12 +219,6 @@ class Consumer(messaging.Consumer): LOG.exception(_('Failed to fetch message from queue: %s' % e)) self.failed_connection = True - def attach_to_eventlet(self): - """Only needed for unit tests!""" - timer = utils.LoopingCall(self.fetch, enable_callbacks=True) - timer.start(0.1) - return timer - class AdapterConsumer(Consumer): """Calls methods on a proxy object based on method and args.""" @@ -251,7 +289,7 @@ class AdapterConsumer(Consumer): # NOTE(vish): this iterates through the generator list(rval) except Exception as e: - logging.exception('Exception during message handling') + LOG.exception('Exception during message handling') if msg_id: msg_reply(msg_id, None, sys.exc_info()) return diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index 01871606c..bd83bc520 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -14,9 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -from nova import flags -from nova.rpc.common import RemoteError, LOG - import kombu import kombu.entity import kombu.messaging @@ -24,8 +21,22 @@ import kombu.connection import itertools import sys import time +import traceback +import types import uuid +import eventlet +from eventlet import greenpool +from eventlet import pools +import greenlet + +from nova import context +from nova import exception +from nova import flags +from nova.rpc.common import RemoteError, LOG + +# Needed for tests +eventlet.monkey_patch() FLAGS = flags.FLAGS @@ -317,7 +328,7 @@ class Connection(object): pass time.sleep(1) self.connection = kombu.connection.Connection(**self.params) - self.queue_num = itertools.count(1) + self.consumer_num = itertools.count(1) try: self.connection.ensure_connection(errback=self.connect_error, @@ -634,7 +645,7 @@ class RpcContext(context.RequestContext): class MulticallWaiter(object): def __init__(self, connection): self._connection = connection - self._iterator = connection.consume() + self._iterator = connection.iterconsume() self._result = None self._done = False diff --git a/nova/tests/test_rpc.py b/nova/tests/test_rpc.py index ba9c0a859..2b9922491 100644 --- a/nova/tests/test_rpc.py +++ b/nova/tests/test_rpc.py @@ -33,13 +33,17 @@ class RpcTestCase(test.TestCase): super(RpcTestCase, self).setUp() self.conn = rpc.create_connection(True) self.receiver = TestReceiver() - self.consumer = rpc.create_consumer(self.conn, - 'test', - self.receiver, - False) - self.consumer.attach_to_eventlet() + rpc.create_consumer(self.conn, + 'test', + self.receiver, + False) + self.conn.consume_in_thread() self.context = context.get_admin_context() + def tearDown(self): + self.conn.close() + super(RpcTestCase, self).tearDown() + def test_call_succeed(self): value = 42 result = rpc.call(self.context, 'test', {"method": "echo", @@ -139,16 +143,17 @@ class RpcTestCase(test.TestCase): nested = Nested() conn = rpc.create_connection(True) - consumer = rpc.create_consumer(conn, - 'nested', - nested, - False) - consumer.attach_to_eventlet() + rpc.create_consumer(conn, + 'nested', + nested, + False) + conn.consume_in_thread() value = 42 result = rpc.call(self.context, 'nested', {"method": "echo", "args": {"queue": "test", "value": value}}) + conn.close() self.assertEqual(value, result) -- cgit From e5310d666f167efe6e3c9f97176d13801489fdfd Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Sun, 28 Aug 2011 18:18:40 -0700 Subject: fix nova-ajax-console-proxy --- bin/nova-ajax-console-proxy | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bin/nova-ajax-console-proxy b/bin/nova-ajax-console-proxy index 0a789b4b9..b3205ec56 100755 --- a/bin/nova-ajax-console-proxy +++ b/bin/nova-ajax-console-proxy @@ -113,11 +113,11 @@ class AjaxConsoleProxy(object): AjaxConsoleProxy.tokens[kwargs['token']] = \ {'args': kwargs, 'last_activity': time.time()} - conn = rpc.create_connection(new=True) - consumer = rpc.create_consumer( - conn, - FLAGS.ajax_console_proxy_topic, - TopicProxy) + self.conn = rpc.create_connection(new=True) + rpc.create_consumer( + self.conn, + FLAGS.ajax_console_proxy_topic, + TopicProxy) def delete_expired_tokens(): now = time.time() @@ -129,7 +129,7 @@ class AjaxConsoleProxy(object): for k in to_delete: del AjaxConsoleProxy.tokens[k] - utils.LoopingCall(consumer.fetch, enable_callbacks=True).start(0.1) + self.conn.consume_in_thread() utils.LoopingCall(delete_expired_tokens).start(1) if __name__ == '__main__': @@ -142,3 +142,4 @@ if __name__ == '__main__': server = wsgi.Server("AJAX Console Proxy", acp, port=acp_port) service.serve(server) service.wait() + self.conn.close() -- cgit From da11af2893719677a9113ce391d37b0dada6585c Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Sun, 28 Aug 2011 18:19:41 -0700 Subject: fix nova/tests/test_test.py --- nova/tests/test_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/tests/test_test.py b/nova/tests/test_test.py index 64f11fa45..6075abbb0 100644 --- a/nova/tests/test_test.py +++ b/nova/tests/test_test.py @@ -40,6 +40,6 @@ class IsolationTestCase(test.TestCase): connection = rpc.create_connection(new=True) proxy = NeverCalled() - consumer = rpc.create_consumer(connection, 'compute', - proxy, fanout=False) - consumer.attach_to_eventlet() + rpc.create_consumer(connection, 'compute', + proxy, fanout=False) + connection.consume_in_thread() -- cgit From 822d92ed1f6a5f2f0951c5e43be6ce0c8fb75e65 Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 19:12:43 -0700 Subject: remove fake IPAM lib, since qmanager must now access nova DB directly --- nova/network/quantum/fake.py | 124 ------------------------------------------- nova/tests/test_quantum.py | 8 --- 2 files changed, 132 deletions(-) diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index 3f6996e63..00cdd8e08 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -91,127 +91,3 @@ class FakeQuantumClientConnection: return (net_id, port_id) return (None, None) - - -def get_ipam_lib(net_man): - return FakeQuantumIPAMLib() - - -class FakeQuantumIPAMLib(): - - def __init__(self): - self.subnets = {} - - def create_subnet(self, context, label, tenant_id, quantum_net_id, - priority, cidr=None, gateway_v6=None, - cidr_v6=None, dns1=None, dns2=None): - if int(cidr.split("/")[1]) != 24: - raise Exception("fake ipam_lib only supports /24s") - v4_ips = [] - net_start = cidr[0:cidr.rfind(".") + 1] - subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) - for i in xrange(2, subnet_size - 1): - v4_ips.append({"ip": net_start + str(i), - "allocated": False, - "virtual_interface_id": None, - "instance_id": None}) - self.subnets[quantum_net_id] = {\ - "label": label, - "priority": priority, - "gateway": net_start + "1", - "netmask": "255.255.255.0", - "broadcast": net_start + "255", - "cidr": cidr, - "gateway_v6": gateway_v6, - "cidr_v6": cidr_v6, - "dns1": dns1, - "dns2": dns2, - "project_id": tenant_id, - "v4_ips": v4_ips} - - def get_network_id_by_cidr(self, context, cidr, project_id): - for net_id, s in self.subnets.items(): - if s['cidr'] == cidr or s['cidr_v6'] == cidr: - return net_id - return None - - def delete_subnets_by_net_id(self, context, net_id, project_id): - self.verify_subnet_exists(context, project_id, net_id) - del self.subnets[net_id] - - def get_project_and_global_net_ids(self, context, project_id): - net_list = [] - id_priority_map = {} - for nid, s in self.subnets.items(): - if s['project_id'] == project_id or \ - s['project_id'] == None: - net_list.append((nid, s['project_id'])) - id_priority_map[nid] = s['priority'] - return sorted(net_list, key=lambda x: id_priority_map[x[0]]) - - def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): - subnet = self.subnets[quantum_net_id] - for i in xrange(0, len(subnet['v4_ips'])): - ip = subnet['v4_ips'][i] - if not ip['allocated']: - subnet['v4_ips'][i] = {'ip': ip['ip'], - 'allocated': True, - 'virtual_interface_id': vif_rec['uuid'], - 'instance_id': vif_rec['instance_id']} - return - raise Exception("Unable to find available IP for net '%s'" %\ - quantum_net_id) - - def get_subnets_by_net_id(self, context, tenant_id, net_id): - self.verify_subnet_exists(context, tenant_id, net_id) - - subnet_data = self.subnets[net_id] - subnet_data_v4 = { - 'network_id': net_id, - 'cidr': subnet_data['cidr'], - 'gateway': subnet_data['gateway'], - 'broadcast': subnet_data['broadcast'], - 'netmask': subnet_data['netmask'], - 'dns1': subnet_data['dns1'], - 'dns2': subnet_data['dns2']} - subnet_data_v6 = { - 'network_id': net_id, - 'cidr': subnet_data['cidr_v6'], - 'gateway': subnet_data['gateway_v6'], - 'broadcast': None, - 'netmask': None, - 'dns1': None, - 'dns2': None} - return (subnet_data_v4, subnet_data_v6) - - def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): - self.verify_subnet_exists(context, project_id, net_id) - - subnet_data = self.subnets[net_id] - if subnet_data['cidr_v6']: - ip = ipv6.to_global(subnet_data['cidr_v6'], - "ca:fe:de:ad:be:ef", - project_id) - return [ip] - return [] - - def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): - self.verify_subnet_exists(context, project_id, net_id) - - subnet_data = self.subnets[net_id] - for ip in subnet_data['v4_ips']: - if ip['virtual_interface_id'] == vif_id: - return [ip['ip']] - return [] - - def verify_subnet_exists(self, context, tenant_id, quantum_net_id): - if quantum_net_id not in self.subnets: - raise exception.NetworkNotFound(network_id=quantum_net_id) - - def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): - s = self.subnets[net_id] - for ip in s['v4_ips']: - if ip['virtual_interface_id'] == vif_ref['id']: - ip['allocated'] = False - ip['instance_id'] = None - ip['virtual_interface_id'] = None diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index ae9900b67..e7a8a01fa 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -224,14 +224,6 @@ class QuantumTestCaseBase(object): self.net_man.validate_networks, ctx, [("", None)]) -class QuantumFakeIPAMTestCase(QuantumTestCaseBase, test.TestCase): - - def setUp(self): - super(QuantumFakeIPAMTestCase, self).setUp() - self.net_man = quantum_manager.QuantumManager( \ - ipam_lib="nova.network.quantum.fake") - - class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): def setUp(self): -- cgit From 716303049eaee59841ca4679d73ecb4e5be52cfd Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 19:13:02 -0700 Subject: add doc-strings for all major modules --- nova/network/quantum/manager.py | 97 +++++++++++++++++++++++++----- nova/network/quantum/melange_ipam_lib.py | 81 ++++++++++++++++++------- nova/network/quantum/nova_ipam_lib.py | 85 ++++++++++++++++++-------- nova/network/quantum/quantum_connection.py | 28 ++++++++- 4 files changed, 226 insertions(+), 65 deletions(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index fb13a8496..a002a3d7b 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -35,8 +35,27 @@ flags.DEFINE_string('quantum_ipam_lib', class QuantumManager(manager.FlatManager): + """ NetworkManager class that communicates with a Quantum service + via a web services API to provision VM network connectivity. + + For IP Address management, QuantumManager can be configured to + use either Nova's local DB or the Melange IPAM service. + + Currently, the QuantumManager does NOT support any of the 'gateway' + functionality implemented by the Nova VlanManager, including: + * floating IPs + * DHCP + * NAT gateway + + Support for these capabilities are targted for future releases. + """ def __init__(self, ipam_lib=None, *args, **kwargs): + """ Initialize two key libraries, the connection to a + Quantum service, and the library for implementing IPAM. + + Calls inherited FlatManager constructor. + """ if FLAGS.fake_network: self.q_conn = fake.FakeQuantumClientConnection() @@ -53,6 +72,17 @@ class QuantumManager(manager.FlatManager): network_size, cidr_v6, gateway_v6, bridge, bridge_interface, dns1=None, dns2=None, uuid=None, **kwargs): + """ Unlike other NetworkManagers, with QuantumManager, each + create_networks calls should create only a single network. + + Two scenarios exist: + - no 'uuid' is specified, in which case we contact + Quantum and create a new network. + - an existing 'uuid' is specified, corresponding to + a Quantum network created out of band. + + In both cases, we initialize a subnet using the IPAM lib. + """ if num_networks != 1: raise Exception("QuantumManager requires that only one" " network is created per call") @@ -74,27 +104,46 @@ class QuantumManager(manager.FlatManager): priority, cidr, gateway_v6, cidr_v6, dns1, dns2) def delete_network(self, context, fixed_range): + """ Lookup network by IPv4 cidr, delete both the IPAM + subnet and the corresponding Quantum network. + """ project_id = context.project_id quantum_net_id = self.ipam.get_network_id_by_cidr( context, fixed_range, project_id) self.ipam.delete_subnets_by_net_id(context, quantum_net_id, project_id) - try: - q_tenant_id = project_id or FLAGS.quantum_default_tenant_id - self.q_conn.delete_network(q_tenant_id, quantum_net_id) - except Exception, e: - raise Exception("Unable to delete Quantum Network with " - "fixed_range = %s (ports still in use?)." % fixed_range) + q_tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.q_conn.delete_network(q_tenant_id, quantum_net_id) def allocate_for_instance(self, context, **kwargs): + """ Called by compute when it is creating a new VM. + + There are three key tasks: + - Determine the number and order of vNICs to create + - Allocate IP addresses + - Create ports on a Quantum network and attach vNICs. + + We support two approaches to determining vNICs: + - By default, a VM gets a vNIC for any network belonging + to the VM's project, and a vNIC for any "global" network + that has a NULL project_id. vNIC order is determined + by the network's 'priority' field. + - If the 'os-create-server-ext' was used to create the VM, + only the networks in 'requested_networks' are used to + create vNICs, and the vNIC order is determiend by the + order in the requested_networks array. + + For each vNIC, use the FlatManager to create the entries + in the virtual_interfaces table, contact Quantum to + create a port and attachment the vNIC, and use the IPAM + lib to allocate IP addresses. + """ instance_id = kwargs.pop('instance_id') instance_type_id = kwargs['instance_type_id'] host = kwargs.pop('host') project_id = kwargs.pop('project_id') LOG.debug(_("network allocations for instance %s"), instance_id) - # if using the create-server-networks extension, 'requested_networks' - # will be defined, otherwise, just grab relevant nets from IPAM requested_networks = kwargs.get('requested_networks') if requested_networks: @@ -116,10 +165,12 @@ class QuantumManager(manager.FlatManager): # updating the virtual_interfaces table to use UUIDs would # be one solution, but this would require significant work # elsewhere. - network_ref = db.network_get_by_uuid(context, quantum_net_id) + admin_context = context.elevated() + network_ref = db.network_get_by_uuid(admin_context, + quantum_net_id) vif_rec = manager.FlatManager.add_virtual_interface(self, - context, instance_id, network_ref['id']) + context, instance_id, network_ref['id']) # talk to Quantum API to create and attach port. q_tenant_id = project_id or FLAGS.quantum_default_tenant_id @@ -133,6 +184,18 @@ class QuantumManager(manager.FlatManager): def get_instance_nw_info(self, context, instance_id, instance_type_id, host): + """ This method is used by compute to fetch all network data + that should be used when creating the VM. + + The method simply loops through all virtual interfaces + stored in the nova DB and queries the IPAM lib to get + the associated IP data. + + The format of returned data is 'defined' by the initial + set of NetworkManagers found in nova/network/manager.py . + Ideally this 'interface' will be more formally defined + in the future. + """ network_info = [] instance = db.instance_get(context, instance_id) project_id = instance.project_id @@ -202,6 +265,11 @@ class QuantumManager(manager.FlatManager): return network_info def deallocate_for_instance(self, context, **kwargs): + """ Called when a VM is terminated. Loop through each virtual + interface in the Nova DB and remove the Quantum port and + clear the IP allocation using the IPAM. Finally, remove + the virtual interfaces from the Nova DB. + """ instance_id = kwargs.get('instance_id') project_id = kwargs.pop('project_id', None) @@ -234,11 +302,12 @@ class QuantumManager(manager.FlatManager): self._do_trigger_security_group_members_refresh_for_instance( instance_id) - # validates that this tenant has quantum networks with the associated - # UUIDs. This is called by the 'os-create-server-ext' API extension - # code so that we can return an API error code to the caller if they - # request an invalid network. def validate_networks(self, context, networks): + """ Validates that this tenant has quantum networks with the associated + UUIDs. This is called by the 'os-create-server-ext' API extension + code so that we can return an API error code to the caller if they + request an invalid network. + """ if networks is None: return diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index e2e09f139..7b7baf281 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -32,43 +32,54 @@ def get_ipam_lib(net_man): class QuantumMelangeIPAMLib: + """ Implements Quantum IP Address Management (IPAM) interface + using the Melange service, which is access using the Melange + web services API. + """ def __init__(self): + """ Initialize class used to connect to Melange server""" self.m_conn = melange_connection.MelangeConnection() def create_subnet(self, context, label, project_id, quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): - tenant_id = project_id or FLAGS.quantum_default_tenant_id - if cidr: - self.m_conn.create_block(quantum_net_id, cidr, + """ Contact Melange and create a subnet for any non-NULL + IPv4 or IPv6 subnets. + + Also create a entry in the Nova networks DB, but only + to store values not represented in Melange or to + temporarily provide compatibility with Nova code that + accesses IPAM data directly via the DB (e.g., nova-api) + """ + tenant_id = project_id or FLAGS.quantum_default_tenant_id + if cidr: + self.m_conn.create_block(quantum_net_id, cidr, project_id=tenant_id, dns1=dns1, dns2=dns2) - if cidr_v6: - self.m_conn.create_block(quantum_net_id, cidr_v6, + if cidr_v6: + self.m_conn.create_block(quantum_net_id, cidr_v6, project_id=tenant_id, dns1=dns1, dns2=dns2) - # create a entry in the network table, even though - # most data is stored in melange. This is used to - # store data not kept by melange (e.g., priority) - # and to 'fake' other parts of nova (e.g., the API) - # until we get get all accesses to be via the - # network manager API. - net = {"uuid": quantum_net_id, + net = {"uuid": quantum_net_id, "project_id": project_id, "priority": priority, "label": label} - network = self.db.network_create_safe(context, net) + network = self.db.network_create_safe(context, net) def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): + """ Pass call to allocate fixed IP on to Melange""" tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.allocate_ip(quantum_net_id, vif_ref['uuid'], project_id=tenant_id, mac_address=vif_ref['address']) def get_network_id_by_cidr(self, context, cidr, project_id): + """ Find the Quantum UUID associated with a IPv4 CIDR + address for the specified tenant. + """ tenant_id = project_id or FLAGS.quantum_default_tenant_id all_blocks = self.m_conn.get_blocks(tenant_id) for b in all_blocks['ip_blocks']: @@ -77,6 +88,9 @@ class QuantumMelangeIPAMLib: raise Exception("No network found for cidr %s" % cidr) def delete_subnets_by_net_id(self, context, net_id, project_id): + """ Find Melange block associated with the Quantum UUID, + then tell Melange to delete that block. + """ admin_context = context.elevated() tenant_id = project_id or FLAGS.quantum_default_tenant_id all_blocks = self.m_conn.get_blocks(tenant_id) @@ -88,9 +102,11 @@ class QuantumMelangeIPAMLib: if network is not None: db.network_delete_safe(context, network['id']) - # get all networks with this project_id, as well as all networks - # where the project-id is not set (these are shared networks) def get_project_and_global_net_ids(self, context, project_id): + """ Fetches all networks associated with this project, or + that are "global" (i.e., have no project set). + Returns list sorted by 'priority'. + """ admin_context = context.elevated() id_proj_map = {} if project_id is None: @@ -115,12 +131,16 @@ class QuantumMelangeIPAMLib: return sorted(id_priority_map.items(), key=lambda x: id_priority_map[x[0]]) - # FIXME: (danwent) Melange actually returns the subnet info - # when we query for a particular interface. we may want to - # reworks the ipam_manager python API to let us take advantage of - # this, as right now we have to get all blocks and cycle through - # them. def get_subnets_by_net_id(self, context, project_id, net_id): + """ Returns information about the IPv4 and IPv6 subnets + associated with a Quantum Network UUID. + """ + + # FIXME: (danwent) Melange actually returns the subnet info + # when we query for a particular interface. we may want to + # reworks the ipam_manager python API to let us take advantage of + # this, as right now we have to get all blocks and cycle through + # them. subnet_v4 = None subnet_v6 = None tenant_id = project_id or FLAGS.quantum_default_tenant_id @@ -142,26 +162,41 @@ class QuantumMelangeIPAMLib: return (subnet_v4, subnet_v6) def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): - return self.get_ips_by_interface(context, net_id, vif_id, + """ Returns a list of IPv4 address strings associated with + the specified virtual interface. + """ + return self._get_ips_by_interface(context, net_id, vif_id, project_id, 4) def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): - return self.get_ips_by_interface(context, net_id, vif_id, + """ Returns a list of IPv6 address strings associated with + the specified virtual interface. + """ + return self._get_ips_by_interface(context, net_id, vif_id, project_id, 6) - def get_ips_by_interface(self, context, net_id, vif_id, project_id, + def _get_ips_by_interface(self, context, net_id, vif_id, project_id, ip_version): + """ Helper method to fetch v4 or v6 addresses for a particular + virtual interface. + """ tenant_id = project_id or FLAGS.quantum_default_tenant_id ip_list = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id) return [ip['address'] for ip in ip_list \ if IPNetwork(ip['address']).version == ip_version] def verify_subnet_exists(self, context, project_id, quantum_net_id): + """ Confirms that a subnet exists that is associated with the + specified Quantum Network UUID. + """ tenant_id = project_id or FLAGS.quantum_default_tenant_id v4_subnet, v6_subnet = self.get_subnets_by_net_id(context, tenant_id, quantum_net_id) return v4_subnet is not None def deallocate_ips_by_vif(self, context, project_id, net_id, vif_ref): + """ Deallocate all fixed IPs associated with the specified + virtual interface. + """ tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.deallocate_ips(net_id, vif_ref['uuid'], tenant_id) diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 6e7e5d244..ce7d3efcb 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -37,52 +37,69 @@ def get_ipam_lib(net_man): class QuantumNovaIPAMLib: + """ Implements Quantum IP Address Management (IPAM) interface + using the local Nova database. This implementation is inline + with how IPAM is used by other NetworkManagers. + """ def __init__(self, net_manager): + """ Holds a reference to the "parent" network manager, used + to take advantage of various FlatManager methods to avoid + code duplication. + """ self.net_manager = net_manager def create_subnet(self, context, label, tenant_id, quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): - admin_context = context.elevated() - subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) - networks = manager.FlatManager.create_networks(self.net_manager, + """ Re-use the basic FlatManager create_networks method to + initialize the networks and fixed_ips tables in Nova DB. + + Also stores a few more fields in the networks table that + are needed by Quantum but not the FlatManager. + """ + admin_context = context.elevated() + subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) + networks = manager.FlatManager.create_networks(self.net_manager, admin_context, label, cidr, False, 1, subnet_size, cidr_v6, gateway_v6, quantum_net_id, None, dns1, dns2) - if len(networks) != 1: - raise Exception("Error creating network entry") + if len(networks) != 1: + raise Exception("Error creating network entry") - # now grab the network and update additional fields - network = networks[0] - net = {"project_id": tenant_id, + network = networks[0] + net = {"project_id": tenant_id, "priority": priority, "uuid": quantum_net_id} - db.network_update(admin_context, network['id'], net) + db.network_update(admin_context, network['id'], net) def get_network_id_by_cidr(self, context, cidr, project_id): - admin_context = context.elevated() - network = db.network_get_by_cidr(admin_context, cidr) - if not network: - raise Exception("No network with fixed_range = %s" \ - % fixed_range) - return network['uuid'] + """ Grabs Quantum network UUID based on IPv4 CIDR. """ + admin_context = context.elevated() + network = db.network_get_by_cidr(admin_context, cidr) + if not network: + raise Exception("No network with fixed_range = %s" % fixed_range) + return network['uuid'] def delete_subnets_by_net_id(self, context, net_id, project_id): - admin_context = context.elevated() - network = db.network_get_by_uuid(admin_context, net_id) - if not network: - raise Exception("No network with net_id = %s" % net_id) - manager.FlatManager.delete_network(self.net_manager, + """ Deletes a network based on Quantum UUID. Uses FlatManager + delete_network to avoid duplication. + """ + admin_context = context.elevated() + network = db.network_get_by_uuid(admin_context, net_id) + if not network: + raise Exception("No network with net_id = %s" % net_id) + manager.FlatManager.delete_network(self.net_manager, admin_context, network['cidr'], require_disassociated=False) def get_project_and_global_net_ids(self, context, project_id): - - # get all networks with this project_id, as well as all networks - # where the project-id is not set (these are shared networks) + """ Fetches all networks associated with this project, or + that are "global" (i.e., have no project set). + Returns list sorted by 'priority'. + """ admin_context = context.elevated() networks = db.project_get_networks(admin_context, project_id, False) networks.extend(db.project_get_networks(admin_context, None, False)) @@ -95,6 +112,8 @@ class QuantumNovaIPAMLib: return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): + """ Allocates a single fixed IPv4 address for a virtual interface. + """ admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, quantum_net_id) if network['cidr']: @@ -106,6 +125,9 @@ class QuantumNovaIPAMLib: db.fixed_ip_update(admin_context, address, values) def get_subnets_by_net_id(self, context, tenant_id, net_id): + """ Returns information about the IPv4 and IPv6 subnets + associated with a Quantum Network UUID. + """ n = db.network_get_by_uuid(context.elevated(), net_id) subnet_data_v4 = { 'network_id': n['uuid'], @@ -126,12 +148,18 @@ class QuantumNovaIPAMLib: return (subnet_data_v4, subnet_data_v6) def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): + """ Returns a list of IPv4 address strings associated with + the specified virtual interface, based on the fixed_ips table. + """ vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) fixed_ips = db.fixed_ip_get_by_virtual_interface(context, vif_rec['id']) return [f['address'] for f in fixed_ips] def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): + """ Returns a list containing a single IPv6 address strings + associated with the specified virtual interface. + """ admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, net_id) vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) @@ -143,10 +171,17 @@ class QuantumNovaIPAMLib: return [] def verify_subnet_exists(self, context, tenant_id, quantum_net_id): + """ Confirms that a subnet exists that is associated with the + specified Quantum Network UUID. Raises an exception if no + such subnet exists. + """ admin_context = context.elevated() - network = db.network_get_by_uuid(admin_context, quantum_net_id) + db.network_get_by_uuid(admin_context, quantum_net_id) def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): + """ Deallocate all fixed IPs associated with the specified + virtual interface. + """ try: admin_context = context.elevated() fixed_ips = db.fixed_ip_get_by_virtual_interface(admin_context, @@ -156,5 +191,5 @@ class QuantumNovaIPAMLib: {'allocated': False, 'virtual_interface_id': None}) except exception.FixedIpNotFoundForInstance: - LOG.error(_('Failed to deallocate fixed IP for vif %s' % \ + LOG.error(_('No fixed IPs to deallocate for vif %s' % \ vif_ref['id'])) diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index a13867af2..bd3592c2c 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -38,31 +38,49 @@ flags.DEFINE_string('quantum_default_tenant_id', class QuantumClientConnection: + """ Abstracts connection to Quantum service into higher level + operations performed by the QuantumManager. + + Separating this out as a class also let's us create a 'fake' + version of this class for unit tests. + """ def __init__(self): + """ Initialize Quantum client class based on flags. """ self.client = quantum_client.Client(FLAGS.quantum_connection_host, FLAGS.quantum_connection_port, format="json", logger=LOG) def create_network(self, tenant_id, network_name): + """ Create network using specified name, return Quantum + network UUID. + """ data = {'network': {'name': network_name}} resdict = self.client.create_network(data, tenant=tenant_id) return resdict["network"]["id"] def delete_network(self, tenant_id, net_id): + """ Deletes Quantum network with specified UUID. """ self.client.delete_network(net_id, tenant=tenant_id) def network_exists(self, tenant_id, net_id): + """ Determine if a Quantum network exists for the + specified tenant. + """ try: self.client.show_network_details(net_id, tenant=tenant_id) except: - # FIXME: client lib should expose more granular exceptions + # FIXME: (danwent) client lib should expose granular exceptions # so we can confirm we're getting a 404 and not some other error return False return True def create_and_attach_port(self, tenant_id, net_id, interface_id): + """ Creates a Quantum port on the specified network, sets + status to ACTIVE to enable traffic, and attaches the + vNIC with the specified interface-id. + """ LOG.debug("Connecting interface %s to net %s for %s" % \ (interface_id, net_id, tenant_id)) port_data = {'port': {'state': 'ACTIVE'}} @@ -74,15 +92,19 @@ class QuantumClientConnection: tenant=tenant_id) def detach_and_delete_port(self, tenant_id, net_id, port_id): + """ Detach and delete the specified Quantum port. """ LOG.debug("Deleting port %s on net %s for %s" % \ (port_id, net_id, tenant_id)) self.client.detach_resource(net_id, port_id, tenant=tenant_id) self.client.delete_port(net_id, port_id, tenant=tenant_id) - # FIXME: (danwent) this will be inefficient until API implements querying def get_port_by_attachment(self, tenant_id, attachment_id): - + """ Given a tenant, search for the Quantum network and port + UUID that has the specified interface-id attachment. + """ + # FIXME: (danwent) this will be inefficient until the Quantum + # API implements querying a port by the interface-id net_list_resdict = self.client.list_networks(tenant=tenant_id) for n in net_list_resdict["networks"]: net_id = n['id'] -- cgit From 6fbb35d596f670d6dcdda2486a12fc09ef9be853 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Sun, 28 Aug 2011 19:22:53 -0700 Subject: add carrot/kombu tests... small thread fix for kombu --- nova/rpc/__init__.py | 4 +- nova/rpc/common.py | 6 + nova/rpc/impl_carrot.py | 4 - nova/rpc/impl_kombu.py | 12 +- nova/tests/test_rpc_amqp.py | 88 -------------- nova/tests/test_rpc_carrot.py | 202 ++++++++++++++++++++++++++++++++ nova/tests/test_rpc_kombu.py | 266 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 481 insertions(+), 101 deletions(-) delete mode 100644 nova/tests/test_rpc_amqp.py create mode 100644 nova/tests/test_rpc_carrot.py create mode 100644 nova/tests/test_rpc_kombu.py diff --git a/nova/rpc/__init__.py b/nova/rpc/__init__.py index 10b69c8b5..2a47ba87b 100644 --- a/nova/rpc/__init__.py +++ b/nova/rpc/__init__.py @@ -23,7 +23,7 @@ from nova import flags FLAGS = flags.FLAGS flags.DEFINE_string('rpc_backend', - 'carrot', + 'kombu', "The messaging module to use, defaults to carrot.") impl_table = {'kombu': 'nova.rpc.impl_kombu', @@ -42,7 +42,7 @@ def create_connection(new=True): def create_consumer(conn, topic, proxy, fanout=False): - return RPCIMPL.create_consumer(conn, topic, proxy, fanout) + RPCIMPL.create_consumer(conn, topic, proxy, fanout) def call(context, topic, msg): diff --git a/nova/rpc/common.py b/nova/rpc/common.py index 1d3065a83..b8c280630 100644 --- a/nova/rpc/common.py +++ b/nova/rpc/common.py @@ -1,8 +1,14 @@ from nova import exception +from nova import flags from nova import log as logging LOG = logging.getLogger('nova.rpc') +flags.DEFINE_integer('rpc_thread_pool_size', 1024, + 'Size of RPC thread pool') +flags.DEFINE_integer('rpc_conn_pool_size', 30, + 'Size of RPC connection pool') + class RemoteError(exception.Error): """Signifies that a remote class has raised an exception. diff --git a/nova/rpc/impl_carrot.py b/nova/rpc/impl_carrot.py index efff788a0..07af0a116 100644 --- a/nova/rpc/impl_carrot.py +++ b/nova/rpc/impl_carrot.py @@ -49,10 +49,6 @@ from nova.rpc.common import RemoteError, LOG eventlet.monkey_patch() FLAGS = flags.FLAGS -flags.DEFINE_integer('rpc_thread_pool_size', 1024, - 'Size of RPC thread pool') -flags.DEFINE_integer('rpc_conn_pool_size', 30, - 'Size of RPC connection pool') class Connection(carrot_connection.BrokerConnection): diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index bd83bc520..49bca1d81 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -40,11 +40,6 @@ eventlet.monkey_patch() FLAGS = flags.FLAGS -flags.DEFINE_integer('rpc_conn_pool_size', 30, - 'Size of RPC connection pool') -flags.DEFINE_integer('rpc_thread_pool_size', 1024, - 'Size of RPC thread pool') - class ConsumerBase(object): """Consumer base class.""" @@ -328,6 +323,9 @@ class Connection(object): pass time.sleep(1) self.connection = kombu.connection.Connection(**self.params) + if FLAGS.fake_rabbit: + # Kludge to speed up tests. + self.connection.transport.polling_interval = 0.0 self.consumer_num = itertools.count(1) try: @@ -422,13 +420,13 @@ class Connection(object): self.consume() except greenlet.GreenletExit: return - if not self.consumer_thread: + if self.consumer_thread is None: self.consumer_thread = eventlet.spawn(_consumer_thread) return self.consumer_thread def cancel_consumer_thread(self): """Cancel a consumer thread""" - if self.consumer_thread: + if self.consumer_thread is not None: self.consumer_thread.kill() try: self.consumer_thread.wait() diff --git a/nova/tests/test_rpc_amqp.py b/nova/tests/test_rpc_amqp.py deleted file mode 100644 index 2215a908b..000000000 --- a/nova/tests/test_rpc_amqp.py +++ /dev/null @@ -1,88 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 Openstack, LLC. -# 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. - -""" -Tests For RPC AMQP. -""" - -from nova import context -from nova import log as logging -from nova import rpc -from nova.rpc import amqp -from nova import test - - -LOG = logging.getLogger('nova.tests.rpc') - - -class RpcAMQPTestCase(test.TestCase): - def setUp(self): - super(RpcAMQPTestCase, self).setUp() - self.conn = rpc.create_connection(True) - self.receiver = TestReceiver() - self.consumer = rpc.create_consumer(self.conn, - 'test', - self.receiver, - False) - self.consumer.attach_to_eventlet() - self.context = context.get_admin_context() - - def test_connectionpool_single(self): - """Test that ConnectionPool recycles a single connection.""" - conn1 = amqp.ConnectionPool.get() - amqp.ConnectionPool.put(conn1) - conn2 = amqp.ConnectionPool.get() - amqp.ConnectionPool.put(conn2) - self.assertEqual(conn1, conn2) - - -class TestReceiver(object): - """Simple Proxy class so the consumer has methods to call. - - Uses static methods because we aren't actually storing any state. - - """ - - @staticmethod - def echo(context, value): - """Simply returns whatever value is sent in.""" - LOG.debug(_("Received %s"), value) - return value - - @staticmethod - def context(context, value): - """Returns dictionary version of context.""" - LOG.debug(_("Received %s"), context) - return context.to_dict() - - @staticmethod - def echo_three_times(context, value): - context.reply(value) - context.reply(value + 1) - context.reply(value + 2) - - @staticmethod - def echo_three_times_yield(context, value): - yield value - yield value + 1 - yield value + 2 - - @staticmethod - def fail(context, value): - """Raises an exception with the value sent in.""" - raise Exception(value) diff --git a/nova/tests/test_rpc_carrot.py b/nova/tests/test_rpc_carrot.py new file mode 100644 index 000000000..cf84980ab --- /dev/null +++ b/nova/tests/test_rpc_carrot.py @@ -0,0 +1,202 @@ +# 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. +""" +Unit Tests for remote procedure calls using queue +""" + +from nova import context +from nova import log as logging +from nova.rpc import impl_carrot as rpc +from nova import test + + +LOG = logging.getLogger('nova.tests.rpc') + + +class RpcCarrotTestCase(test.TestCase): + def setUp(self): + super(RpcCarrotTestCase, self).setUp() + self.conn = rpc.create_connection(True) + self.receiver = TestReceiver() + rpc.create_consumer(self.conn, + 'test', + self.receiver, + False) + self.conn.consume_in_thread() + self.context = context.get_admin_context() + + def tearDown(self): + self.conn.close() + super(RpcCarrotTestCase, self).tearDown() + + def test_connectionpool_single(self): + """Test that ConnectionPool recycles a single connection.""" + conn1 = rpc.ConnectionPool.get() + rpc.ConnectionPool.put(conn1) + conn2 = rpc.ConnectionPool.get() + rpc.ConnectionPool.put(conn2) + self.assertEqual(conn1, conn2) + + def test_call_succeed(self): + value = 42 + result = rpc.call(self.context, 'test', {"method": "echo", + "args": {"value": value}}) + self.assertEqual(value, result) + + def test_call_succeed_despite_multiple_returns(self): + value = 42 + result = rpc.call(self.context, 'test', {"method": "echo_three_times", + "args": {"value": value}}) + self.assertEqual(value + 2, result) + + def test_call_succeed_despite_multiple_returns_yield(self): + value = 42 + result = rpc.call(self.context, 'test', + {"method": "echo_three_times_yield", + "args": {"value": value}}) + self.assertEqual(value + 2, result) + + def test_multicall_succeed_once(self): + value = 42 + result = rpc.multicall(self.context, + 'test', + {"method": "echo", + "args": {"value": value}}) + for i, x in enumerate(result): + if i > 0: + self.fail('should only receive one response') + self.assertEqual(value + i, x) + + def test_multicall_succeed_three_times(self): + value = 42 + result = rpc.multicall(self.context, + 'test', + {"method": "echo_three_times", + "args": {"value": value}}) + for i, x in enumerate(result): + self.assertEqual(value + i, x) + + def test_multicall_succeed_three_times_yield(self): + value = 42 + result = rpc.multicall(self.context, + 'test', + {"method": "echo_three_times_yield", + "args": {"value": value}}) + for i, x in enumerate(result): + self.assertEqual(value + i, x) + + def test_context_passed(self): + """Makes sure a context is passed through rpc call.""" + value = 42 + result = rpc.call(self.context, + 'test', {"method": "context", + "args": {"value": value}}) + self.assertEqual(self.context.to_dict(), result) + + def test_call_exception(self): + """Test that exception gets passed back properly. + + rpc.call returns a RemoteError object. The value of the + exception is converted to a string, so we convert it back + to an int in the test. + + """ + value = 42 + self.assertRaises(rpc.RemoteError, + rpc.call, + self.context, + 'test', + {"method": "fail", + "args": {"value": value}}) + try: + rpc.call(self.context, + 'test', + {"method": "fail", + "args": {"value": value}}) + self.fail("should have thrown rpc.RemoteError") + except rpc.RemoteError as exc: + self.assertEqual(int(exc.value), value) + + def test_nested_calls(self): + """Test that we can do an rpc.call inside another call.""" + class Nested(object): + @staticmethod + def echo(context, queue, value): + """Calls echo in the passed queue""" + LOG.debug(_("Nested received %(queue)s, %(value)s") + % locals()) + # TODO: so, it will replay the context and use the same REQID? + # that's bizarre. + ret = rpc.call(context, + queue, + {"method": "echo", + "args": {"value": value}}) + LOG.debug(_("Nested return %s"), ret) + return value + + nested = Nested() + conn = rpc.create_connection(True) + rpc.create_consumer(conn, + 'nested', + nested, + False) + conn.consume_in_thread() + value = 42 + result = rpc.call(self.context, + 'nested', {"method": "echo", + "args": {"queue": "test", + "value": value}}) + conn.close() + self.assertEqual(value, result) + + +class TestReceiver(object): + """Simple Proxy class so the consumer has methods to call. + + Uses static methods because we aren't actually storing any state. + + """ + + @staticmethod + def echo(context, value): + """Simply returns whatever value is sent in.""" + LOG.debug(_("Received %s"), value) + return value + + @staticmethod + def context(context, value): + """Returns dictionary version of context.""" + LOG.debug(_("Received %s"), context) + return context.to_dict() + + @staticmethod + def echo_three_times(context, value): + context.reply(value) + context.reply(value + 1) + context.reply(value + 2) + + @staticmethod + def echo_three_times_yield(context, value): + yield value + yield value + 1 + yield value + 2 + + @staticmethod + def fail(context, value): + """Raises an exception with the value sent in.""" + raise Exception(value) diff --git a/nova/tests/test_rpc_kombu.py b/nova/tests/test_rpc_kombu.py new file mode 100644 index 000000000..457dfdeca --- /dev/null +++ b/nova/tests/test_rpc_kombu.py @@ -0,0 +1,266 @@ +# 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. +""" +Unit Tests for remote procedure calls using queue +""" + +from nova import context +from nova import log as logging +from nova.rpc import impl_kombu as rpc +from nova import test + + +LOG = logging.getLogger('nova.tests.rpc') + + +class RpcKombuTestCase(test.TestCase): + def setUp(self): + super(RpcKombuTestCase, self).setUp() + self.conn = rpc.create_connection() + self.receiver = TestReceiver() + rpc.create_consumer(self.conn, + 'test', + self.receiver, + False) + self.conn.consume_in_thread() + self.context = context.get_admin_context() + + def tearDown(self): + self.conn.close() + super(RpcKombuTestCase, self).tearDown() + + def test_reusing_connection(self): + """Test that reusing a connection returns same one.""" + conn_context = rpc.create_connection(new=False) + conn1 = conn_context.connection + conn_context.close() + conn_context = rpc.create_connection(new=False) + conn2 = conn_context.connection + conn_context.close() + self.assertEqual(conn1, conn2) + + def test_topic_send_receive(self): + """Test sending to a topic exchange/queue""" + + conn = rpc.create_connection() + message = 'topic test message' + + self.received_message = None + + def _callback(message): + self.received_message = message + + conn.declare_topic_consumer('a_topic', _callback) + conn.topic_send('a_topic', message) + conn.consume(limit=1) + conn.close() + + self.assertEqual(self.received_message, message) + + def test_direct_send_receive(self): + """Test sending to a direct exchange/queue""" + conn = rpc.create_connection() + message = 'direct test message' + + self.received_message = None + + def _callback(message): + self.received_message = message + + conn.declare_direct_consumer('a_direct', _callback) + conn.direct_send('a_direct', message) + conn.consume(limit=1) + conn.close() + + self.assertEqual(self.received_message, message) + + @test.skip_test("kombu memory transport seems buggy with fanout queues " + "as this test passes when you use rabbit (fake_rabbit=False)") + def test_fanout_send_receive(self): + """Test sending to a fanout exchange and consuming from 2 queues""" + + conn = rpc.create_connection() + conn2 = rpc.create_connection() + message = 'fanout test message' + + self.received_message = None + + def _callback(message): + self.received_message = message + + conn.declare_fanout_consumer('a_fanout', _callback) + conn2.declare_fanout_consumer('a_fanout', _callback) + conn.fanout_send('a_fanout', message) + + conn.consume(limit=1) + conn.close() + self.assertEqual(self.received_message, message) + + self.received_message = None + conn2.consume(limit=1) + conn2.close() + self.assertEqual(self.received_message, message) + + def test_call_succeed(self): + value = 42 + result = rpc.call(self.context, 'test', {"method": "echo", + "args": {"value": value}}) + self.assertEqual(value, result) + + def test_call_succeed_despite_multiple_returns(self): + value = 42 + result = rpc.call(self.context, 'test', {"method": "echo_three_times", + "args": {"value": value}}) + self.assertEqual(value + 2, result) + + def test_call_succeed_despite_multiple_returns_yield(self): + value = 42 + result = rpc.call(self.context, 'test', + {"method": "echo_three_times_yield", + "args": {"value": value}}) + self.assertEqual(value + 2, result) + + def test_multicall_succeed_once(self): + value = 42 + result = rpc.multicall(self.context, + 'test', + {"method": "echo", + "args": {"value": value}}) + for i, x in enumerate(result): + if i > 0: + self.fail('should only receive one response') + self.assertEqual(value + i, x) + + def test_multicall_succeed_three_times(self): + value = 42 + result = rpc.multicall(self.context, + 'test', + {"method": "echo_three_times", + "args": {"value": value}}) + for i, x in enumerate(result): + self.assertEqual(value + i, x) + + def test_multicall_succeed_three_times_yield(self): + value = 42 + result = rpc.multicall(self.context, + 'test', + {"method": "echo_three_times_yield", + "args": {"value": value}}) + for i, x in enumerate(result): + self.assertEqual(value + i, x) + + def test_context_passed(self): + """Makes sure a context is passed through rpc call.""" + value = 42 + result = rpc.call(self.context, + 'test', {"method": "context", + "args": {"value": value}}) + self.assertEqual(self.context.to_dict(), result) + + def test_call_exception(self): + """Test that exception gets passed back properly. + + rpc.call returns a RemoteError object. The value of the + exception is converted to a string, so we convert it back + to an int in the test. + + """ + value = 42 + self.assertRaises(rpc.RemoteError, + rpc.call, + self.context, + 'test', + {"method": "fail", + "args": {"value": value}}) + try: + rpc.call(self.context, + 'test', + {"method": "fail", + "args": {"value": value}}) + self.fail("should have thrown rpc.RemoteError") + except rpc.RemoteError as exc: + self.assertEqual(int(exc.value), value) + + def test_nested_calls(self): + """Test that we can do an rpc.call inside another call.""" + class Nested(object): + @staticmethod + def echo(context, queue, value): + """Calls echo in the passed queue""" + LOG.debug(_("Nested received %(queue)s, %(value)s") + % locals()) + # TODO: so, it will replay the context and use the same REQID? + # that's bizarre. + ret = rpc.call(context, + queue, + {"method": "echo", + "args": {"value": value}}) + LOG.debug(_("Nested return %s"), ret) + return value + + nested = Nested() + conn = rpc.create_connection(True) + rpc.create_consumer(conn, + 'nested', + nested, + False) + conn.consume_in_thread() + value = 42 + result = rpc.call(self.context, + 'nested', {"method": "echo", + "args": {"queue": "test", + "value": value}}) + conn.close() + self.assertEqual(value, result) + + +class TestReceiver(object): + """Simple Proxy class so the consumer has methods to call. + + Uses static methods because we aren't actually storing any state. + + """ + + @staticmethod + def echo(context, value): + """Simply returns whatever value is sent in.""" + LOG.debug(_("Received %s"), value) + return value + + @staticmethod + def context(context, value): + """Returns dictionary version of context.""" + LOG.debug(_("Received %s"), context) + return context.to_dict() + + @staticmethod + def echo_three_times(context, value): + context.reply(value) + context.reply(value + 1) + context.reply(value + 2) + + @staticmethod + def echo_three_times_yield(context, value): + yield value + yield value + 1 + yield value + 2 + + @staticmethod + def fail(context, value): + """Raises an exception with the value sent in.""" + raise Exception(value) -- cgit From 4c2e9ae35b22e7ef2e3fdd20ed72bac115510ada Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Sun, 28 Aug 2011 19:23:31 -0700 Subject: carrot consumer thread fix --- nova/rpc/impl_carrot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/rpc/impl_carrot.py b/nova/rpc/impl_carrot.py index 07af0a116..d0e6f8269 100644 --- a/nova/rpc/impl_carrot.py +++ b/nova/rpc/impl_carrot.py @@ -118,13 +118,13 @@ class Connection(carrot_connection.BrokerConnection): consumer_set.wait() except greenlet.GreenletExit: return - if not self._rpc_consumer_thread: + if self._rpc_consumer_thread is None: self._rpc_consumer_thread = eventlet.spawn(_consumer_thread) return self._rpc_consumer_thread def cancel_consumer_thread(self): """Cancel a consumer thread""" - if self._rpc_consumer_thread: + if self._rpc_consumer_thread is not None: self._rpc_consumer_thread.kill() try: self._rpc_consumer_thread.wait() -- cgit From 53f796e0cfcec9d5c56dca86ee3c185625917dca Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Sun, 28 Aug 2011 19:27:49 -0700 Subject: remove unused rpc connections in test_cloud and test_adminapi --- nova/tests/test_adminapi.py | 2 -- nova/tests/test_cloud.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/nova/tests/test_adminapi.py b/nova/tests/test_adminapi.py index 06cc498ac..aaa633adc 100644 --- a/nova/tests/test_adminapi.py +++ b/nova/tests/test_adminapi.py @@ -38,8 +38,6 @@ class AdminApiTestCase(test.TestCase): super(AdminApiTestCase, self).setUp() self.flags(connection_type='fake') - self.conn = rpc.create_connection() - # set up our cloud self.api = admin.AdminController() diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 0793784f8..14ab64f33 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -51,8 +51,6 @@ class CloudTestCase(test.TestCase): self.flags(connection_type='fake', stub_network=True) - self.conn = rpc.create_connection() - # set up our cloud self.cloud = cloud.CloudController() -- cgit From 605fe4f19af3af830a2a8c82809e9ce5909c602d Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 20:00:38 -0700 Subject: restore fixed_ip_associate_pool in nova/db/sqlalchemy.py to its original form before this branch. Figured out how to make unit tests pass without requiring that this function changes --- nova/db/sqlalchemy/api.py | 4 +++- nova/tests/test_quantum.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 80ce76e8f..00af62682 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -688,8 +688,10 @@ def fixed_ip_associate(context, address, instance_id, network_id=None): def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): session = get_session() with session.begin(): + network_or_none = or_(models.FixedIp.network_id == network_id, + models.FixedIp.network_id == None) fixed_ip_ref = session.query(models.FixedIp).\ - filter_by(network_id=network_id).\ + filter(network_or_none).\ filter_by(reserved=False).\ filter_by(deleted=False).\ filter_by(instance=None).\ diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index e7a8a01fa..3efdba910 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -17,6 +17,8 @@ from nova import context from nova import db +from nova.db.sqlalchemy import models +from nova.db.sqlalchemy.session import get_session from nova import exception from nova import log as logging from nova.network.quantum import manager as quantum_manager @@ -238,6 +240,15 @@ class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): for n in db.network_get_all(ctx): db.network_delete_safe(ctx, n['id']) + # I've found that other unit tests have a nasty habit of + # of creating fixed IPs and not cleaning up, which can + # confuse these tests, so we clean them all. + session = get_session() + result = session.query(models.FixedIp).all() + with session.begin(): + for fip_ref in result: + session.delete(fip_ref) + # Cannot run this unit tests auotmatically for now, as it requires # melange to be running locally. # -- cgit From 25cd526a72a98f184ed57fc85e7be2997305ce31 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 29 Aug 2011 00:12:30 -0700 Subject: pep8 fixes --- nova/flags.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index e09b4721a..774da4ab4 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -302,9 +302,12 @@ DEFINE_bool('rabbit_use_ssl', False, 'connect over SSL') DEFINE_string('rabbit_userid', 'guest', 'rabbit userid') DEFINE_string('rabbit_password', 'guest', 'rabbit password') DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') -DEFINE_integer('rabbit_retry_interval', 1, 'rabbit connection retry interval to start') -DEFINE_integer('rabbit_retry_backoff', 2, 'rabbit connection retry backoff in seconds') -DEFINE_integer('rabbit_max_retries', 0, 'maximum rabbit connection attempts (0=try forever)') +DEFINE_integer('rabbit_retry_interval', 1, + 'rabbit connection retry interval to start') +DEFINE_integer('rabbit_retry_backoff', 2, + 'rabbit connection retry backoff in seconds') +DEFINE_integer('rabbit_max_retries', 0, + 'maximum rabbit connection attempts (0=try forever)') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_boolean('rabbit_durable_queues', False, 'use durable queues') DEFINE_list('enabled_apis', ['ec2', 'osapi'], -- cgit From 39ca3df042bd3fa9a8ae2bf97d9383be7360d900 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 29 Aug 2011 09:45:00 -0400 Subject: Increased migration number. --- .../versions/043_update_instance_states.py | 138 --------------------- .../versions/044_update_instance_states.py | 138 +++++++++++++++++++++ 2 files changed, 138 insertions(+), 138 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/044_update_instance_states.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py deleted file mode 100644 index e58ae5362..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/043_update_instance_states.py +++ /dev/null @@ -1,138 +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. - -import sqlalchemy -from sqlalchemy import MetaData, Table, Column, String - -from nova.compute import task_states -from nova.compute import vm_states - - -meta = MetaData() - - -c_task_state = Column('task_state', - String(length=255, convert_unicode=False, - assert_unicode=None, unicode_error=None, - _warn_on_bytestring=False), - nullable=True) - - -_upgrade_translations = { - "stopping": { - "state_description": vm_states.ACTIVE, - "task_state": task_states.STOPPING, - }, - "stopped": { - "state_description": vm_states.STOPPED, - "task_state": None, - }, - "terminated": { - "state_description": vm_states.DELETED, - "task_state": None, - }, - "terminating": { - "state_description": vm_states.ACTIVE, - "task_state": task_states.DELETING, - }, - "running": { - "state_description": vm_states.ACTIVE, - "task_state": None, - }, - "scheduling": { - "state_description": vm_states.BUILDING, - "task_state": task_states.SCHEDULING, - }, - "migrating": { - "state_description": vm_states.MIGRATING, - "task_state": None, - }, - "pending": { - "state_description": vm_states.BUILDING, - "task_state": task_states.SCHEDULING, - }, -} - - -_downgrade_translations = { - vm_states.ACTIVE: { - None: "running", - task_states.DELETING: "terminating", - task_states.STOPPING: "stopping", - }, - vm_states.BUILDING: { - None: "pending", - task_states.SCHEDULING: "scheduling", - }, - vm_states.STOPPED: { - None: "stopped", - }, - vm_states.REBUILDING: { - None: "pending", - }, - vm_states.DELETED: { - None: "terminated", - }, - vm_states.MIGRATING: { - None: "migrating", - }, -} - - -def upgrade(migrate_engine): - meta.bind = migrate_engine - - instance_table = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - c_state = instance_table.c.state - c_state.alter(name='power_state') - - c_vm_state = instance_table.c.state_description - c_vm_state.alter(name='vm_state') - - instance_table.create_column(c_task_state) - - for old_state, values in _upgrade_translations.iteritems(): - instance_table.update().\ - values(**values).\ - where(c_vm_state == old_state).\ - execute() - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - - instance_table = Table('instances', meta, autoload=True, - autoload_with=migrate_engine) - - c_task_state = instance_table.c.task_state - - c_state = instance_table.c.power_state - c_state.alter(name='state') - - c_vm_state = instance_table.c.vm_state - c_vm_state.alter(name='state_description') - - for old_vm_state, old_task_states in _downgrade_translations.iteritems(): - for old_task_state, new_state_desc in old_task_states.iteritems(): - instance_table.update().\ - where(c_task_state == old_task_state).\ - where(c_vm_state == old_vm_state).\ - values(vm_state=new_state_desc).\ - execute() - - instance_table.drop_column('task_state') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/044_update_instance_states.py b/nova/db/sqlalchemy/migrate_repo/versions/044_update_instance_states.py new file mode 100644 index 000000000..e58ae5362 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/044_update_instance_states.py @@ -0,0 +1,138 @@ +# 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. + +import sqlalchemy +from sqlalchemy import MetaData, Table, Column, String + +from nova.compute import task_states +from nova.compute import vm_states + + +meta = MetaData() + + +c_task_state = Column('task_state', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + + +_upgrade_translations = { + "stopping": { + "state_description": vm_states.ACTIVE, + "task_state": task_states.STOPPING, + }, + "stopped": { + "state_description": vm_states.STOPPED, + "task_state": None, + }, + "terminated": { + "state_description": vm_states.DELETED, + "task_state": None, + }, + "terminating": { + "state_description": vm_states.ACTIVE, + "task_state": task_states.DELETING, + }, + "running": { + "state_description": vm_states.ACTIVE, + "task_state": None, + }, + "scheduling": { + "state_description": vm_states.BUILDING, + "task_state": task_states.SCHEDULING, + }, + "migrating": { + "state_description": vm_states.MIGRATING, + "task_state": None, + }, + "pending": { + "state_description": vm_states.BUILDING, + "task_state": task_states.SCHEDULING, + }, +} + + +_downgrade_translations = { + vm_states.ACTIVE: { + None: "running", + task_states.DELETING: "terminating", + task_states.STOPPING: "stopping", + }, + vm_states.BUILDING: { + None: "pending", + task_states.SCHEDULING: "scheduling", + }, + vm_states.STOPPED: { + None: "stopped", + }, + vm_states.REBUILDING: { + None: "pending", + }, + vm_states.DELETED: { + None: "terminated", + }, + vm_states.MIGRATING: { + None: "migrating", + }, +} + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + instance_table = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_state = instance_table.c.state + c_state.alter(name='power_state') + + c_vm_state = instance_table.c.state_description + c_vm_state.alter(name='vm_state') + + instance_table.create_column(c_task_state) + + for old_state, values in _upgrade_translations.iteritems(): + instance_table.update().\ + values(**values).\ + where(c_vm_state == old_state).\ + execute() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + instance_table = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + c_task_state = instance_table.c.task_state + + c_state = instance_table.c.power_state + c_state.alter(name='state') + + c_vm_state = instance_table.c.vm_state + c_vm_state.alter(name='state_description') + + for old_vm_state, old_task_states in _downgrade_translations.iteritems(): + for old_task_state, new_state_desc in old_task_states.iteritems(): + instance_table.update().\ + where(c_task_state == old_task_state).\ + where(c_vm_state == old_vm_state).\ + values(vm_state=new_state_desc).\ + execute() + + instance_table.drop_column('task_state') -- cgit From 0972d9188b0b73fa357f75896ab3bebda9a2a9de Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 29 Aug 2011 10:13:39 -0400 Subject: Removed test_parallel_builds --- nova/tests/test_xenapi.py | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 2f0559366..45dad3516 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -16,7 +16,6 @@ """Test suite for XenAPI.""" -import eventlet import functools import json import os @@ -203,42 +202,6 @@ class XenAPIVMTestCase(test.TestCase): self.context = context.RequestContext(self.user_id, self.project_id) 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_ref': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, - 'instance_type_id': '3', # m1.large - 'os_type': 'linux', - 'architecture': 'x86-64'} - network_info = [({'bridge': 'fa0', 'id': 0, 'injected': False}, - {'broadcast': '192.168.0.255', - 'dns': ['192.168.0.1'], - 'gateway': '192.168.0.1', - 'gateway6': 'dead:beef::1', - 'ip6s': [{'enabled': '1', - 'ip': 'dead:beef::dcad:beff:feef:0', - 'netmask': '64'}], - 'ips': [{'enabled': '1', - 'ip': '192.168.0.100', - 'netmask': '255.255.255.0'}], - 'label': 'fake', - 'mac': 'DE:AD:BE:EF:00:00', - 'rxtx_cap': 3})] - instance = db.instance_create(self.context, values) - self.conn.spawn(self.context, instance, network_info) - - 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, []) -- cgit -- cgit From 81d30e900d3329f40bfd05682b73e73951c435ca Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Mon, 29 Aug 2011 08:31:56 -0700 Subject: update file name for db migrate script after merge --- .../versions/041_add_network_priority.py | 44 ---------------------- .../versions/043_add_network_priority.py | 44 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 44 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py deleted file mode 100644 index e69380199..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2011 Nicira, Inc. -# 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 sqlalchemy import * -from migrate import * - -from nova import log as logging -from nova import utils - - -meta = MetaData() - -# Add priority column to networks table -priority = Column('priority', Integer()) - - -def upgrade(migrate_engine): - meta.bind = migrate_engine - - # grab tables and (column for dropping later) - networks = Table('networks', meta, autoload=True) - - try: - networks.create_column(priority) - except Exception: - logging.error(_("priority column not added to networks table")) - raise - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - networks.drop_column(priority) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py new file mode 100644 index 000000000..e69380199 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py @@ -0,0 +1,44 @@ +# Copyright 2011 Nicira, Inc. +# 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 sqlalchemy import * +from migrate import * + +from nova import log as logging +from nova import utils + + +meta = MetaData() + +# Add priority column to networks table +priority = Column('priority', Integer()) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + # grab tables and (column for dropping later) + networks = Table('networks', meta, autoload=True) + + try: + networks.create_column(priority) + except Exception: + logging.error(_("priority column not added to networks table")) + raise + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + networks.drop_column(priority) -- cgit From ed1399b5a4a023b606263b5464dfe684e460a126 Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Mon, 29 Aug 2011 08:33:12 -0700 Subject: update file name for db migrate script after merge (again) --- .../versions/043_add_network_priority.py | 44 ---------------------- .../versions/044_add_network_priority.py | 44 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 44 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py deleted file mode 100644 index e69380199..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2011 Nicira, Inc. -# 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 sqlalchemy import * -from migrate import * - -from nova import log as logging -from nova import utils - - -meta = MetaData() - -# Add priority column to networks table -priority = Column('priority', Integer()) - - -def upgrade(migrate_engine): - meta.bind = migrate_engine - - # grab tables and (column for dropping later) - networks = Table('networks', meta, autoload=True) - - try: - networks.create_column(priority) - except Exception: - logging.error(_("priority column not added to networks table")) - raise - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - networks.drop_column(priority) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py new file mode 100644 index 000000000..e69380199 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py @@ -0,0 +1,44 @@ +# Copyright 2011 Nicira, Inc. +# 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 sqlalchemy import * +from migrate import * + +from nova import log as logging +from nova import utils + + +meta = MetaData() + +# Add priority column to networks table +priority = Column('priority', Integer()) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + # grab tables and (column for dropping later) + networks = Table('networks', meta, autoload=True) + + try: + networks.create_column(priority) + except Exception: + logging.error(_("priority column not added to networks table")) + raise + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + networks.drop_column(priority) -- cgit From dc129eeb96a0a201fb3d032078eaf8ab192ca207 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 29 Aug 2011 09:03:56 -0700 Subject: add alias to mailmap --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 5c8df80e0..3c7a31e03 100644 --- a/.mailmap +++ b/.mailmap @@ -15,6 +15,7 @@ + @@ -36,7 +37,7 @@ - + -- cgit From 6d8663a887a1241b1c3136626e7b915be860273b Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 29 Aug 2011 09:04:43 -0700 Subject: remove 'uuid' param for nova-manage network delete that I had add previously --- bin/nova-manage | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index a95890e36..051079ef3 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -818,15 +818,12 @@ class NetworkCommands(object): @args('--network', dest="fixed_range", metavar='', help='Network to delete') - @args('--uuid', dest="uuid", metavar='', - help='UUID of network to delete') def delete(self, fixed_range): """Deletes a network""" # delete the network net_manager = utils.import_object(FLAGS.network_manager) - net_manager.delete_network(context.get_admin_context(), fixed_range, - uuid=None) + net_manager.delete_network(context.get_admin_context(), fixed_range) @args('--network', dest="fixed_range", metavar='', -- cgit From 024b76a8df5c96d37dea0a05f66dfe4628a64a28 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 29 Aug 2011 10:27:25 -0700 Subject: more logging info to help identify bad payloads --- nova/notifier/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 6ef4a050e..043838536 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -122,4 +122,5 @@ def notify(publisher_id, event_type, priority, payload): driver.notify(msg) except Exception, e: LOG.exception(_("Problem '%(e)s' attempting to " - "send to notification system." % locals())) + "send to notification system. Payload=%(payload)s" % + locals())) -- cgit From 7e8e39160d1329f4923334fa822310d266651907 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 29 Aug 2011 10:43:26 -0700 Subject: access db directly in networkmanagers's delete_network method, so stubbed test call works correctly --- nova/network/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index d1883ff8d..b625e7823 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -791,7 +791,7 @@ class NetworkManager(manager.SchedulerDependentManager): if require_disassociated and network.project_id is not None: raise ValueError(_('Network must be disassociated from project %s' ' before delete' % network.project_id)) - self.db.network_delete_safe(context, network.id) + db.network_delete_safe(context, network.id) @property def _bottom_reserved_ips(self): # pylint: disable=R0201 -- cgit From bb4f7129e945602a8e830520e7877a33b2539530 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 29 Aug 2011 11:04:27 -0700 Subject: remove brackets from mailmap entry --- .mailmap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 3c7a31e03..a46dd6673 100644 --- a/.mailmap +++ b/.mailmap @@ -15,7 +15,7 @@ - + danwent@gmail.com -- cgit From ee15f2a58217d522e23d811db4958e2e9b2338d6 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 29 Aug 2011 14:36:12 -0700 Subject: ditched rpc.create_consumer(conn) interface... instead you now do conn.create_consumer(.. --- nova/rpc/__init__.py | 6 +---- nova/rpc/impl_carrot.py | 29 +++++++++++------------ nova/rpc/impl_kombu.py | 55 +++++++++++++++++++++---------------------- nova/service.py | 8 +++---- nova/tests/test_rpc.py | 10 ++------ nova/tests/test_rpc_carrot.py | 10 ++------ nova/tests/test_rpc_kombu.py | 10 ++------ nova/tests/test_test.py | 3 +-- 8 files changed, 52 insertions(+), 79 deletions(-) diff --git a/nova/rpc/__init__.py b/nova/rpc/__init__.py index 2a47ba87b..fe50fb476 100644 --- a/nova/rpc/__init__.py +++ b/nova/rpc/__init__.py @@ -24,7 +24,7 @@ from nova import flags FLAGS = flags.FLAGS flags.DEFINE_string('rpc_backend', 'kombu', - "The messaging module to use, defaults to carrot.") + "The messaging module to use, defaults to kombu.") impl_table = {'kombu': 'nova.rpc.impl_kombu', 'amqp': 'nova.rpc.impl_kombu', @@ -41,10 +41,6 @@ def create_connection(new=True): return RPCIMPL.create_connection(new=new) -def create_consumer(conn, topic, proxy, fanout=False): - RPCIMPL.create_consumer(conn, topic, proxy, fanout) - - def call(context, topic, msg): return RPCIMPL.call(context, topic, msg) diff --git a/nova/rpc/impl_carrot.py b/nova/rpc/impl_carrot.py index d0e6f8269..6d504aaec 100644 --- a/nova/rpc/impl_carrot.py +++ b/nova/rpc/impl_carrot.py @@ -132,6 +132,20 @@ class Connection(carrot_connection.BrokerConnection): pass self._rpc_consumer_thread = None + def create_consumer(self, topic, proxy, fanout=False): + """Create a consumer that calls methods in the proxy""" + if fanout: + consumer = FanoutAdapterConsumer( + connection=self, + topic=topic, + proxy=proxy) + else: + consumer = TopicAdapterConsumer( + connection=self, + topic=topic, + proxy=proxy) + self._rpc_consumers.append(consumer) + class Pool(pools.Pool): """Class that implements a Pool of Connections.""" @@ -187,7 +201,6 @@ class Consumer(messaging.Consumer): LOG.error(_('Unable to connect to AMQP server ' 'after %(tries)d tries. Shutting down.') % locals()) sys.exit(1) - connection._rpc_consumers.append(self) def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): """Wraps the parent fetch with some logic for failed connection.""" @@ -568,20 +581,6 @@ def create_connection(new=True): return Connection.instance(new=new) -def create_consumer(conn, topic, proxy, fanout=False): - """Create a consumer that calls methods in the proxy""" - if fanout: - return FanoutAdapterConsumer( - connection=conn, - topic=topic, - proxy=proxy) - else: - return TopicAdapterConsumer( - connection=conn, - topic=topic, - proxy=proxy) - - def call(context, topic, msg): """Sends a message on a topic and wait for a response.""" rv = multicall(context, topic, msg) diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index 49bca1d81..83ee1b122 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -404,26 +404,6 @@ class Connection(object): '%s' % str(e))) self.reconnect() - def consume(self, limit=None): - """Consume from all queues/consumers""" - it = self.iterconsume(limit=limit) - while True: - try: - it.next() - except StopIteration: - return - - def consume_in_thread(self): - """Consumer from all queues/consumers in a greenthread""" - def _consumer_thread(): - try: - self.consume() - except greenlet.GreenletExit: - return - if self.consumer_thread is None: - self.consumer_thread = eventlet.spawn(_consumer_thread) - return self.consumer_thread - def cancel_consumer_thread(self): """Cancel a consumer thread""" if self.consumer_thread is not None: @@ -478,6 +458,33 @@ class Connection(object): """Send a 'fanout' message""" self.publisher_send(FanoutPublisher, topic, msg) + def consume(self, limit=None): + """Consume from all queues/consumers""" + it = self.iterconsume(limit=limit) + while True: + try: + it.next() + except StopIteration: + return + + def consume_in_thread(self): + """Consumer from all queues/consumers in a greenthread""" + def _consumer_thread(): + try: + self.consume() + except greenlet.GreenletExit: + return + if self.consumer_thread is None: + self.consumer_thread = eventlet.spawn(_consumer_thread) + return self.consumer_thread + + def create_consumer(self, topic, proxy, fanout=False): + """Create a consumer that calls a method in a proxy object""" + if fanout: + self.declare_fanout_consumer(topic, ProxyCallback(proxy)) + else: + self.declare_topic_consumer(topic, ProxyCallback(proxy)) + class Pool(pools.Pool): """Class that implements a Pool of Connections.""" @@ -678,14 +685,6 @@ def create_connection(new=True): return ConnectionContext(pooled=not new) -def create_consumer(conn, topic, proxy, fanout=False): - """Create a consumer that calls a method in a proxy object""" - if fanout: - conn.declare_fanout_consumer(topic, ProxyCallback(proxy)) - else: - conn.declare_topic_consumer(topic, ProxyCallback(proxy)) - - def multicall(context, topic, msg): """Make a call that returns multiple times.""" # Can't use 'with' for multicall, as it returns an iterator diff --git a/nova/service.py b/nova/service.py index ab7925eb3..247eb4fb1 100644 --- a/nova/service.py +++ b/nova/service.py @@ -153,14 +153,12 @@ class Service(object): self.topic) # Share this same connection for these Consumers - rpc.create_consumer(self.conn, self.topic, self, - fanout=False) + self.conn.create_consumer(self.topic, self, fanout=False) node_topic = '%s.%s' % (self.topic, self.host) - rpc.create_consumer(self.conn, node_topic, self, - fanout=False) + self.conn.create_consumer(node_topic, self, fanout=False) - rpc.create_consumer(self.conn, self.topic, self, fanout=True) + self.conn.create_consumer(self.topic, self, fanout=True) # Consume from all consumers in a thread self.conn.consume_in_thread() diff --git a/nova/tests/test_rpc.py b/nova/tests/test_rpc.py index 2b9922491..ba91ea3b2 100644 --- a/nova/tests/test_rpc.py +++ b/nova/tests/test_rpc.py @@ -33,10 +33,7 @@ class RpcTestCase(test.TestCase): super(RpcTestCase, self).setUp() self.conn = rpc.create_connection(True) self.receiver = TestReceiver() - rpc.create_consumer(self.conn, - 'test', - self.receiver, - False) + self.conn.create_consumer('test', self.receiver, False) self.conn.consume_in_thread() self.context = context.get_admin_context() @@ -143,10 +140,7 @@ class RpcTestCase(test.TestCase): nested = Nested() conn = rpc.create_connection(True) - rpc.create_consumer(conn, - 'nested', - nested, - False) + conn.create_consumer('nested', nested, False) conn.consume_in_thread() value = 42 result = rpc.call(self.context, diff --git a/nova/tests/test_rpc_carrot.py b/nova/tests/test_rpc_carrot.py index cf84980ab..ff704ecf8 100644 --- a/nova/tests/test_rpc_carrot.py +++ b/nova/tests/test_rpc_carrot.py @@ -33,10 +33,7 @@ class RpcCarrotTestCase(test.TestCase): super(RpcCarrotTestCase, self).setUp() self.conn = rpc.create_connection(True) self.receiver = TestReceiver() - rpc.create_consumer(self.conn, - 'test', - self.receiver, - False) + self.conn.create_consumer('test', self.receiver, False) self.conn.consume_in_thread() self.context = context.get_admin_context() @@ -151,10 +148,7 @@ class RpcCarrotTestCase(test.TestCase): nested = Nested() conn = rpc.create_connection(True) - rpc.create_consumer(conn, - 'nested', - nested, - False) + conn.create_consumer('nested', nested, False) conn.consume_in_thread() value = 42 result = rpc.call(self.context, diff --git a/nova/tests/test_rpc_kombu.py b/nova/tests/test_rpc_kombu.py index 457dfdeca..7db88ecd0 100644 --- a/nova/tests/test_rpc_kombu.py +++ b/nova/tests/test_rpc_kombu.py @@ -33,10 +33,7 @@ class RpcKombuTestCase(test.TestCase): super(RpcKombuTestCase, self).setUp() self.conn = rpc.create_connection() self.receiver = TestReceiver() - rpc.create_consumer(self.conn, - 'test', - self.receiver, - False) + self.conn.create_consumer('test', self.receiver, False) self.conn.consume_in_thread() self.context = context.get_admin_context() @@ -215,10 +212,7 @@ class RpcKombuTestCase(test.TestCase): nested = Nested() conn = rpc.create_connection(True) - rpc.create_consumer(conn, - 'nested', - nested, - False) + conn.create_consumer('nested', nested, False) conn.consume_in_thread() value = 42 result = rpc.call(self.context, diff --git a/nova/tests/test_test.py b/nova/tests/test_test.py index 6075abbb0..3482ff6a0 100644 --- a/nova/tests/test_test.py +++ b/nova/tests/test_test.py @@ -40,6 +40,5 @@ class IsolationTestCase(test.TestCase): connection = rpc.create_connection(new=True) proxy = NeverCalled() - rpc.create_consumer(connection, 'compute', - proxy, fanout=False) + connection.create_consumer('compute', proxy, fanout=False) connection.consume_in_thread() -- cgit From a16efa7b94a15040657b961b0fd29a4d2720ef21 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 29 Aug 2011 14:54:20 -0700 Subject: created nova/tests/test_rpc_common.py which contains a rpc test base class so we can share tests between the rpc implementations --- nova/tests/test_rpc.py | 157 +---------------------------------- nova/tests/test_rpc_carrot.py | 169 ++----------------------------------- nova/tests/test_rpc_common.py | 188 ++++++++++++++++++++++++++++++++++++++++++ nova/tests/test_rpc_kombu.py | 172 +++----------------------------------- 4 files changed, 211 insertions(+), 475 deletions(-) create mode 100644 nova/tests/test_rpc_common.py diff --git a/nova/tests/test_rpc.py b/nova/tests/test_rpc.py index ba91ea3b2..6b4454747 100644 --- a/nova/tests/test_rpc.py +++ b/nova/tests/test_rpc.py @@ -22,167 +22,16 @@ Unit Tests for remote procedure calls using queue from nova import context from nova import log as logging from nova import rpc -from nova import test +from nova.tests import test_rpc_common LOG = logging.getLogger('nova.tests.rpc') -class RpcTestCase(test.TestCase): +class RpcTestCase(test_rpc_common._BaseRpcTestCase): def setUp(self): + self.rpc = rpc super(RpcTestCase, self).setUp() - self.conn = rpc.create_connection(True) - self.receiver = TestReceiver() - self.conn.create_consumer('test', self.receiver, False) - self.conn.consume_in_thread() - self.context = context.get_admin_context() def tearDown(self): - self.conn.close() super(RpcTestCase, self).tearDown() - - def test_call_succeed(self): - value = 42 - result = rpc.call(self.context, 'test', {"method": "echo", - "args": {"value": value}}) - self.assertEqual(value, result) - - def test_call_succeed_despite_multiple_returns(self): - value = 42 - result = rpc.call(self.context, 'test', {"method": "echo_three_times", - "args": {"value": value}}) - self.assertEqual(value + 2, result) - - def test_call_succeed_despite_multiple_returns_yield(self): - value = 42 - result = rpc.call(self.context, 'test', - {"method": "echo_three_times_yield", - "args": {"value": value}}) - self.assertEqual(value + 2, result) - - def test_multicall_succeed_once(self): - value = 42 - result = rpc.multicall(self.context, - 'test', - {"method": "echo", - "args": {"value": value}}) - for i, x in enumerate(result): - if i > 0: - self.fail('should only receive one response') - self.assertEqual(value + i, x) - - def test_multicall_succeed_three_times(self): - value = 42 - result = rpc.multicall(self.context, - 'test', - {"method": "echo_three_times", - "args": {"value": value}}) - for i, x in enumerate(result): - self.assertEqual(value + i, x) - - def test_multicall_succeed_three_times_yield(self): - value = 42 - result = rpc.multicall(self.context, - 'test', - {"method": "echo_three_times_yield", - "args": {"value": value}}) - for i, x in enumerate(result): - self.assertEqual(value + i, x) - - def test_context_passed(self): - """Makes sure a context is passed through rpc call.""" - value = 42 - result = rpc.call(self.context, - 'test', {"method": "context", - "args": {"value": value}}) - self.assertEqual(self.context.to_dict(), result) - - def test_call_exception(self): - """Test that exception gets passed back properly. - - rpc.call returns a RemoteError object. The value of the - exception is converted to a string, so we convert it back - to an int in the test. - - """ - value = 42 - self.assertRaises(rpc.RemoteError, - rpc.call, - self.context, - 'test', - {"method": "fail", - "args": {"value": value}}) - try: - rpc.call(self.context, - 'test', - {"method": "fail", - "args": {"value": value}}) - self.fail("should have thrown rpc.RemoteError") - except rpc.RemoteError as exc: - self.assertEqual(int(exc.value), value) - - def test_nested_calls(self): - """Test that we can do an rpc.call inside another call.""" - class Nested(object): - @staticmethod - def echo(context, queue, value): - """Calls echo in the passed queue""" - LOG.debug(_("Nested received %(queue)s, %(value)s") - % locals()) - # TODO: so, it will replay the context and use the same REQID? - # that's bizarre. - ret = rpc.call(context, - queue, - {"method": "echo", - "args": {"value": value}}) - LOG.debug(_("Nested return %s"), ret) - return value - - nested = Nested() - conn = rpc.create_connection(True) - conn.create_consumer('nested', nested, False) - conn.consume_in_thread() - value = 42 - result = rpc.call(self.context, - 'nested', {"method": "echo", - "args": {"queue": "test", - "value": value}}) - conn.close() - self.assertEqual(value, result) - - -class TestReceiver(object): - """Simple Proxy class so the consumer has methods to call. - - Uses static methods because we aren't actually storing any state. - - """ - - @staticmethod - def echo(context, value): - """Simply returns whatever value is sent in.""" - LOG.debug(_("Received %s"), value) - return value - - @staticmethod - def context(context, value): - """Returns dictionary version of context.""" - LOG.debug(_("Received %s"), context) - return context.to_dict() - - @staticmethod - def echo_three_times(context, value): - context.reply(value) - context.reply(value + 1) - context.reply(value + 2) - - @staticmethod - def echo_three_times_yield(context, value): - yield value - yield value + 1 - yield value + 2 - - @staticmethod - def fail(context, value): - """Raises an exception with the value sent in.""" - raise Exception(value) diff --git a/nova/tests/test_rpc_carrot.py b/nova/tests/test_rpc_carrot.py index ff704ecf8..57cdebf4f 100644 --- a/nova/tests/test_rpc_carrot.py +++ b/nova/tests/test_rpc_carrot.py @@ -16,181 +16,30 @@ # License for the specific language governing permissions and limitations # under the License. """ -Unit Tests for remote procedure calls using queue +Unit Tests for remote procedure calls using carrot """ from nova import context from nova import log as logging -from nova.rpc import impl_carrot as rpc -from nova import test +from nova.rpc import impl_carrot +from nova.tests import test_rpc_common LOG = logging.getLogger('nova.tests.rpc') -class RpcCarrotTestCase(test.TestCase): +class RpcCarrotTestCase(test_rpc_common._BaseRpcTestCase): def setUp(self): + self.rpc = impl_carrot super(RpcCarrotTestCase, self).setUp() - self.conn = rpc.create_connection(True) - self.receiver = TestReceiver() - self.conn.create_consumer('test', self.receiver, False) - self.conn.consume_in_thread() - self.context = context.get_admin_context() def tearDown(self): - self.conn.close() super(RpcCarrotTestCase, self).tearDown() def test_connectionpool_single(self): """Test that ConnectionPool recycles a single connection.""" - conn1 = rpc.ConnectionPool.get() - rpc.ConnectionPool.put(conn1) - conn2 = rpc.ConnectionPool.get() - rpc.ConnectionPool.put(conn2) + conn1 = self.rpc.ConnectionPool.get() + self.rpc.ConnectionPool.put(conn1) + conn2 = self.rpc.ConnectionPool.get() + self.rpc.ConnectionPool.put(conn2) self.assertEqual(conn1, conn2) - - def test_call_succeed(self): - value = 42 - result = rpc.call(self.context, 'test', {"method": "echo", - "args": {"value": value}}) - self.assertEqual(value, result) - - def test_call_succeed_despite_multiple_returns(self): - value = 42 - result = rpc.call(self.context, 'test', {"method": "echo_three_times", - "args": {"value": value}}) - self.assertEqual(value + 2, result) - - def test_call_succeed_despite_multiple_returns_yield(self): - value = 42 - result = rpc.call(self.context, 'test', - {"method": "echo_three_times_yield", - "args": {"value": value}}) - self.assertEqual(value + 2, result) - - def test_multicall_succeed_once(self): - value = 42 - result = rpc.multicall(self.context, - 'test', - {"method": "echo", - "args": {"value": value}}) - for i, x in enumerate(result): - if i > 0: - self.fail('should only receive one response') - self.assertEqual(value + i, x) - - def test_multicall_succeed_three_times(self): - value = 42 - result = rpc.multicall(self.context, - 'test', - {"method": "echo_three_times", - "args": {"value": value}}) - for i, x in enumerate(result): - self.assertEqual(value + i, x) - - def test_multicall_succeed_three_times_yield(self): - value = 42 - result = rpc.multicall(self.context, - 'test', - {"method": "echo_three_times_yield", - "args": {"value": value}}) - for i, x in enumerate(result): - self.assertEqual(value + i, x) - - def test_context_passed(self): - """Makes sure a context is passed through rpc call.""" - value = 42 - result = rpc.call(self.context, - 'test', {"method": "context", - "args": {"value": value}}) - self.assertEqual(self.context.to_dict(), result) - - def test_call_exception(self): - """Test that exception gets passed back properly. - - rpc.call returns a RemoteError object. The value of the - exception is converted to a string, so we convert it back - to an int in the test. - - """ - value = 42 - self.assertRaises(rpc.RemoteError, - rpc.call, - self.context, - 'test', - {"method": "fail", - "args": {"value": value}}) - try: - rpc.call(self.context, - 'test', - {"method": "fail", - "args": {"value": value}}) - self.fail("should have thrown rpc.RemoteError") - except rpc.RemoteError as exc: - self.assertEqual(int(exc.value), value) - - def test_nested_calls(self): - """Test that we can do an rpc.call inside another call.""" - class Nested(object): - @staticmethod - def echo(context, queue, value): - """Calls echo in the passed queue""" - LOG.debug(_("Nested received %(queue)s, %(value)s") - % locals()) - # TODO: so, it will replay the context and use the same REQID? - # that's bizarre. - ret = rpc.call(context, - queue, - {"method": "echo", - "args": {"value": value}}) - LOG.debug(_("Nested return %s"), ret) - return value - - nested = Nested() - conn = rpc.create_connection(True) - conn.create_consumer('nested', nested, False) - conn.consume_in_thread() - value = 42 - result = rpc.call(self.context, - 'nested', {"method": "echo", - "args": {"queue": "test", - "value": value}}) - conn.close() - self.assertEqual(value, result) - - -class TestReceiver(object): - """Simple Proxy class so the consumer has methods to call. - - Uses static methods because we aren't actually storing any state. - - """ - - @staticmethod - def echo(context, value): - """Simply returns whatever value is sent in.""" - LOG.debug(_("Received %s"), value) - return value - - @staticmethod - def context(context, value): - """Returns dictionary version of context.""" - LOG.debug(_("Received %s"), context) - return context.to_dict() - - @staticmethod - def echo_three_times(context, value): - context.reply(value) - context.reply(value + 1) - context.reply(value + 2) - - @staticmethod - def echo_three_times_yield(context, value): - yield value - yield value + 1 - yield value + 2 - - @staticmethod - def fail(context, value): - """Raises an exception with the value sent in.""" - raise Exception(value) diff --git a/nova/tests/test_rpc_common.py b/nova/tests/test_rpc_common.py new file mode 100644 index 000000000..b922be1df --- /dev/null +++ b/nova/tests/test_rpc_common.py @@ -0,0 +1,188 @@ +# 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. +""" +Unit Tests for remote procedure calls shared between all implementations +""" + +from nova import context +from nova import log as logging +from nova.rpc.common import RemoteError +from nova import test + + +LOG = logging.getLogger('nova.tests.rpc') + + +class _BaseRpcTestCase(test.TestCase): + def setUp(self): + super(_BaseRpcTestCase, self).setUp() + self.conn = self.rpc.create_connection(True) + self.receiver = TestReceiver() + self.conn.create_consumer('test', self.receiver, False) + self.conn.consume_in_thread() + self.context = context.get_admin_context() + + def tearDown(self): + self.conn.close() + super(_BaseRpcTestCase, self).tearDown() + + def test_call_succeed(self): + value = 42 + result = self.rpc.call(self.context, 'test', {"method": "echo", + "args": {"value": value}}) + self.assertEqual(value, result) + + def test_call_succeed_despite_multiple_returns(self): + value = 42 + result = self.rpc.call(self.context, 'test', {"method": "echo_three_times", + "args": {"value": value}}) + self.assertEqual(value + 2, result) + + def test_call_succeed_despite_multiple_returns_yield(self): + value = 42 + result = self.rpc.call(self.context, 'test', + {"method": "echo_three_times_yield", + "args": {"value": value}}) + self.assertEqual(value + 2, result) + + def test_multicall_succeed_once(self): + value = 42 + result = self.rpc.multicall(self.context, + 'test', + {"method": "echo", + "args": {"value": value}}) + for i, x in enumerate(result): + if i > 0: + self.fail('should only receive one response') + self.assertEqual(value + i, x) + + def test_multicall_succeed_three_times(self): + value = 42 + result = self.rpc.multicall(self.context, + 'test', + {"method": "echo_three_times", + "args": {"value": value}}) + for i, x in enumerate(result): + self.assertEqual(value + i, x) + + def test_multicall_succeed_three_times_yield(self): + value = 42 + result = self.rpc.multicall(self.context, + 'test', + {"method": "echo_three_times_yield", + "args": {"value": value}}) + for i, x in enumerate(result): + self.assertEqual(value + i, x) + + def test_context_passed(self): + """Makes sure a context is passed through rpc call.""" + value = 42 + result = self.rpc.call(self.context, + 'test', {"method": "context", + "args": {"value": value}}) + self.assertEqual(self.context.to_dict(), result) + + def test_call_exception(self): + """Test that exception gets passed back properly. + + rpc.call returns a RemoteError object. The value of the + exception is converted to a string, so we convert it back + to an int in the test. + + """ + value = 42 + self.assertRaises(RemoteError, + self.rpc.call, + self.context, + 'test', + {"method": "fail", + "args": {"value": value}}) + try: + self.rpc.call(self.context, + 'test', + {"method": "fail", + "args": {"value": value}}) + self.fail("should have thrown RemoteError") + except RemoteError as exc: + self.assertEqual(int(exc.value), value) + + def test_nested_calls(self): + """Test that we can do an rpc.call inside another call.""" + class Nested(object): + @staticmethod + def echo(context, queue, value): + """Calls echo in the passed queue""" + LOG.debug(_("Nested received %(queue)s, %(value)s") + % locals()) + # TODO: so, it will replay the context and use the same REQID? + # that's bizarre. + ret = self.rpc.call(context, + queue, + {"method": "echo", + "args": {"value": value}}) + LOG.debug(_("Nested return %s"), ret) + return value + + nested = Nested() + conn = self.rpc.create_connection(True) + conn.create_consumer('nested', nested, False) + conn.consume_in_thread() + value = 42 + result = self.rpc.call(self.context, + 'nested', {"method": "echo", + "args": {"queue": "test", + "value": value}}) + conn.close() + self.assertEqual(value, result) + + +class TestReceiver(object): + """Simple Proxy class so the consumer has methods to call. + + Uses static methods because we aren't actually storing any state. + + """ + + @staticmethod + def echo(context, value): + """Simply returns whatever value is sent in.""" + LOG.debug(_("Received %s"), value) + return value + + @staticmethod + def context(context, value): + """Returns dictionary version of context.""" + LOG.debug(_("Received %s"), context) + return context.to_dict() + + @staticmethod + def echo_three_times(context, value): + context.reply(value) + context.reply(value + 1) + context.reply(value + 2) + + @staticmethod + def echo_three_times_yield(context, value): + yield value + yield value + 1 + yield value + 2 + + @staticmethod + def fail(context, value): + """Raises an exception with the value sent in.""" + raise Exception(value) diff --git a/nova/tests/test_rpc_kombu.py b/nova/tests/test_rpc_kombu.py index 7db88ecd0..101ed14af 100644 --- a/nova/tests/test_rpc_kombu.py +++ b/nova/tests/test_rpc_kombu.py @@ -16,37 +16,33 @@ # License for the specific language governing permissions and limitations # under the License. """ -Unit Tests for remote procedure calls using queue +Unit Tests for remote procedure calls using kombu """ from nova import context from nova import log as logging -from nova.rpc import impl_kombu as rpc from nova import test +from nova.rpc import impl_kombu +from nova.tests import test_rpc_common LOG = logging.getLogger('nova.tests.rpc') -class RpcKombuTestCase(test.TestCase): +class RpcKombuTestCase(test_rpc_common._BaseRpcTestCase): def setUp(self): + self.rpc = impl_kombu super(RpcKombuTestCase, self).setUp() - self.conn = rpc.create_connection() - self.receiver = TestReceiver() - self.conn.create_consumer('test', self.receiver, False) - self.conn.consume_in_thread() - self.context = context.get_admin_context() def tearDown(self): - self.conn.close() super(RpcKombuTestCase, self).tearDown() def test_reusing_connection(self): """Test that reusing a connection returns same one.""" - conn_context = rpc.create_connection(new=False) + conn_context = self.rpc.create_connection(new=False) conn1 = conn_context.connection conn_context.close() - conn_context = rpc.create_connection(new=False) + conn_context = self.rpc.create_connection(new=False) conn2 = conn_context.connection conn_context.close() self.assertEqual(conn1, conn2) @@ -54,7 +50,7 @@ class RpcKombuTestCase(test.TestCase): def test_topic_send_receive(self): """Test sending to a topic exchange/queue""" - conn = rpc.create_connection() + conn = self.rpc.create_connection() message = 'topic test message' self.received_message = None @@ -71,7 +67,7 @@ class RpcKombuTestCase(test.TestCase): def test_direct_send_receive(self): """Test sending to a direct exchange/queue""" - conn = rpc.create_connection() + conn = self.rpc.create_connection() message = 'direct test message' self.received_message = None @@ -91,8 +87,8 @@ class RpcKombuTestCase(test.TestCase): def test_fanout_send_receive(self): """Test sending to a fanout exchange and consuming from 2 queues""" - conn = rpc.create_connection() - conn2 = rpc.create_connection() + conn = self.rpc.create_connection() + conn2 = self.rpc.create_connection() message = 'fanout test message' self.received_message = None @@ -112,149 +108,3 @@ class RpcKombuTestCase(test.TestCase): conn2.consume(limit=1) conn2.close() self.assertEqual(self.received_message, message) - - def test_call_succeed(self): - value = 42 - result = rpc.call(self.context, 'test', {"method": "echo", - "args": {"value": value}}) - self.assertEqual(value, result) - - def test_call_succeed_despite_multiple_returns(self): - value = 42 - result = rpc.call(self.context, 'test', {"method": "echo_three_times", - "args": {"value": value}}) - self.assertEqual(value + 2, result) - - def test_call_succeed_despite_multiple_returns_yield(self): - value = 42 - result = rpc.call(self.context, 'test', - {"method": "echo_three_times_yield", - "args": {"value": value}}) - self.assertEqual(value + 2, result) - - def test_multicall_succeed_once(self): - value = 42 - result = rpc.multicall(self.context, - 'test', - {"method": "echo", - "args": {"value": value}}) - for i, x in enumerate(result): - if i > 0: - self.fail('should only receive one response') - self.assertEqual(value + i, x) - - def test_multicall_succeed_three_times(self): - value = 42 - result = rpc.multicall(self.context, - 'test', - {"method": "echo_three_times", - "args": {"value": value}}) - for i, x in enumerate(result): - self.assertEqual(value + i, x) - - def test_multicall_succeed_three_times_yield(self): - value = 42 - result = rpc.multicall(self.context, - 'test', - {"method": "echo_three_times_yield", - "args": {"value": value}}) - for i, x in enumerate(result): - self.assertEqual(value + i, x) - - def test_context_passed(self): - """Makes sure a context is passed through rpc call.""" - value = 42 - result = rpc.call(self.context, - 'test', {"method": "context", - "args": {"value": value}}) - self.assertEqual(self.context.to_dict(), result) - - def test_call_exception(self): - """Test that exception gets passed back properly. - - rpc.call returns a RemoteError object. The value of the - exception is converted to a string, so we convert it back - to an int in the test. - - """ - value = 42 - self.assertRaises(rpc.RemoteError, - rpc.call, - self.context, - 'test', - {"method": "fail", - "args": {"value": value}}) - try: - rpc.call(self.context, - 'test', - {"method": "fail", - "args": {"value": value}}) - self.fail("should have thrown rpc.RemoteError") - except rpc.RemoteError as exc: - self.assertEqual(int(exc.value), value) - - def test_nested_calls(self): - """Test that we can do an rpc.call inside another call.""" - class Nested(object): - @staticmethod - def echo(context, queue, value): - """Calls echo in the passed queue""" - LOG.debug(_("Nested received %(queue)s, %(value)s") - % locals()) - # TODO: so, it will replay the context and use the same REQID? - # that's bizarre. - ret = rpc.call(context, - queue, - {"method": "echo", - "args": {"value": value}}) - LOG.debug(_("Nested return %s"), ret) - return value - - nested = Nested() - conn = rpc.create_connection(True) - conn.create_consumer('nested', nested, False) - conn.consume_in_thread() - value = 42 - result = rpc.call(self.context, - 'nested', {"method": "echo", - "args": {"queue": "test", - "value": value}}) - conn.close() - self.assertEqual(value, result) - - -class TestReceiver(object): - """Simple Proxy class so the consumer has methods to call. - - Uses static methods because we aren't actually storing any state. - - """ - - @staticmethod - def echo(context, value): - """Simply returns whatever value is sent in.""" - LOG.debug(_("Received %s"), value) - return value - - @staticmethod - def context(context, value): - """Returns dictionary version of context.""" - LOG.debug(_("Received %s"), context) - return context.to_dict() - - @staticmethod - def echo_three_times(context, value): - context.reply(value) - context.reply(value + 1) - context.reply(value + 2) - - @staticmethod - def echo_three_times_yield(context, value): - yield value - yield value + 1 - yield value + 2 - - @staticmethod - def fail(context, value): - """Raises an exception with the value sent in.""" - raise Exception(value) -- cgit From db27d93bc195598a5dd0e7a35480281447cf4ea1 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 29 Aug 2011 15:08:32 -0700 Subject: doc string cleanup --- nova/rpc/impl_kombu.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index 83ee1b122..ffd6447da 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -290,7 +290,7 @@ class FanoutPublisher(Publisher): class Connection(object): - """Connection instance object.""" + """Connection object.""" def __init__(self): self.consumers = [] @@ -503,7 +503,18 @@ ConnectionPool = Pool( class ConnectionContext(object): + """The class that is actually returned to the caller of + create_connection(). This is a essentially a wrapper around + Connection that supports 'with' and can return a new Connection or + one from a pool. It will also catch when an instance of this class + is to be deleted so that we can return Connections to the pool on + exceptions and so forth without making the caller be responsible for + catching all exceptions and making sure to return a connection to + the pool. + """ + def __init__(self, pooled=True): + """Create a new connection, or get one from the pool""" self.connection = None if pooled: self.connection = ConnectionPool.get() @@ -512,9 +523,13 @@ class ConnectionContext(object): self.pooled = pooled def __enter__(self): + """with ConnectionContext() should return self""" return self def _done(self): + """If the connection came from a pool, clean it up and put it back. + If it did not come from a pool, close it. + """ if self.connection: if self.pooled: # Reset the connection so it's ready for the next caller @@ -533,19 +548,19 @@ class ConnectionContext(object): self.connection = None def __exit__(self, t, v, tb): - """end if 'with' statement. We're done here.""" + """end of 'with' statement. We're done here.""" self._done() def __del__(self): - """Put Connection back into the pool if this ConnectionContext - is being deleted - """ + """Caller is done with this connection. Make sure we cleaned up.""" self._done() def close(self): + """Caller is done with this connection.""" self._done() def __getattr__(self, key): + """Proxy all other calls to the Connection instance""" if self.connection: return getattr(self.connection, key) else: @@ -637,6 +652,7 @@ def _pack_context(msg, context): class RpcContext(context.RequestContext): + """Context that supports replying to a rpc.call""" def __init__(self, *args, **kwargs): msg_id = kwargs.pop('msg_id', None) self.msg_id = msg_id @@ -656,7 +672,7 @@ class MulticallWaiter(object): def done(self): self._done = True - self._connection = None + self._connection.close() def __call__(self, data): """The consume() callback will call this. Store the result.""" @@ -666,6 +682,7 @@ class MulticallWaiter(object): self._result = data['result'] def __iter__(self): + """Return a result until we get a 'None' response from consumer""" if self._done: raise StopIteration while True: -- cgit From 468ed475207b023cfa3eada48338d34375f55be2 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 29 Aug 2011 15:15:58 -0700 Subject: fix ajax console proxy for new create_consumer method --- bin/nova-ajax-console-proxy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/nova-ajax-console-proxy b/bin/nova-ajax-console-proxy index b3205ec56..23fb42fb5 100755 --- a/bin/nova-ajax-console-proxy +++ b/bin/nova-ajax-console-proxy @@ -114,8 +114,7 @@ class AjaxConsoleProxy(object): {'args': kwargs, 'last_activity': time.time()} self.conn = rpc.create_connection(new=True) - rpc.create_consumer( - self.conn, + self.conn.create_consumer( FLAGS.ajax_console_proxy_topic, TopicProxy) -- cgit From 345afb31678a1f94fcca6d63a4ab506e537c3a9c Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 29 Aug 2011 15:25:54 -0700 Subject: pep8 fix for test_rpc_common.py --- nova/tests/test_rpc_common.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_rpc_common.py b/nova/tests/test_rpc_common.py index b922be1df..4ab4e8a0e 100644 --- a/nova/tests/test_rpc_common.py +++ b/nova/tests/test_rpc_common.py @@ -49,8 +49,9 @@ class _BaseRpcTestCase(test.TestCase): def test_call_succeed_despite_multiple_returns(self): value = 42 - result = self.rpc.call(self.context, 'test', {"method": "echo_three_times", - "args": {"value": value}}) + result = self.rpc.call(self.context, 'test', + {"method": "echo_three_times", + "args": {"value": value}}) self.assertEqual(value + 2, result) def test_call_succeed_despite_multiple_returns_yield(self): -- cgit From 8965e567ce25e6b9718f1bca60b35f586bab985f Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 29 Aug 2011 15:26:26 -0700 Subject: remove unneeded connection= in carrot Consumer init --- nova/rpc/impl_carrot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/rpc/impl_carrot.py b/nova/rpc/impl_carrot.py index 6d504aaec..1d23c1853 100644 --- a/nova/rpc/impl_carrot.py +++ b/nova/rpc/impl_carrot.py @@ -171,7 +171,6 @@ class Consumer(messaging.Consumer): """ def __init__(self, *args, **kwargs): - connection = kwargs.get('connection') max_retries = FLAGS.rabbit_max_retries sleep_time = FLAGS.rabbit_retry_interval tries = 0 -- cgit From ebd47b7cb397f33c1e7c9f32dd5b77f7fd5d6642 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 29 Aug 2011 22:27:28 -0400 Subject: Update RequestContext so that it correctly sets self.is_admin from the roles array. Additionally add a bit of code to ignore case as well. Resolves issues when accessing admin API's w/ Keystone. --- nova/context.py | 2 +- nova/tests/test_context.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 nova/tests/test_context.py diff --git a/nova/context.py b/nova/context.py index b917a1d81..5c22641a0 100644 --- a/nova/context.py +++ b/nova/context.py @@ -38,7 +38,7 @@ class RequestContext(object): self.roles = roles or [] self.is_admin = is_admin if self.is_admin is None: - self.admin = 'admin' in self.roles + self.is_admin = 'admin' in [x.lower() for x in self.roles] self.read_deleted = read_deleted self.remote_address = remote_address if not timestamp: diff --git a/nova/tests/test_context.py b/nova/tests/test_context.py new file mode 100644 index 000000000..b2507fa59 --- /dev/null +++ b/nova/tests/test_context.py @@ -0,0 +1,33 @@ +# 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 nova import context +from nova import test + + +class ContextTestCase(test.TestCase): + + def test_request_context_sets_is_admin(self): + ctxt = context.RequestContext('111', + '222', + roles=['admin', 'weasel']) + self.assertEquals(ctxt.is_admin, True) + + def test_request_context_sets_is_admin_upcase(self): + ctxt = context.RequestContext('111', + '222', + roles=['Admin', 'weasel']) + self.assertEquals(ctxt.is_admin, True) -- cgit From a635027ddbeb73dfad8bbf2890f67cb1ed7511bf Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 30 Aug 2011 00:03:39 -0700 Subject: disassociate floating ips before re-associating, and prevent re-association of already associated floating ips in manager --- nova/exception.py | 4 ++ nova/network/api.py | 6 ++ nova/network/manager.py | 7 +++ .../api/openstack/contrib/test_floating_ips.py | 68 ++++++++++++++++++++-- nova/tests/test_network.py | 16 +++++ 5 files changed, 96 insertions(+), 5 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index 32981f4d5..b54981963 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -533,6 +533,10 @@ class NoMoreFloatingIps(FloatingIpNotFound): message = _("Zero floating ips available.") +class FloatingIpAlreadyInUse(NovaException): + message = _("Floating ip %(address) already in use by %(fixed_ip).") + + class NoFloatingIpsDefined(NotFound): message = _("Zero floating ips exist.") diff --git a/nova/network/api.py b/nova/network/api.py index d04474df3..78580d360 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -111,6 +111,12 @@ class API(base.Base): '(%(project)s)') % {'address': floating_ip['address'], 'project': context.project_id}) + + # If this address has been previously associated to a + # different instance, disassociate the floating_ip + if floating_ip['fixed_ip'] and floating_ip['fixed_ip'] is not fixed_ip: + self.disassociate_floating_ip(context, floating_ip['address']) + # NOTE(vish): if we are multi_host, send to the instances host if fixed_ip['network']['multi_host']: host = fixed_ip['instance']['host'] diff --git a/nova/network/manager.py b/nova/network/manager.py index b4605eea5..e6b30d1a0 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -280,6 +280,13 @@ class FloatingIP(object): def associate_floating_ip(self, context, floating_address, fixed_address): """Associates an floating ip to a fixed ip.""" + floating_ip = self.db.floating_ip_get_by_address(context, + floating_address) + if floating_ip['fixed_ip']: + raise exception.FloatingIpAlreadyInUse( + address=floating_ip['address'], + fixed_ip=floating_ip['fixed_ip']['address']) + self.db.floating_ip_fixed_ip_associate(context, floating_address, fixed_address) diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 568faf867..c14c29bf3 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -23,6 +23,7 @@ from nova import db from nova import test from nova import network from nova.tests.api.openstack import fakes +from nova.tests.api.openstack import test_servers from nova.api.openstack.contrib.floating_ips import FloatingIPController @@ -60,10 +61,38 @@ def compute_api_associate(self, context, instance_id, floating_ip): pass +def network_api_associate(self, context, floating_ip, fixed_ip): + pass + + def network_api_disassociate(self, context, floating_address): pass +def network_get_instance_nw_info(self, context, instance): + info = { + 'label': 'fake', + 'gateway': 'fake', + 'dhcp_server': 'fake', + 'broadcast': 'fake', + 'mac': 'fake', + 'vif_uuid': 'fake', + 'rxtx_cap': 'fake', + 'dns': [], + 'ips': [{'ip': '10.0.0.1'}], + 'should_create_bridge': False, + 'should_create_vlan': False} + + return [['ignore', info]] + + +def fake_instance_get(context, instance_id): + return { + "id": 1, + "user_id": 'fakeuser', + "project_id": '123'} + + class FloatingIpTest(test.TestCase): address = "10.10.10.10" @@ -79,9 +108,6 @@ class FloatingIpTest(test.TestCase): def setUp(self): super(FloatingIpTest, self).setUp() - self.controller = FloatingIPController() - fakes.stub_out_networking(self.stubs) - fakes.stub_out_rate_limiting(self.stubs) self.stubs.Set(network.api.API, "get_floating_ip", network_api_get_floating_ip) self.stubs.Set(network.api.API, "get_floating_ip_by_ip", @@ -92,10 +118,13 @@ class FloatingIpTest(test.TestCase): network_api_allocate) self.stubs.Set(network.api.API, "release_floating_ip", network_api_release) - self.stubs.Set(compute.api.API, "associate_floating_ip", - compute_api_associate) self.stubs.Set(network.api.API, "disassociate_floating_ip", network_api_disassociate) + self.stubs.Set(network.api.API, "get_instance_nw_info", + network_get_instance_nw_info) + self.stubs.Set(db.api, 'instance_get', + fake_instance_get) + self.context = context.get_admin_context() self._create_floating_ip() @@ -165,6 +194,8 @@ class FloatingIpTest(test.TestCase): self.assertEqual(res.status_int, 202) def test_add_floating_ip_to_instance(self): + self.stubs.Set(network.api.API, "associate_floating_ip", + network_api_associate) body = dict(addFloatingIp=dict(address='11.0.0.1')) req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = "POST" @@ -174,6 +205,33 @@ class FloatingIpTest(test.TestCase): resp = req.get_response(fakes.wsgi_app()) self.assertEqual(resp.status_int, 202) + def test_add_associated_floating_ip_to_instance(self): + def fake_fixed_ip_get_by_address(ctx, address, session=None): + return {'address': address, 'network': {'multi_host': None, + 'host': 'fake'}} + + self.disassociated = False + + def fake_network_api_disassociate(local_self, ctx, floating_address): + self.disassociated = True + + db.floating_ip_update(self.context, self.address, {'project_id': '123', + 'fixed_ip_id': 1}) + self.stubs.Set(network.api.API, "disassociate_floating_ip", + fake_network_api_disassociate) + self.stubs.Set(db.api, "fixed_ip_get_by_address", + fake_fixed_ip_get_by_address) + + body = dict(addFloatingIp=dict(address=self.address)) + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 202) + self.assertTrue(self.disassociated) + def test_remove_floating_ip_from_instance(self): body = dict(removeFloatingIp=dict(address='11.0.0.1')) req = webob.Request.blank('/v1.1/123/servers/test_inst/action') diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 0b8539442..25ff940f0 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -371,6 +371,22 @@ class VlanNetworkTestCase(test.TestCase): self.mox.ReplayAll() self.network.validate_networks(self.context, requested_networks) + def test_cant_associate_associated_floating_ip(self): + ctxt = context.RequestContext('testuser', 'testproject', + is_admin=False) + + def fake_floating_ip_get_by_address(context, address): + return {'address': '10.10.10.10', + 'fixed_ip': {'address': '10.0.0.1'}} + self.stubs.Set(self.network.db, 'floating_ip_get_by_address', + fake_floating_ip_get_by_address) + + self.assertRaises(exception.FloatingIpAlreadyInUse, + self.network.associate_floating_ip, + ctxt, + mox.IgnoreArg(), + mox.IgnoreArg()) + class CommonNetworkTestCase(test.TestCase): -- cgit From fdbb12e1e4b0b2cc28344510afb1c57620240901 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 30 Aug 2011 10:42:51 -0400 Subject: Fix a bad merge on my part, this fixes rebuilds\! --- nova/compute/manager.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 167be66db..0477db745 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -547,9 +547,6 @@ class ComputeManager(manager.SchedulerDependentManager): vm_state=vm_states.REBUILDING, task_state=task_states.BLOCK_DEVICE_MAPPING) - image_ref = kwargs.get('image_ref') - instance_ref.image_ref = image_ref - instance_ref.injected_files = kwargs.get('injected_files', []) network_info = self.network_api.get_instance_nw_info(context, instance_ref) @@ -572,11 +569,9 @@ class ComputeManager(manager.SchedulerDependentManager): power_state=current_power_state, vm_state=vm_states.ACTIVE, task_state=None, - image_ref=image_ref, launched_at=utils.utcnow()) - usage_info = utils.usage_from_instance(instance_ref, - image_ref=image_ref) + usage_info = utils.usage_from_instance(instance_ref) notifier.notify('compute.%s' % self.host, 'compute.instance.rebuild', notifier.INFO, -- cgit From 1155b734164eb5856d68c926f7bf64a37ae4a3a4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 30 Aug 2011 11:13:25 -0400 Subject: supporting changes-since --- nova/api/openstack/servers.py | 24 +++++++++++++----------- nova/db/sqlalchemy/api.py | 12 ++++++++---- nova/tests/api/openstack/test_servers.py | 24 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 27c67e79e..e0e40679a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -107,6 +107,14 @@ class Controller(object): LOG.error(reason) raise exception.InvalidInput(reason=reason) + if 'changes-since' in search_opts: + try: + parsed = utils.parse_isotime(search_opts['changes-since']) + except ValueError: + msg = _('Invalid changes-since value') + raise exc.HTTPBadRequest(explanation=msg) + search_opts['changes-since'] = parsed + # By default, compute's get_all() will return deleted instances. # If an admin hasn't specified a 'deleted' search option, we need # to filter out deleted instances by setting the filter ourselves. @@ -114,23 +122,17 @@ class Controller(object): # should return recently deleted images according to the API spec. if 'deleted' not in search_opts: - # Admin hasn't specified deleted filter if 'changes-since' not in search_opts: - # No 'changes-since', so we need to find non-deleted servers + # No 'changes-since', so we only want non-deleted servers search_opts['deleted'] = False - else: - # This is the default, but just in case.. - search_opts['deleted'] = True - - instance_list = self.compute_api.get_all( - context, search_opts=search_opts) - # FIXME(comstud): 'changes-since' is not fully implemented. Where - # should this be filtered? + instance_list = self.compute_api.get_all(context, + search_opts=search_opts) limited_list = self._limit_items(instance_list, req) servers = [self._build_view(req, inst, is_detail)['server'] - for inst in limited_list] + for inst in limited_list] + return dict(servers=servers) @scheduler_api.redirect_handler diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 65b09a65d..1685e9928 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -35,6 +35,7 @@ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload_all from sqlalchemy.sql import func +from sqlalchemy.sql.expression import desc from sqlalchemy.sql.expression import literal_column FLAGS = flags.FLAGS @@ -1250,12 +1251,17 @@ def instance_get_all_by_filters(context, filters): options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ options(joinedload('instance_type')).\ - filter_by(deleted=can_read_deleted(context)) + order_by(desc(models.Instance.updated_at)) # Make a copy of the filters dictionary to use going forward, as we'll # be modifying it and we shouldn't affect the caller's use of it. filters = filters.copy() + if 'changes-since' in filters: + changes_since = filters['changes-since'] + query_prefix = query_prefix.\ + filter(models.Instance.updated_at > changes_since) + if not context.is_admin: # If we're not admin context, add appropriate filter.. if context.project_id: @@ -1277,9 +1283,7 @@ def instance_get_all_by_filters(context, filters): query_prefix = _exact_match_filter(query_prefix, filter_name, filters.pop(filter_name)) - instances = query_prefix.\ - filter_by(deleted=can_read_deleted(context)).\ - all() + instances = query_prefix.all() if not instances: return [] diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 3559e6de5..9036d6552 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1257,6 +1257,30 @@ class ServersTest(test.TestCase): self.assertEqual(len(servers), 1) self.assertEqual(servers[0]['id'], 100) + def test_get_servers_allows_changes_since_v1_1(self): + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + self.assertTrue('changes-since' in search_opts) + changes_since = datetime.datetime(2011, 1, 24, 17, 8, 1) + self.assertEqual(search_opts['changes-since'], changes_since) + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + + params = 'changes-since=2011-01-24T17:08:01Z' + req = webob.Request.blank('/v1.1/fake/servers?%s' % params) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) + + def test_get_servers_allows_changes_since_bad_value_v1_1(self): + params = 'changes-since=asdf' + req = webob.Request.blank('/v1.1/fake/servers?%s' % params) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_get_servers_unknown_or_admin_options1(self): """Test getting servers by admin-only or unknown options. This tests when admin_api is off. Make sure the admin and -- cgit From a127747db0ab3405a768e8f680a2eb94ae8ce314 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 30 Aug 2011 11:44:19 -0400 Subject: adding an assert --- nova/tests/api/openstack/test_servers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 9036d6552..5f1ca466a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1263,6 +1263,7 @@ class ServersTest(test.TestCase): self.assertTrue('changes-since' in search_opts) changes_since = datetime.datetime(2011, 1, 24, 17, 8, 1) self.assertEqual(search_opts['changes-since'], changes_since) + self.assertTrue('deleted' not in search_opts) return [stub_instance(100)] self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) -- cgit From b515d427e05010ba5a984dd549cb6418629de50d Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Tue, 30 Aug 2011 18:18:23 +0200 Subject: With OS API, if the property 'ramdisk_id' isn't set on the AMI image, Nova can not instantiate it. With EC2 API, the AMI image can be instantiate. --- nova/api/openstack/create_instance_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 483ff4985..269e46dba 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -283,7 +283,7 @@ class CreateInstanceHelper(object): try: ramdisk_id = image_meta['properties']['ramdisk_id'] except KeyError: - raise exception.RamdiskNotFoundForImage(image_id=image_id) + ramdisk_id = None return kernel_id, ramdisk_id -- cgit From e8db401156cdb842ef7bf15e44e34ac5ae672e46 Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Tue, 30 Aug 2011 18:27:48 +0200 Subject: The exception 'RamdiskNotFoundForImage' is no longer used. --- nova/exception.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index 32981f4d5..aa3407480 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -402,10 +402,6 @@ 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.") -- cgit From 1c6d74a08dbb5b472e85e3d3a1fe2b3b8b9b89e3 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 30 Aug 2011 12:51:02 -0400 Subject: changing default sort to created_at --- 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 1685e9928..1e55d08e7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1251,7 +1251,7 @@ def instance_get_all_by_filters(context, filters): options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ options(joinedload('instance_type')).\ - order_by(desc(models.Instance.updated_at)) + order_by(desc(models.Instance.created_at)) # Make a copy of the filters dictionary to use going forward, as we'll # be modifying it and we shouldn't affect the caller's use of it. -- cgit From 980ae6aa2f3797e428beee6e383d8bd134175734 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 30 Aug 2011 14:48:02 -0400 Subject: yielding all the images --- nova/image/glance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/image/glance.py b/nova/image/glance.py index 9060f6a91..f2f246ba6 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -153,7 +153,8 @@ class GlanceImageService(service.BaseImageService): except KeyError: raise exception.ImagePaginationFailed() - self._fetch_images(fetch_func, **kwargs) + for image in self._fetch_images(fetch_func, **kwargs): + yield image def show(self, context, image_id): """Returns a dict with image data for the given opaque image id.""" -- cgit From 09fd29a9cc29904679cc8921adaf7559c23f347f Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 30 Aug 2011 14:52:23 -0400 Subject: fixing short-ciruit condition --- nova/image/glance.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/image/glance.py b/nova/image/glance.py index f2f246ba6..b5f52351f 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -141,12 +141,13 @@ class GlanceImageService(service.BaseImageService): """Paginate through results from glance server""" images = fetch_func(**kwargs) - for image in images: - yield image - else: + if not images: # break out of recursive loop to end pagination return + for image in images: + yield image + try: # attempt to advance the marker in order to fetch next page kwargs['marker'] = images[-1]['id'] -- cgit -- cgit From dcf5970dd9bed27201c593d7d053970a632e5eee Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 30 Aug 2011 12:01:18 -0700 Subject: make two functions instead of fast flag and add compute api commands instead of hitting db directly --- bin/instance-usage-audit | 5 ++- nova/api/openstack/contrib/simple_tenant_usage.py | 14 ++++----- nova/compute/api.py | 17 +++++++--- nova/db/api.py | 16 +++++++--- nova/db/sqlalchemy/api.py | 36 +++++++++++++++------- .../openstack/contrib/test_simple_tenant_usage.py | 10 +++--- 6 files changed, 62 insertions(+), 36 deletions(-) diff --git a/bin/instance-usage-audit b/bin/instance-usage-audit index a06c6b1b3..7ce5732e7 100755 --- a/bin/instance-usage-audit +++ b/bin/instance-usage-audit @@ -102,9 +102,8 @@ if __name__ == '__main__': logging.setup() begin, end = time_period(FLAGS.instance_usage_audit_period) print "Creating usages for %s until %s" % (str(begin), str(end)) - instances = db.instance_get_active_by_window(context.get_admin_context(), - begin, - end) + ctxt = context.get_admin_context() + instances = db.instance_get_active_by_window_joined(ctxt, begin, end) print "%s instances" % len(instances) for instance_ref in instances: usage_info = utils.usage_from_instance(instance_ref, diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index 16e712815..363ac1451 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -19,10 +19,9 @@ import urlparse import webob from datetime import datetime -from nova import db from nova import exception from nova import flags -from nova.compute import instance_types +from nova.compute import api from nova.api.openstack import extensions from nova.api.openstack import views from nova.db.sqlalchemy.session import get_session @@ -71,11 +70,11 @@ class SimpleTenantUsageController(object): def _tenant_usages_for_period(self, context, period_start, period_stop, tenant_id=None, detailed=True): - instances = db.instance_get_active_by_window(context, + compute_api = api.API() + instances = compute_api.get_active_by_window(context, period_start, period_stop, - tenant_id, - fast=True) + tenant_id) from nova import log as logging logging.info(instances) rval = {} @@ -90,8 +89,9 @@ class SimpleTenantUsageController(object): if not flavors.get(flavor_type): try: - flavors[flavor_type] = db.instance_type_get(context, - flavor_type) + it_ref = compute_api.get_instance_type(context, + flavor_type) + flavors[flavor_type] = it_ref except exception.InstanceTypeNotFound: # can't bill if there is no instance type continue diff --git a/nova/compute/api.py b/nova/compute/api.py index 3b4bde8ea..53bab53a4 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -19,13 +19,11 @@ """Handles all requests relating to instances (guest vms).""" -import eventlet import novaclient import re import time from nova import block_device -from nova import db from nova import exception from nova import flags import nova.image @@ -237,7 +235,7 @@ class API(base.Base): self.ensure_default_security_group(context) if key_data is None and key_name: - key_pair = db.key_pair_get(context, context.user_id, key_name) + key_pair = self.db.key_pair_get(context, context.user_id, key_name) key_data = key_pair['public_key'] if reservation_id is None: @@ -802,6 +800,15 @@ class API(base.Base): "args": {"topic": FLAGS.compute_topic, "instance_id": instance_id}}) + def get_active_by_window(self, context, begin, end=None, project_id=None): + """Get instances that were continuously active over a window.""" + return self.db.instance_get_active_by_window(context, begin, end, + project_id) + + def get_instance_type(self, context, instance_type_id): + """Get an instance type by instance type id.""" + return self.db.instance_type_get(context, instance_type_id) + def get(self, context, instance_id): """Get a single instance with the given instance_id.""" # NOTE(sirp): id used to be exclusively integer IDs; now we're @@ -1001,7 +1008,7 @@ class API(base.Base): :param extra_properties: dict of extra image properties to include """ - instance = db.api.instance_get(context, instance_id) + instance = self.db.api.instance_get(context, instance_id) properties = {'instance_uuid': instance['uuid'], 'user_id': str(context.user_id), 'image_state': 'creating', @@ -1026,7 +1033,7 @@ class API(base.Base): def rebuild(self, context, instance_id, image_href, admin_password, name=None, metadata=None, files_to_inject=None): """Rebuild the given instance with the provided metadata.""" - instance = db.api.instance_get(context, instance_id) + instance = self.db.instance_get(context, instance_id) if instance["state"] == power_state.BUILDING: msg = _("Instance already building") diff --git a/nova/db/api.py b/nova/db/api.py index 3233985b6..148887635 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -496,14 +496,20 @@ def instance_get_all_by_filters(context, filters): return IMPL.instance_get_all_by_filters(context, filters) -def instance_get_active_by_window(context, begin, end=None, - project_id=None, fast=False): +def instance_get_active_by_window(context, begin, end=None, project_id=None): """Get instances active during a certain time window. - Setting fast to true will stop all joinedloads. Specifying a project_id will filter for a certain project.""" - return IMPL.instance_get_active_by_window(context, begin, end, - project_id, fast) + return IMPL.instance_get_active_by_window(context, begin, end, project_id) + + +def instance_get_active_by_window_joined(context, begin, end=None, + project_id=None): + """Get instances and joins active during a certain time window. + + Specifying a project_id will filter for a certain project.""" + return IMPL.instance_get_active_by_window_joined(context, begin, end, + project_id) def instance_get_all_by_user(context, user_id): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index c4cc199eb..d76dc22ed 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1306,19 +1306,33 @@ def instance_get_all_by_filters(context, filters): return instances -@require_admin_context -def instance_get_active_by_window(context, begin, end=None, - project_id=None, fast=False): - """Return instances that were continuously active over the given window""" +@require_context +def instance_get_active_by_window(context, begin, end=None, project_id=None): + """Return instances that were continuously active over window.""" session = get_session() - query = session.query(models.Instance) - if not fast: - query = query.options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ips.network')).\ - options(joinedload('instance_type')) + query = session.query(models.Instance).\ + filter(models.Instance.launched_at < begin) + if end: + query = query.filter(or_(models.Instance.terminated_at == None, + models.Instance.terminated_at > end)) + else: + query = query.filter(models.Instance.terminated_at == None) + if project_id: + query = query.filter_by(project_id=project_id) + return query.all() + - query = query.filter(models.Instance.launched_at < begin) +@require_admin_context +def instance_get_active_by_window_joined(context, begin, end=None, + project_id=None): + """Return instances and joins that were continuously active over window.""" + session = get_session() + query = session.query(models.Instance).\ + options(joinedload_all('fixed_ips.floating_ips')).\ + options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ips.network')).\ + options(joinedload('instance_type')).\ + filter(models.Instance.launched_at < begin) if end: query = query.filter(or_(models.Instance.terminated_at == None, models.Instance.terminated_at > end)) diff --git a/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py b/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py index 2bd619820..de0a6d779 100644 --- a/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py +++ b/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py @@ -20,9 +20,9 @@ import json import webob from nova import context -from nova import db from nova import flags from nova import test +from nova.compute import api from nova.tests.api.openstack import fakes @@ -39,7 +39,7 @@ STOP = datetime.datetime.utcnow() START = STOP - datetime.timedelta(hours=HOURS) -def fake_instance_type_get(context, instance_type_id): +def fake_instance_type_get(self, context, instance_type_id): return {'id': 1, 'vcpus': VCPUS, 'local_gb': LOCAL_GB, @@ -59,7 +59,7 @@ def get_fake_db_instance(start, end, instance_id, tenant_id): 'launched_at': start, 'terminated_at': end} -def fake_instance_get_active_by_window(context, begin, end, project_id, fast): +def fake_instance_get_active_by_window(self, context, begin, end, project_id): return [get_fake_db_instance(START, STOP, x, @@ -70,9 +70,9 @@ def fake_instance_get_active_by_window(context, begin, end, project_id, fast): class SimpleTenantUsageTest(test.TestCase): def setUp(self): super(SimpleTenantUsageTest, self).setUp() - self.stubs.Set(db, "instance_type_get", + self.stubs.Set(api.API, "get_instance_type", fake_instance_type_get) - self.stubs.Set(db, "instance_get_active_by_window", + self.stubs.Set(api.API, "get_active_by_window", fake_instance_get_active_by_window) self.admin_context = context.RequestContext('fakeadmin_0', 'faketenant_0', -- cgit From 85e182e72d8f15678234701f6b254bf6c8e17f3a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 30 Aug 2011 12:08:53 -0700 Subject: fix a bunch of direct usages of db in compute api --- nova/compute/api.py | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 53bab53a4..f1099576e 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -387,9 +387,9 @@ class API(base.Base): security_groups = [] for security_group_name in security_group: - group = db.security_group_get_by_name(context, - context.project_id, - security_group_name) + group = self.db.security_group_get_by_name(context, + context.project_id, + security_group_name) security_groups.append(group['id']) for security_group_id in security_groups: @@ -549,8 +549,9 @@ class API(base.Base): def has_finished_migration(self, context, instance_uuid): """Returns true if an instance has a finished migration.""" try: - db.migration_get_by_instance_and_status(context, instance_uuid, - 'finished') + self.db.migration_get_by_instance_and_status(context, + instance_uuid, + 'finished') return True except exception.NotFound: return False @@ -564,14 +565,15 @@ class API(base.Base): :param context: the security context """ try: - db.security_group_get_by_name(context, context.project_id, - 'default') + self.db.security_group_get_by_name(context, + context.project_id, + 'default') except exception.NotFound: values = {'name': 'default', 'description': 'default', 'user_id': context.user_id, 'project_id': context.project_id} - db.security_group_create(context, values) + self.db.security_group_create(context, values) def trigger_security_group_rules_refresh(self, context, security_group_id): """Called when a rule is added to or removed from a security_group.""" @@ -636,7 +638,7 @@ class API(base.Base): """Called when a rule is added to or removed from a security_group""" hosts = [x['host'] for (x, idx) - in db.service_get_all_compute_sorted(context)] + in self.db.service_get_all_compute_sorted(context)] for host in hosts: rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), @@ -664,11 +666,11 @@ class API(base.Base): def add_security_group(self, context, instance_id, security_group_name): """Add security group to the instance""" - security_group = db.security_group_get_by_name(context, - context.project_id, - security_group_name) + security_group = self.db.security_group_get_by_name(context, + context.project_id, + security_group_name) # check if the server exists - inst = db.instance_get(context, instance_id) + inst = self.db.instance_get(context, instance_id) #check if the security group is associated with the server if self._is_security_group_associated_with_server(security_group, instance_id): @@ -680,21 +682,21 @@ class API(base.Base): if inst['state'] != power_state.RUNNING: raise exception.InstanceNotRunning(instance_id=instance_id) - db.instance_add_security_group(context.elevated(), - instance_id, - security_group['id']) + self.db.instance_add_security_group(context.elevated(), + instance_id, + security_group['id']) rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, inst['host']), + self.db.queue_get_for(context, FLAGS.compute_topic, inst['host']), {"method": "refresh_security_group_rules", "args": {"security_group_id": security_group['id']}}) def remove_security_group(self, context, instance_id, security_group_name): """Remove the security group associated with the instance""" - security_group = db.security_group_get_by_name(context, - context.project_id, - security_group_name) + security_group = self.db.security_group_get_by_name(context, + context.project_id, + security_group_name) # check if the server exists - inst = db.instance_get(context, instance_id) + inst = self.db.instance_get(context, instance_id) #check if the security group is associated with the server if not self._is_security_group_associated_with_server(security_group, instance_id): @@ -706,11 +708,11 @@ class API(base.Base): if inst['state'] != power_state.RUNNING: raise exception.InstanceNotRunning(instance_id=instance_id) - db.instance_remove_security_group(context.elevated(), - instance_id, - security_group['id']) + self.db.instance_remove_security_group(context.elevated(), + instance_id, + security_group['id']) rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, inst['host']), + self.db.queue_get_for(context, FLAGS.compute_topic, inst['host']), {"method": "refresh_security_group_rules", "args": {"security_group_id": security_group['id']}}) -- cgit From 2fcc6da8ba528c5169f7394d57f90ccd2754a23c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 30 Aug 2011 12:14:25 -0700 Subject: pep8, fix fakes --- nova/api/openstack/contrib/simple_tenant_usage.py | 1 + nova/compute/api.py | 12 ++++++------ nova/tests/api/openstack/contrib/test_createserverext.py | 2 ++ nova/tests/api/openstack/contrib/test_simple_tenant_usage.py | 2 +- nova/tests/api/openstack/test_servers.py | 1 + 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index 363ac1451..e81aef66e 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -30,6 +30,7 @@ from webob import exc FLAGS = flags.FLAGS + class SimpleTenantUsageController(object): def _hours_for(self, instance, period_start, period_stop): launched_at = instance['launched_at'] diff --git a/nova/compute/api.py b/nova/compute/api.py index f1099576e..0074028e2 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -388,8 +388,8 @@ class API(base.Base): security_groups = [] for security_group_name in security_group: group = self.db.security_group_get_by_name(context, - context.project_id, - security_group_name) + context.project_id, + security_group_name) security_groups.append(group['id']) for security_group_id in security_groups: @@ -667,8 +667,8 @@ class API(base.Base): def add_security_group(self, context, instance_id, security_group_name): """Add security group to the instance""" security_group = self.db.security_group_get_by_name(context, - context.project_id, - security_group_name) + context.project_id, + security_group_name) # check if the server exists inst = self.db.instance_get(context, instance_id) #check if the security group is associated with the server @@ -693,8 +693,8 @@ class API(base.Base): def remove_security_group(self, context, instance_id, security_group_name): """Remove the security group associated with the instance""" security_group = self.db.security_group_get_by_name(context, - context.project_id, - security_group_name) + context.project_id, + security_group_name) # check if the server exists inst = self.db.instance_get(context, instance_id) #check if the security group is associated with the server diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index e5eed14fe..f6d9ba784 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -23,6 +23,7 @@ from xml.dom import minidom import stubout import webob +from nova import db from nova import exception from nova import flags from nova import test @@ -76,6 +77,7 @@ class CreateserverextTest(test.TestCase): def __init__(self): self.injected_files = None self.networks = None + self.db = db def create(self, *args, **kwargs): if 'injected_files' in kwargs: diff --git a/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py b/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py index de0a6d779..2430b9d51 100644 --- a/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py +++ b/nova/tests/api/openstack/contrib/test_simple_tenant_usage.py @@ -26,7 +26,6 @@ from nova.compute import api from nova.tests.api.openstack import fakes - FLAGS = flags.FLAGS SERVERS = 5 @@ -59,6 +58,7 @@ def get_fake_db_instance(start, end, instance_id, tenant_id): 'launched_at': start, 'terminated_at': end} + def fake_instance_get_active_by_window(self, context, begin, end, project_id): return [get_fake_db_instance(START, STOP, diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 3559e6de5..d065f48b6 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3229,6 +3229,7 @@ class TestServerInstanceCreation(test.TestCase): def __init__(self): self.injected_files = None self.networks = None + self.db = db def create(self, *args, **kwargs): if 'injected_files' in kwargs: -- cgit From 5cf27b5a338f7821f82c91df5889159b56fa0bb6 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 30 Aug 2011 12:41:30 -0700 Subject: fix remaining tests --- nova/api/openstack/contrib/simple_tenant_usage.py | 2 +- nova/compute/api.py | 2 +- .../api/openstack/contrib/test_security_groups.py | 72 +++++++++++----------- nova/tests/api/openstack/test_extensions.py | 1 + nova/tests/api/openstack/test_server_actions.py | 2 +- 5 files changed, 40 insertions(+), 39 deletions(-) diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index e81aef66e..69b38e229 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -212,7 +212,7 @@ class SimpleTenantUsageController(object): class Simple_tenant_usage(extensions.ExtensionDescriptor): def get_name(self): - return "Simple_tenant_usage" + return "SimpleTenantUsage" def get_alias(self): return "os-simple-tenant-usage" diff --git a/nova/compute/api.py b/nova/compute/api.py index 0074028e2..205207d66 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1010,7 +1010,7 @@ class API(base.Base): :param extra_properties: dict of extra image properties to include """ - instance = self.db.api.instance_get(context, instance_id) + instance = self.db.instance_get(context, instance_id) properties = {'instance_uuid': instance['uuid'], 'user_id': str(context.user_id), 'image_state': 'creating', diff --git a/nova/tests/api/openstack/contrib/test_security_groups.py b/nova/tests/api/openstack/contrib/test_security_groups.py index bc1536911..0816a6312 100644 --- a/nova/tests/api/openstack/contrib/test_security_groups.py +++ b/nova/tests/api/openstack/contrib/test_security_groups.py @@ -360,7 +360,7 @@ class TestSecurityGroups(test.TestCase): def test_associate_by_invalid_server_id(self): body = dict(addSecurityGroup=dict(name='test')) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group) req = webob.Request.blank('/v1.1/123/servers/invalid/action') req.headers['Content-Type'] = 'application/json' @@ -372,7 +372,7 @@ class TestSecurityGroups(test.TestCase): def test_associate_without_body(self): req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(addSecurityGroup=None) - self.stubs.Set(nova.db, 'instance_get', return_server) + self.stubs.Set(nova.db.api, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -382,7 +382,7 @@ class TestSecurityGroups(test.TestCase): def test_associate_no_security_group_name(self): req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(addSecurityGroup=dict()) - self.stubs.Set(nova.db, 'instance_get', return_server) + self.stubs.Set(nova.db.api, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -392,7 +392,7 @@ class TestSecurityGroups(test.TestCase): def test_associate_security_group_name_with_whitespaces(self): req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(addSecurityGroup=dict(name=" ")) - self.stubs.Set(nova.db, 'instance_get', return_server) + self.stubs.Set(nova.db.api, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -400,9 +400,9 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_associate_non_existing_instance(self): - self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant) + self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) body = dict(addSecurityGroup=dict(name="test")) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group) req = webob.Request.blank('/v1.1/123/servers/10000/action') req.headers['Content-Type'] = 'application/json' @@ -412,8 +412,8 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 404) def test_associate_non_running_instance(self): - self.stubs.Set(nova.db, 'instance_get', return_non_running_server) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'instance_get', return_non_running_server) + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group_without_instances) body = dict(addSecurityGroup=dict(name="test")) req = webob.Request.blank('/v1.1/123/servers/1/action') @@ -424,8 +424,8 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_associate_already_associated_security_group_to_instance(self): - self.stubs.Set(nova.db, 'instance_get', return_server) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'instance_get', return_server) + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group) body = dict(addSecurityGroup=dict(name="test")) req = webob.Request.blank('/v1.1/123/servers/1/action') @@ -436,12 +436,12 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_associate(self): - self.stubs.Set(nova.db, 'instance_get', return_server) - self.mox.StubOutWithMock(nova.db, 'instance_add_security_group') - nova.db.instance_add_security_group(mox.IgnoreArg(), + self.stubs.Set(nova.db.api, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db.api, 'instance_add_security_group') + nova.db.api.instance_add_security_group(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group_without_instances) self.mox.ReplayAll() @@ -454,12 +454,12 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 202) def test_associate_xml(self): - self.stubs.Set(nova.db, 'instance_get', return_server) - self.mox.StubOutWithMock(nova.db, 'instance_add_security_group') - nova.db.instance_add_security_group(mox.IgnoreArg(), + self.stubs.Set(nova.db.api, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db.api, 'instance_add_security_group') + nova.db.api.instance_add_security_group(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group_without_instances) self.mox.ReplayAll() @@ -483,7 +483,7 @@ class TestSecurityGroups(test.TestCase): def test_disassociate_by_invalid_server_id(self): body = dict(removeSecurityGroup=dict(name='test')) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group) req = webob.Request.blank('/v1.1/123/servers/invalid/action') req.headers['Content-Type'] = 'application/json' @@ -495,7 +495,7 @@ class TestSecurityGroups(test.TestCase): def test_disassociate_without_body(self): req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(removeSecurityGroup=None) - self.stubs.Set(nova.db, 'instance_get', return_server) + self.stubs.Set(nova.db.api, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -505,7 +505,7 @@ class TestSecurityGroups(test.TestCase): def test_disassociate_no_security_group_name(self): req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(removeSecurityGroup=dict()) - self.stubs.Set(nova.db, 'instance_get', return_server) + self.stubs.Set(nova.db.api, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -515,7 +515,7 @@ class TestSecurityGroups(test.TestCase): def test_disassociate_security_group_name_with_whitespaces(self): req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(removeSecurityGroup=dict(name=" ")) - self.stubs.Set(nova.db, 'instance_get', return_server) + self.stubs.Set(nova.db.api, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -523,9 +523,9 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_disassociate_non_existing_instance(self): - self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant) + self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) body = dict(removeSecurityGroup=dict(name="test")) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group) req = webob.Request.blank('/v1.1/123/servers/10000/action') req.headers['Content-Type'] = 'application/json' @@ -535,8 +535,8 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 404) def test_disassociate_non_running_instance(self): - self.stubs.Set(nova.db, 'instance_get', return_non_running_server) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'instance_get', return_non_running_server) + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group) body = dict(removeSecurityGroup=dict(name="test")) req = webob.Request.blank('/v1.1/123/servers/1/action') @@ -547,8 +547,8 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_disassociate_already_associated_security_group_to_instance(self): - self.stubs.Set(nova.db, 'instance_get', return_server) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'instance_get', return_server) + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group_without_instances) body = dict(removeSecurityGroup=dict(name="test")) req = webob.Request.blank('/v1.1/123/servers/1/action') @@ -559,12 +559,12 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_disassociate(self): - self.stubs.Set(nova.db, 'instance_get', return_server) - self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group') - nova.db.instance_remove_security_group(mox.IgnoreArg(), + self.stubs.Set(nova.db.api, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db.api, 'instance_remove_security_group') + nova.db.api.instance_remove_security_group(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group) self.mox.ReplayAll() @@ -577,12 +577,12 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 202) def test_disassociate_xml(self): - self.stubs.Set(nova.db, 'instance_get', return_server) - self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group') - nova.db.instance_remove_security_group(mox.IgnoreArg(), + self.stubs.Set(nova.db.api, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db.api, 'instance_remove_security_group') + nova.db.api.instance_remove_security_group(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) - self.stubs.Set(nova.db, 'security_group_get_by_name', + self.stubs.Set(nova.db.api, 'security_group_get_by_name', return_security_group) self.mox.ReplayAll() diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 05267d8fb..31443242b 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -95,6 +95,7 @@ class ExtensionControllerTest(test.TestCase): "Quotas", "Rescue", "SecurityGroups", + "SimpleTenantUsage", "VSAs", "VirtualInterfaces", "Volumes", diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 3dfdeb79c..c9c33abbd 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -248,7 +248,7 @@ class ServerActionsTest(test.TestCase): def fake_migration_get(*args): return {} - self.stubs.Set(nova.db, 'migration_get_by_instance_and_status', + self.stubs.Set(nova.db.api, 'migration_get_by_instance_and_status', fake_migration_get) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) -- cgit From 4f65e0153c22886b118bdb92402b91d9b209632c Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 30 Aug 2011 13:53:01 -0700 Subject: restore old way FLAGS.rpc_backend worked.. no short name support for consistency --- nova/rpc/__init__.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/nova/rpc/__init__.py b/nova/rpc/__init__.py index fe50fb476..4f57a345d 100644 --- a/nova/rpc/__init__.py +++ b/nova/rpc/__init__.py @@ -23,18 +23,10 @@ from nova import flags FLAGS = flags.FLAGS flags.DEFINE_string('rpc_backend', - 'kombu', + 'nova.rpc.impl_kombu', "The messaging module to use, defaults to kombu.") -impl_table = {'kombu': 'nova.rpc.impl_kombu', - 'amqp': 'nova.rpc.impl_kombu', - 'carrot': 'nova.rpc.impl_carrot'} - - -# rpc_backend can be a short name like 'kombu', or it can be the full -# module name -RPCIMPL = import_object(impl_table.get(FLAGS.rpc_backend, - FLAGS.rpc_backend)) +RPCIMPL = import_object(FLAGS.rpc_backend) def create_connection(new=True): -- cgit From b6c306b1a207fd2c5ee2e53d841fd8e60c2fd8e1 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 30 Aug 2011 13:55:06 -0700 Subject: add kombu to pip-requires and contrib/nova.sh --- contrib/nova.sh | 2 +- tools/pip-requires | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/nova.sh b/contrib/nova.sh index 7994e5133..16cddebd5 100755 --- a/contrib/nova.sh +++ b/contrib/nova.sh @@ -81,7 +81,7 @@ if [ "$CMD" == "install" ]; then sudo apt-get install -y python-netaddr python-pastedeploy python-eventlet sudo apt-get install -y python-novaclient python-glance python-cheetah sudo apt-get install -y python-carrot python-tempita python-sqlalchemy - sudo apt-get install -y python-suds + sudo apt-get install -y python-suds python-kombu if [ "$USE_IPV6" == 1 ]; then diff --git a/tools/pip-requires b/tools/pip-requires index 60b502ffd..66d6a48d9 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -8,6 +8,7 @@ anyjson==0.2.4 boto==1.9b carrot==0.10.5 eventlet +kombu lockfile==0.8 lxml==2.3 python-novaclient==2.6.0 -- cgit From e326acf46748904704dd97f511927559dc2480f2 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 30 Aug 2011 15:05:39 -0700 Subject: Fix for LP Bug #837534 --- nova/api/openstack/create_instance_helper.py | 13 ++++++ .../api/openstack/contrib/test_createserverext.py | 46 +++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 483ff4985..019283fdf 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -138,7 +138,10 @@ class CreateInstanceHelper(object): raise exc.HTTPBadRequest(explanation=msg) zone_blob = server_dict.get('blob') + user_data = server_dict.get('user_data') + self._validate_user_data(user_data) + availability_zone = server_dict.get('availability_zone') name = server_dict['name'] self._validate_server_name(name) @@ -370,6 +373,16 @@ class CreateInstanceHelper(object): return networks + def _validate_user_data(self, user_data): + """Check if the user_data is encoded properly""" + if not user_data: + return + try: + user_data = base64.b64decode(user_data) + except TypeError: + expl = _('Userdata content cannot be decoded') + raise exc.HTTPBadRequest(explanation=expl) + class ServerXMLDeserializer(wsgi.XMLDeserializer): """ diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index e5eed14fe..65c7c1682 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -37,7 +37,8 @@ import nova.image.fake import nova.rpc from nova.tests.api.openstack import fakes - +from nova import log as logging +LOG = logging.getLogger('nova.api.openstack.test') FLAGS = flags.FLAGS FLAGS.verbose = True @@ -76,6 +77,7 @@ class CreateserverextTest(test.TestCase): def __init__(self): self.injected_files = None self.networks = None + self.user_data = None def create(self, *args, **kwargs): if 'injected_files' in kwargs: @@ -87,6 +89,10 @@ class CreateserverextTest(test.TestCase): self.networks = kwargs['requested_networks'] else: self.networks = None + + if 'user_data' in kwargs: + self.user_data = kwargs['user_data'] + return [{'id': '1234', 'display_name': 'fakeinstance', 'uuid': FAKE_UUID, 'created_at': "", @@ -119,6 +125,14 @@ class CreateserverextTest(test.TestCase): server['networks'] = network_list return {'server': server} + def _create_user_data_request_dict(self, user_data): + server = {} + server['name'] = 'new-server-test' + server['imageRef'] = 1 + server['flavorRef'] = 1 + server['user_data'] = user_data + return {'server': server} + def _get_create_request_json(self, body_dict): req = webob.Request.blank('/v1.1/123/os-create-server-ext') req.headers['Content-Type'] = 'application/json' @@ -178,6 +192,13 @@ class CreateserverextTest(test.TestCase): self._run_create_instance_with_mock_compute_api(request) return request, response, compute_api.networks + def _create_instance_with_user_data_json(self, networks): + body_dict = self._create_user_data_request_dict(networks) + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + return request, response, compute_api.user_data + def _create_instance_with_networks_xml(self, networks): body_dict = self._create_networks_request_dict(networks) request = self._get_create_request_xml(body_dict) @@ -188,6 +209,7 @@ class CreateserverextTest(test.TestCase): def test_create_instance_with_no_networks(self): request, response, networks = \ self._create_instance_with_networks_json(networks=None) + LOG.debug(response) self.assertEquals(response.status_int, 202) self.assertEquals(networks, None) @@ -304,3 +326,25 @@ class CreateserverextTest(test.TestCase): self.assertEquals(response.status_int, 202) self.assertEquals(compute_api.networks, [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)]) + + def test_create_instance_with_userdata(self): + user_data_contents = '#!/bin/bash\necho "Oh no!"\n' + user_data_contents = base64.b64encode(user_data_contents) + request, response, user_data = \ + self._create_instance_with_user_data_json(user_data_contents) + self.assertEquals(response.status_int, 202) + self.assertEquals(user_data, user_data_contents) + + def test_create_instance_with_userdata_none(self): + user_data_contents = None + request, response, user_data = \ + self._create_instance_with_user_data_json(user_data_contents) + self.assertEquals(response.status_int, 202) + self.assertEquals(user_data, user_data_contents) + + def test_create_instance_with_userdata_with_non_b64_content(self): + user_data_contents = '#!/bin/bash\necho "Oh no!"\n' + request, response, user_data = \ + self._create_instance_with_user_data_json(user_data_contents) + self.assertEquals(response.status_int, 400) + self.assertEquals(user_data, None) -- cgit From 476101d81cf81e6035b44e2257c1bcd8e958043a Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 30 Aug 2011 15:09:08 -0700 Subject: Removed debug messages --- nova/tests/api/openstack/contrib/test_createserverext.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index 65c7c1682..d8a5c9e55 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -37,8 +37,7 @@ import nova.image.fake import nova.rpc from nova.tests.api.openstack import fakes -from nova import log as logging -LOG = logging.getLogger('nova.api.openstack.test') + FLAGS = flags.FLAGS FLAGS.verbose = True @@ -209,7 +208,6 @@ class CreateserverextTest(test.TestCase): def test_create_instance_with_no_networks(self): request, response, networks = \ self._create_instance_with_networks_json(networks=None) - LOG.debug(response) self.assertEquals(response.status_int, 202) self.assertEquals(networks, None) -- cgit From 4ec4ddd2e6465f0483ecf50d430458169ad4c348 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 30 Aug 2011 15:12:43 -0700 Subject: make default carrot again and delay the import in rpc/__init__.py --- nova/rpc/__init__.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/nova/rpc/__init__.py b/nova/rpc/__init__.py index 4f57a345d..32509fff6 100644 --- a/nova/rpc/__init__.py +++ b/nova/rpc/__init__.py @@ -23,27 +23,33 @@ from nova import flags FLAGS = flags.FLAGS flags.DEFINE_string('rpc_backend', - 'nova.rpc.impl_kombu', - "The messaging module to use, defaults to kombu.") + 'nova.rpc.impl_carrot', + "The messaging module to use, defaults to carrot.") -RPCIMPL = import_object(FLAGS.rpc_backend) +_RPCIMPL = None + +def get_impl(): + global _RPCIMPL + if _RPCIMPL is None: + _RPCIMPL = import_object(FLAGS.rpc_backend) + return _RPCIMPL def create_connection(new=True): - return RPCIMPL.create_connection(new=new) + return get_impl().create_connection(new=new) def call(context, topic, msg): - return RPCIMPL.call(context, topic, msg) + return get_impl().call(context, topic, msg) def cast(context, topic, msg): - return RPCIMPL.cast(context, topic, msg) + return get_impl().cast(context, topic, msg) def fanout_cast(context, topic, msg): - return RPCIMPL.fanout_cast(context, topic, msg) + return get_impl().fanout_cast(context, topic, msg) def multicall(context, topic, msg): - return RPCIMPL.multicall(context, topic, msg) + return get_impl().multicall(context, topic, msg) -- cgit From 5ccbce699880557f9c58d4d403487979d3604ccf Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 30 Aug 2011 15:30:16 -0700 Subject: pep8 fix --- nova/rpc/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/rpc/__init__.py b/nova/rpc/__init__.py index 32509fff6..e29cd80e1 100644 --- a/nova/rpc/__init__.py +++ b/nova/rpc/__init__.py @@ -28,7 +28,9 @@ flags.DEFINE_string('rpc_backend', _RPCIMPL = None + def get_impl(): + """Delay import of rpc_backend until FLAGS are loaded.""" global _RPCIMPL if _RPCIMPL is None: _RPCIMPL = import_object(FLAGS.rpc_backend) -- cgit From 2e12e975ee9d4ab7a17eebb0e36714b56d6b1779 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 30 Aug 2011 15:52:25 -0700 Subject: logging change when rpc pool creates new connection --- nova/rpc/impl_carrot.py | 2 +- nova/rpc/impl_kombu.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/rpc/impl_carrot.py b/nova/rpc/impl_carrot.py index 1d23c1853..303a4ff88 100644 --- a/nova/rpc/impl_carrot.py +++ b/nova/rpc/impl_carrot.py @@ -152,7 +152,7 @@ class Pool(pools.Pool): # TODO(comstud): Timeout connections not used in a while def create(self): - LOG.debug('Creating new connection') + LOG.debug('Pool creating new connection') return Connection.instance(new=True) # Create a ConnectionPool to use for RPC calls. We'll order the diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index ffd6447da..8242bd177 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -491,7 +491,7 @@ class Pool(pools.Pool): # TODO(comstud): Timeout connections not used in a while def create(self): - LOG.debug('Creating new connection') + LOG.debug('Pool creating new connection') return Connection() # Create a ConnectionPool to use for RPC calls. We'll order the -- cgit From 66aa9a6306cde5db2039daaf11a8422619560a33 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 30 Aug 2011 20:45:51 -0700 Subject: fix FloatingIpAlreadyInUse to use correct string pattern, convert ApiErrors to 400 responses --- nova/api/openstack/contrib/floating_ips.py | 6 +++- nova/exception.py | 2 +- .../api/openstack/contrib/test_floating_ips.py | 32 ++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 40086f778..ad3094d52 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -138,7 +138,11 @@ class Floating_ips(extensions.ExtensionDescriptor): msg = _("Address not specified") raise webob.exc.HTTPBadRequest(explanation=msg) - self.compute_api.associate_floating_ip(context, instance_id, address) + try: + self.compute_api.associate_floating_ip(context, instance_id, + address) + except exception.ApiError, e: + raise webob.exc.HTTPBadRequest(explanation=e.message) return webob.Response(status_int=202) diff --git a/nova/exception.py b/nova/exception.py index b54981963..caa65146d 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -534,7 +534,7 @@ class NoMoreFloatingIps(FloatingIpNotFound): class FloatingIpAlreadyInUse(NovaException): - message = _("Floating ip %(address) already in use by %(fixed_ip).") + message = _("Floating ip %(address)s already in use by %(fixed_ip)s.") class NoFloatingIpsDefined(NotFound): diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index c14c29bf3..08148278d 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -205,6 +205,38 @@ class FloatingIpTest(test.TestCase): resp = req.get_response(fakes.wsgi_app()) self.assertEqual(resp.status_int, 202) + def test_associate_floating_ip_to_instance_wrong_project_id(self): + def fake_fixed_ip_get_by_address(ctx, address, session=None): + return {'address': address, 'network': {'multi_host': None, + 'host': 'fake'}} + self.stubs.Set(db.api, "fixed_ip_get_by_address", + fake_fixed_ip_get_by_address) + db.floating_ip_update(self.context, self.address, {'project_id': 'bad', + 'fixed_ip_id': 1}) + body = dict(addFloatingIp=dict(address=self.address)) + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 400) + + def test_associate_floating_ip_to_instance_no_project_id(self): + def fake_fixed_ip_get_by_address(ctx, address, session=None): + return {'address': address, 'network': {'multi_host': None, + 'host': 'fake'}} + self.stubs.Set(db.api, "fixed_ip_get_by_address", + fake_fixed_ip_get_by_address) + db.floating_ip_update(self.context, self.address, {'project_id': None, + 'fixed_ip_id': 1}) + body = dict(addFloatingIp=dict(address=self.address)) + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 400) + def test_add_associated_floating_ip_to_instance(self): def fake_fixed_ip_get_by_address(ctx, address, session=None): return {'address': address, 'network': {'multi_host': None, -- cgit From b7c98734f8829fb4b213869bdfca6481fbeab98e Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 30 Aug 2011 20:53:27 -0700 Subject: use kombu.connection.BrokerConnection vs kombu.connection.Connection so that older versions of kombu (1.0.4) work as well as newer. --- nova/rpc/impl_kombu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index 8242bd177..ab70e7cfb 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -322,7 +322,7 @@ class Connection(object): except self.connection.connection_errors: pass time.sleep(1) - self.connection = kombu.connection.Connection(**self.params) + self.connection = kombu.connection.BrokerConnection(**self.params) if FLAGS.fake_rabbit: # Kludge to speed up tests. self.connection.transport.polling_interval = 0.0 -- cgit From e43ffb5137ba256a21b3241b549d7c66cb7e5e04 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 30 Aug 2011 21:05:43 -0700 Subject: switched default to kombu per vishy --- nova/rpc/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/rpc/__init__.py b/nova/rpc/__init__.py index e29cd80e1..c0cfdd5ce 100644 --- a/nova/rpc/__init__.py +++ b/nova/rpc/__init__.py @@ -23,8 +23,8 @@ from nova import flags FLAGS = flags.FLAGS flags.DEFINE_string('rpc_backend', - 'nova.rpc.impl_carrot', - "The messaging module to use, defaults to carrot.") + 'nova.rpc.impl_kombu', + "The messaging module to use, defaults to kombu.") _RPCIMPL = None -- cgit From 1477b8c33374db1166c6c67ff68e03c94f3436a5 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 30 Aug 2011 21:16:26 -0700 Subject: add test to verify 400 response when out of addresses --- nova/api/openstack/contrib/floating_ips.py | 2 +- nova/tests/api/openstack/contrib/test_floating_ips.py | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index ad3094d52..99c0d1469 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -96,7 +96,7 @@ class FloatingIPController(object): except rpc.RemoteError as ex: # NOTE(tr3buchet) - why does this block exist? if ex.exc_type == 'NoMoreFloatingIps': - raise exception.NoMoreFloatingIps() + raise webob.exc.HTTPBadRequest(explanation=ex.message) else: raise diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 08148278d..fc10f2f6c 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -20,8 +20,9 @@ import webob from nova import compute from nova import context from nova import db -from nova import test from nova import network +from nova import rpc +from nova import test from nova.tests.api.openstack import fakes from nova.tests.api.openstack import test_servers @@ -114,8 +115,6 @@ class FloatingIpTest(test.TestCase): network_api_get_floating_ip) self.stubs.Set(network.api.API, "list_floating_ips", network_api_list_floating_ips) - self.stubs.Set(network.api.API, "allocate_floating_ip", - network_api_allocate) self.stubs.Set(network.api.API, "release_floating_ip", network_api_release) self.stubs.Set(network.api.API, "disassociate_floating_ip", @@ -172,7 +171,20 @@ class FloatingIpTest(test.TestCase): self.assertEqual(res_dict['floating_ip']['ip'], '10.10.10.10') self.assertEqual(res_dict['floating_ip']['instance_id'], None) + def test_floating_ip_allocate_no_free_ips(self): + def fake_call(*args, **kwargs): + raise(rpc.RemoteError('NoMoreFloatingIps', '', '')) + + self.stubs.Set(rpc, "call", fake_call) + req = webob.Request.blank('/v1.1/123/os-floating-ips') + req.method = 'POST' + req.headers['Content-Type'] = 'application/json' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_floating_ip_allocate(self): + self.stubs.Set(network.api.API, "allocate_floating_ip", + network_api_allocate) req = webob.Request.blank('/v1.1/123/os-floating-ips') req.method = 'POST' req.headers['Content-Type'] = 'application/json' -- cgit From c9a6681f484f38778987fbbaa352d07bd8f747c3 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 31 Aug 2011 10:14:07 -0400 Subject: Removed extraneous import and s/vm_state.STOP/vm_states.STOPPED/ --- nova/db/sqlalchemy/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 24e1772f6..631e53ceb 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -28,6 +28,7 @@ from nova import flags from nova import ipv6 from nova import utils from nova import log as logging +from nova.compute import vm_states from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from sqlalchemy import or_ @@ -1102,11 +1103,10 @@ def instance_destroy(context, instance_id): def instance_stop(context, instance_id): session = get_session() with session.begin(): - from nova.compute import power_state session.query(models.Instance).\ filter_by(id=instance_id).\ update({'host': None, - 'vm_state': vm_state.STOP, + 'vm_state': vm_states.STOPPED, 'task_state': None, 'updated_at': literal_column('updated_at')}) session.query(models.SecurityGroupInstanceAssociation).\ -- cgit From ba8163fed57bcd1948be4cfb021fb32391702cc5 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 31 Aug 2011 11:54:19 -0700 Subject: kludge for kombu 1.1.3 memory transport bug --- nova/rpc/impl_kombu.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/nova/rpc/impl_kombu.py b/nova/rpc/impl_kombu.py index ab70e7cfb..b994a6a10 100644 --- a/nova/rpc/impl_kombu.py +++ b/nova/rpc/impl_kombu.py @@ -303,6 +303,7 @@ class Connection(object): self.interval_stepping = FLAGS.rabbit_retry_backoff # max retry-interval = 30 seconds self.interval_max = 30 + self.memory_transport = False self.params = dict(hostname=FLAGS.rabbit_host, port=FLAGS.rabbit_port, @@ -311,6 +312,9 @@ class Connection(object): virtual_host=FLAGS.rabbit_virtual_host) if FLAGS.fake_rabbit: self.params['transport'] = 'memory' + self.memory_transport = True + else: + self.memory_transport = False self.connection = None self.reconnect() @@ -323,7 +327,7 @@ class Connection(object): pass time.sleep(1) self.connection = kombu.connection.BrokerConnection(**self.params) - if FLAGS.fake_rabbit: + if self.memory_transport: # Kludge to speed up tests. self.connection.transport.polling_interval = 0.0 self.consumer_num = itertools.count(1) @@ -345,6 +349,9 @@ class Connection(object): LOG.info(_('Connected to AMQP server on %(hostname)s:%(port)d' % self.params)) self.channel = self.connection.channel() + # work around 'memory' transport bug in 1.1.3 + if self.memory_transport: + self.channel._new_queue('ae.undeliver') for consumer in self.consumers: consumer.reconnect(self.channel) if self.consumers: @@ -374,6 +381,9 @@ class Connection(object): self.cancel_consumer_thread() self.channel.close() self.channel = self.connection.channel() + # work around 'memory' transport bug in 1.1.3 + if self.memory_transport: + self.channel._new_queue('ae.undeliver') self.consumers = [] def declare_consumer(self, consumer_cls, topic, callback): -- cgit From abd6b240b5247a2981e86c1d3161306fb2b4c4c5 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 31 Aug 2011 12:02:14 -0700 Subject: moved key_name per review --- nova/api/openstack/views/servers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 35821f9a7..cd01c9373 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -79,8 +79,6 @@ class ViewBuilder(object): metadata[item['key']] = str(item['value']) inst_dict['metadata'] = metadata - inst_dict['key_name'] = inst.get('key_name', '') - inst_dict['hostId'] = '' if inst.get('host'): inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest() @@ -190,6 +188,7 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) response['uuid'] = inst['uuid'] + response['key_name'] = inst.get('key_name', '') self._build_config_drive(response, inst) def _build_links(self, response, inst): -- cgit From ced3ea3e8d7cf02f988d968d6078182815226719 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 31 Aug 2011 12:23:43 -0700 Subject: fix for chris behrens' comment - move tenant_id => project_id mapping to compute.api.get_all --- nova/api/openstack/servers.py | 5 ----- nova/compute/api.py | 1 + nova/tests/api/openstack/test_servers.py | 11 ++++++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3e76fa1a0..5bbb4e52e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -107,11 +107,6 @@ class Controller(object): LOG.error(reason) raise exception.InvalidInput(reason=reason) - # translate tenant_id filter to internal project_id - if 'tenant_id' in search_opts: - search_opts['project_id'] = search_opts['tenant_id'] - del search_opts['tenant_id'] - # By default, compute's get_all() will return deleted instances. # If an admin hasn't specified a 'deleted' search option, we need # to filter out deleted instances by setting the filter ourselves. diff --git a/nova/compute/api.py b/nova/compute/api.py index 60a13631a..95d6a901d 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -854,6 +854,7 @@ class API(base.Base): 'image': 'image_ref', 'name': 'display_name', 'instance_name': 'name', + 'tenant_id': 'project_id', 'recurse_zones': None, 'flavor': _remap_flavor_filter, 'fixed_ip': _remap_fixed_ip_filter} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 435644dcc..1ecb35f63 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1200,13 +1200,14 @@ class ServersTest(test.TestCase): self.assertEqual(servers[0]['id'], 100) def test_tenant_id_filter_converts_to_project_id_for_admin(self): - def fake_get_all(compute_self, context, search_opts=None): - self.assertNotEqual(search_opts, None) - self.assertEqual(search_opts['project_id'], 'faketenant') - self.assertFalse(search_opts.get('tenant_id')) + def fake_get_all(context, filters=None): + self.assertNotEqual(filters, None) + self.assertEqual(filters['project_id'], 'faketenant') + self.assertFalse(filters.get('tenant_id')) return [stub_instance(100)] - self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + self.stubs.Set(nova.db.api, 'instance_get_all_by_filters', + fake_get_all) self.flags(allow_admin_api=True) req = webob.Request.blank('/v1.1/fake/servers?tenant_id=faketenant') -- cgit From f687e978a41c78e10e0c371c5486298925b5857f Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 31 Aug 2011 12:44:15 -0700 Subject: add explicit message for NoMoreFloatingIps exception --- nova/api/openstack/contrib/floating_ips.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 99c0d1469..6ce531c8f 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -96,7 +96,8 @@ class FloatingIPController(object): except rpc.RemoteError as ex: # NOTE(tr3buchet) - why does this block exist? if ex.exc_type == 'NoMoreFloatingIps': - raise webob.exc.HTTPBadRequest(explanation=ex.message) + msg = _("No more floating ips available.") + raise webob.exc.HTTPBadRequest(explanation=msg) else: raise -- cgit From c9758dd4832c167562baefad5dcc88f2a1a19b73 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 31 Aug 2011 12:51:16 -0700 Subject: fix keypairs stubs --- nova/api/openstack/create_instance_helper.py | 1 + nova/tests/api/openstack/fakes.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index d82cb534f..744353f31 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -189,6 +189,7 @@ class CreateInstanceHelper(object): raise exc.HTTPBadRequest(explanation=msg) except exception.KeypairNotFound as error: msg = _("Invalid key_name provided.") + raise exc.HTTPBadRequest(explanation=msg) except exception.SecurityGroupNotFound as error: raise exc.HTTPBadRequest(explanation=unicode(error)) except RemoteError as err: diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index a095dd90a..a4a90e6da 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -107,11 +107,18 @@ def stub_out_key_pair_funcs(stubs, have_key_pair=True): def key_pair(context, user_id): return [dict(name='key', public_key='public_key')] + def one_key_pair(context, user_id, name): + if name == 'key': + return dict(name='key', public_key='public_key') + else: + raise exc.KeypairNotFound(user_id=user_id, name=name) + def no_key_pair(context, user_id): return [] if have_key_pair: stubs.Set(nova.db, 'key_pair_get_all_by_user', key_pair) + stubs.Set(nova.db, 'key_pair_get', one_key_pair) else: stubs.Set(nova.db, 'key_pair_get_all_by_user', no_key_pair) -- cgit From a87a0bba9c7b046b36ee80bc033df5499cca35e1 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 31 Aug 2011 17:05:01 -0400 Subject: adding support for limiting in image service; updating tests with fixture ids and marker support --- nova/image/glance.py | 9 +++++++++ nova/tests/image/test_glance.py | 29 ++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/nova/image/glance.py b/nova/image/glance.py index b5f52351f..7233eb18d 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -154,6 +154,15 @@ class GlanceImageService(service.BaseImageService): except KeyError: raise exception.ImagePaginationFailed() + try: + kwargs['limit'] = kwargs['limit'] - len(images) + # break if we have reached a provided limit + if kwargs['limit'] <= 0: + return + except KeyError: + # ignore missing limit, just proceed without it + pass + for image in self._fetch_images(fetch_func, **kwargs): yield image diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index 0ff508ffa..5df25df37 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -38,7 +38,16 @@ class StubGlanceClient(object): return self.images[image_id] def get_images_detailed(self, filters=None, marker=None, limit=None): - return self.images.itervalues() + images = self.images.values() + if marker is None: + index = 0 + else: + for index, image in enumerate(images): + if image['id'] == marker: + index += 1 + break + # default to a page size of 3 to ensure we flex the pagination code + return images[index:index + 3] def get_image(self, image_id): return self.images[image_id], [] @@ -86,23 +95,23 @@ class TestGlanceImageServiceProperties(BaseGlanceTest): """Ensure attributes which aren't BASE_IMAGE_ATTRS are stored in the properties dict """ - fixtures = {'image1': {'name': 'image1', 'is_public': True, + fixtures = {'image1': {'id': '1', 'name': 'image1', 'is_public': True, 'foo': 'bar', 'properties': {'prop1': 'propvalue1'}}} self.client.images = fixtures image_meta = self.service.show(self.context, 'image1') - expected = {'name': 'image1', 'is_public': True, + expected = {'id': '1', 'name': 'image1', 'is_public': True, 'properties': {'prop1': 'propvalue1', 'foo': 'bar'}} self.assertEqual(image_meta, expected) def test_detail_passes_through_to_client(self): - fixtures = {'image1': {'name': 'image1', 'is_public': True, + fixtures = {'image1': {'id': '1', 'name': 'image1', 'is_public': True, 'foo': 'bar', 'properties': {'prop1': 'propvalue1'}}} self.client.images = fixtures image_meta = self.service.detail(self.context) - expected = [{'name': 'image1', 'is_public': True, + expected = [{'id': '1', 'name': 'image1', 'is_public': True, 'properties': {'prop1': 'propvalue1', 'foo': 'bar'}}] self.assertEqual(image_meta, expected) @@ -166,6 +175,7 @@ class TestGetterDateTimeNoneTests(BaseGlanceTest): def _make_datetime_fixtures(self): fixtures = { 'image1': { + 'id': '1', 'name': 'image1', 'is_public': True, 'created_at': self.NOW_GLANCE_FORMAT, @@ -173,6 +183,7 @@ class TestGetterDateTimeNoneTests(BaseGlanceTest): 'deleted_at': self.NOW_GLANCE_FORMAT, }, 'image2': { + 'id': '2', 'name': 'image2', 'is_public': True, 'created_at': self.NOW_GLANCE_OLD_FORMAT, @@ -183,13 +194,17 @@ class TestGetterDateTimeNoneTests(BaseGlanceTest): return fixtures def _make_none_datetime_fixtures(self): - fixtures = {'image1': {'name': 'image1', 'is_public': True, + fixtures = {'image1': {'id': '1', + 'name': 'image1', + 'is_public': True, 'updated_at': None, 'deleted_at': None}} return fixtures def _make_blank_datetime_fixtures(self): - fixtures = {'image1': {'name': 'image1', 'is_public': True, + fixtures = {'image1': {'id': '1', + 'name': 'image1', + 'is_public': True, 'updated_at': '', 'deleted_at': ''}} return fixtures -- cgit From 9de8a589b4ee0e007267efe2394b504382e4cdc1 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 31 Aug 2011 14:58:55 -0700 Subject: change to use _get_key_name to retrieve the key --- nova/api/openstack/servers.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f288f2228..53684fa52 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -144,8 +144,15 @@ class Controller(object): except exception.NotFound: raise exc.HTTPNotFound() + def _get_key_name(self, req, body): + """ Get default keypair if not set """ + raise NotImplementedError() + def create(self, req, body): """ Creates a new server for a given user """ + if 'server' in body: + body['server']['key_name'] = self._get_key_name(req, body) + extra_values = None extra_values, instances = self.helper.create_instance( req, body, self.compute_api.create) @@ -564,17 +571,12 @@ class ControllerV10(Controller): raise exc.HTTPNotFound() return webob.Response(status_int=202) - def create(self, req, body): - """ Creates a new server for a given user """ - # note(ja): v1.0 injects the first keypair for the project for testing - if 'server' in body and not 'key_name' in body['server']: - context = req.environ["nova.context"] - keypairs = db.key_pair_get_all_by_user(context.elevated(), - context.user_id) - if keypairs: - body['server']['key_name'] = keypairs[0]['name'] - - return super(ControllerV10, self).create(req, body) + def _get_key_name(self, req, body): + context = req.environ["nova.context"] + keypairs = db.key_pair_get_all_by_user(context, + context.user_id) + if keypairs: + return keypairs[0]['name'] def _image_ref_from_req_data(self, data): return data['server']['imageId'] @@ -647,6 +649,10 @@ class ControllerV11(Controller): except exception.NotFound: raise exc.HTTPNotFound() + def _get_key_name(self, req, body): + if 'server' in body: + return body['server'].get('key_name') + def _image_ref_from_req_data(self, data): try: return data['server']['imageRef'] -- cgit From 642c9ceb1bae9fa5ba008cb69c47f449ea173c3a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 31 Aug 2011 15:24:29 -0700 Subject: expect key_name attribute in 1.1 --- nova/tests/api/openstack/test_servers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index c54bead49..78c872a28 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3630,6 +3630,7 @@ class ServersViewBuilderV11Test(test.TestCase): "id": 1, "uuid": self.instance['uuid'], "name": "test_server", + "key_name": '', "links": [ { "rel": "self", @@ -3653,6 +3654,7 @@ class ServersViewBuilderV11Test(test.TestCase): "id": 1, "uuid": self.instance['uuid'], "name": "test_server", + "key_name": '', "config_drive": None, "links": [ { -- cgit From bd917feb287a3d0e8f2f9f9c60b716c7f599f4ff Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 31 Aug 2011 16:13:55 -0700 Subject: remove extra test --- nova/tests/api/openstack/test_server_actions.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 0c5fab41e..b9ef41465 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -244,19 +244,6 @@ class ServerActionsTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 500) - def test_resized_server_has_correct_status(self): - req = self.webreq('/1', 'GET') - - def fake_migration_get(*args): - return {} - - self.stubs.Set(nova.db.api, 'migration_get_by_instance_and_status', - fake_migration_get) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - body = json.loads(res.body) - self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM') - def test_confirm_resize_server(self): req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) -- cgit From e1cd8f036f34fc622416e74a302959c9e50a798c Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 31 Aug 2011 17:06:15 -0700 Subject: Fix for LP Bug #838251 --- nova/api/ec2/cloud.py | 12 ++---------- nova/api/openstack/contrib/createserverext.py | 24 +++++++++++++++++++----- nova/api/openstack/servers.py | 18 ++++++++++++++++++ nova/utils.py | 9 +++++++++ 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9aebf92e3..d3b3a21ef 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -995,14 +995,6 @@ class CloudController(object): 'status': volume['attach_status'], 'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)} - @staticmethod - def _convert_to_set(lst, label): - if lst is None or lst == []: - return None - if not isinstance(lst, list): - lst = [lst] - return [{label: x} for x in lst] - def _format_kernel_id(self, instance_ref, result, key): kernel_id = instance_ref['kernel_id'] if kernel_id is None: @@ -1160,7 +1152,7 @@ class CloudController(object): if instance.get('security_groups'): for security_group in instance['security_groups']: security_group_names.append(security_group['name']) - result['groupSet'] = CloudController._convert_to_set( + result['groupSet'] = utils.convert_to_set( security_group_names, 'groupId') def _format_instances(self, context, instance_id=None, use_v6=False, @@ -1224,7 +1216,7 @@ class CloudController(object): i['keyName'] = '%s (%s, %s)' % (i['keyName'], instance['project_id'], instance['host']) - i['productCodesSet'] = self._convert_to_set([], 'product_codes') + i['productCodesSet'] = utils.convert_to_set([], 'product_codes') self._format_instance_type(instance, i) i['launchTime'] = instance['created_at'] i['amiLaunchIndex'] = instance['launch_index'] diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py index ba72fdb0b..4cc093891 100644 --- a/nova/api/openstack/contrib/createserverext.py +++ b/nova/api/openstack/contrib/createserverext.py @@ -14,18 +14,32 @@ # License for the specific language governing permissions and limitations # under the License +from nova import utils from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import extensions from nova.api.openstack import servers from nova.api.openstack import wsgi -class Createserverext(extensions.ExtensionDescriptor): - """The servers create ext +class CreateServerController(servers.ControllerV11): + def _build_view(self, req, instance, is_detail=False): + server = super(CreateServerController, self)._build_view(req, + instance, + is_detail) + if is_detail: + self._build_security_groups(server['server'], instance) + return server + + def _build_security_groups(self, response, inst): + sg_names = [] + if inst.get('security_groups'): + sg_names = [security_group['name'] for security_group in \ + inst['security_groups']] + response['security_groups'] = utils.convert_to_set(sg_names, 'name') - Exposes addFixedIp and removeFixedIp actions on servers. - """ +class Createserverext(extensions.ExtensionDescriptor): + """The servers create ext""" def get_name(self): return "Createserverext" @@ -58,7 +72,7 @@ class Createserverext(extensions.ExtensionDescriptor): deserializer = wsgi.RequestDeserializer(body_deserializers) res = extensions.ResourceExtension('os-create-server-ext', - controller=servers.ControllerV11(), + controller=CreateServerController(), deserializer=deserializer, serializer=serializer) resources.append(res) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 27c67e79e..e2cb46165 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -912,6 +912,11 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): server['addresses']) server_node.appendChild(addresses_node) + if 'security_groups' in server: + security_groups_node = self._create_security_groups_node(xml_doc, + server['security_groups']) + server_node.appendChild(security_groups_node) + return server_node def _server_list_to_xml(self, xml_doc, servers, detailed): @@ -964,6 +969,19 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): server_dict['server']) return self.to_xml_string(node, True) + def _security_group_to_xml(self, doc, security_group): + node = doc.createElement('security_group') + node.setAttribute('name', str(security_group.get('name'))) + return node + + def _create_security_groups_node(self, xml_doc, security_groups): + security_groups_node = xml_doc.createElement('security_groups') + if security_groups: + for security_group in security_groups: + node = self._security_group_to_xml(xml_doc, security_group) + security_groups_node.appendChild(node) + return security_groups_node + def create_resource(version='1.0'): controller = { diff --git a/nova/utils.py b/nova/utils.py index 21e6221b2..4855f6fd6 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -901,3 +901,12 @@ def monkey_patch(): func = import_class("%s.%s" % (module, key)) setattr(sys.modules[module], key,\ decorator("%s.%s" % (module, key), func)) + + +def convert_to_set(lst, label): + """Convert a value or list into a set""" + if lst is None or lst == []: + return None + if not isinstance(lst, list): + lst = [lst] + return [{label: x} for x in lst] -- cgit From 20beec509aff1bb3a30e9f1d95d3e2825f2b38ea Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 31 Aug 2011 17:30:11 -0700 Subject: Fix for LP Bug #838466 --- nova/compute/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index e045ef3de..6806522f7 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -383,10 +383,6 @@ class API(base.Base): If you are changing this method, be sure to update both call paths. """ - instance = dict(launch_index=num, **base_options) - instance = self.db.instance_create(context, instance) - instance_id = instance['id'] - elevated = context.elevated() if security_group is None: security_group = ['default'] @@ -400,6 +396,10 @@ class API(base.Base): security_group_name) security_groups.append(group['id']) + instance = dict(launch_index=num, **base_options) + instance = self.db.instance_create(context, instance) + instance_id = instance['id'] + for security_group_id in security_groups: self.db.instance_add_security_group(elevated, instance_id, -- cgit From f4dc231069c530f8f6055b1b7fa006750795b6e4 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Wed, 31 Aug 2011 18:54:15 -0700 Subject: Add comment for an uncommon failure case that we need to fix --- nova/network/quantum/manager.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index c03622218..2fd9175ae 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -213,6 +213,12 @@ class QuantumManager(manager.FlatManager): net_id, port_id = self.q_conn.get_port_by_attachment( q_tenant_id, vif['uuid']) if not net_id: + # TODO(bgh): We need to figure out a way to tell if we + # should actually be raising this exception or not. + # In the case that a VM spawn failed it may not have + # attached the vif and raising the exception here + # prevents deltion of the VM. In that case we should + # probably just log, continue, and move on. raise Exception(_("No network for for virtual interface %s") % vif['uuid']) (v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context, -- cgit From fdf076a04e001b897d01b2a8c4a9e3c980ea8f94 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 31 Aug 2011 21:34:10 -0700 Subject: fix for lp838583 - return instance_id for associated floating_ips, add test --- nova/api/openstack/contrib/floating_ips.py | 6 +++--- .../api/openstack/contrib/test_floating_ips.py | 21 +++++++++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 6ce531c8f..d1add8f83 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -36,9 +36,9 @@ def _translate_floating_ip_view(floating_ip): result['fixed_ip'] = floating_ip['fixed_ip']['address'] except (TypeError, KeyError): result['fixed_ip'] = None - if 'instance' in floating_ip: - result['instance_id'] = floating_ip['instance']['id'] - else: + try: + result['instance_id'] = floating_ip['fixed_ip']['instance_id'] + except (TypeError, KeyError): result['instance_id'] = None return {'floating_ip': result} diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index fc10f2f6c..642f2b841 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -38,14 +38,13 @@ def network_api_get_floating_ip(self, context, id): def network_api_get_floating_ip_by_ip(self, context, address): return {'id': 1, 'address': '10.10.10.10', - 'fixed_ip': {'address': '11.0.0.1'}} + 'fixed_ip': {'address': '10.0.0.1', 'instance_id': 1}}, def network_api_list_floating_ips(self, context): return [{'id': 1, 'address': '10.10.10.10', - 'instance': {'id': 11}, - 'fixed_ip': {'address': '10.0.0.1'}}, + 'fixed_ip': {'address': '10.0.0.1', 'instance_id': 1}}, {'id': 2, 'address': '10.10.10.11'}] @@ -152,7 +151,7 @@ class FloatingIpTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) - response = {'floating_ips': [{'instance_id': 11, + response = {'floating_ips': [{'instance_id': 1, 'ip': '10.10.10.10', 'fixed_ip': '10.0.0.1', 'id': 1}, @@ -171,6 +170,20 @@ class FloatingIpTest(test.TestCase): self.assertEqual(res_dict['floating_ip']['ip'], '10.10.10.10') self.assertEqual(res_dict['floating_ip']['instance_id'], None) + def test_show_associated_floating_ip(self): + def get_floating_ip(self, context, id): + return {'id': 1, 'address': '10.10.10.10', + 'fixed_ip': {'address': '10.0.0.1', 'instance_id': 1}} + self.stubs.Set(network.api.API, "get_floating_ip", get_floating_ip) + + req = webob.Request.blank('/v1.1/123/os-floating-ips/1') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['floating_ip']['id'], 1) + self.assertEqual(res_dict['floating_ip']['ip'], '10.10.10.10') + self.assertEqual(res_dict['floating_ip']['instance_id'], 1) + def test_floating_ip_allocate_no_free_ips(self): def fake_call(*args, **kwargs): raise(rpc.RemoteError('NoMoreFloatingIps', '', '')) -- cgit From 7be0e4cd371691253aa332c2c180be1a83f7f98b Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Thu, 1 Sep 2011 05:02:30 +0000 Subject: Launchpad automatic translations update. --- po/pt_BR.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/po/pt_BR.po b/po/pt_BR.po index bff1135c6..e10b0c0de 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-08-23 11:23+0000\n" -"Last-Translator: msinhore \n" +"PO-Revision-Date: 2011-09-01 04:15+0000\n" +"Last-Translator: Daniel Negri \n" "Language-Team: Brazilian Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-24 04:47+0000\n" -"X-Generator: Launchpad (build 13697)\n" +"X-Launchpad-Export-Date: 2011-09-01 05:02+0000\n" +"X-Generator: Launchpad (build 13827)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -708,7 +708,7 @@ msgstr "Ligação %(queue)s para %(exchange)s com chave %(routing_key)s" #: ../nova/fakerabbit.py:121 #, python-format msgid "Getting from %(queue)s: %(message)s" -msgstr "" +msgstr "Recebendo de %(queue)s: %(message)s" #: ../nova/virt/xenapi/vm_utils.py:135 ../nova/virt/hyperv.py:171 #, python-format @@ -808,7 +808,7 @@ msgstr "Kernel/Ramdisk %s destruidos" #: ../nova/virt/xenapi/vm_utils.py:361 #, python-format msgid "Asking xapi to fetch %(url)s as %(access)s" -msgstr "" +msgstr "Requisitando à xapi a busca da url %(url)s como %(access)s" #: ../nova/virt/xenapi/vm_utils.py:386 ../nova/virt/xenapi/vm_utils.py:402 #, python-format -- cgit From 3ae639e5acd965999138e307508933ae1624b476 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Wed, 31 Aug 2011 23:12:09 -0700 Subject: add specific exceptions for quantum client. Fix doc-strings in client.py --- nova/network/quantum/client.py | 93 ++++++++++++++---------------- nova/network/quantum/quantum_connection.py | 7 +-- 2 files changed, 46 insertions(+), 54 deletions(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 82f23b6b5..22a4b4d63 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -39,6 +39,24 @@ class JSONSerializer(object): return json.loads(data) +# FIXME: (danwent) the full client lib will expose more +# granular exceptions, for now, just try to distinguish +# between the cases we care about. +class QuantumNotFoundException(Exception); + """ Indicates that Quantum Server returned 404""" + pass + + +class QuantumServerException(Exception): + """ Indicates any non-404 error from Quantum Server""" + pass + + +class QuantumIOException(Exception): + """ Indicates network IO trouble reaching Quantum Server""" + pass + + class api_call(object): """A Decorator to add support for format and tenant overriding""" def __init__(self, func): @@ -78,8 +96,7 @@ class Client(object): def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, format="xml", testing_stub=None, key_file=None, cert_file=None, logger=None): - """ - Creates a new client to some service. + """ Creates a new client to some service. :param host: The host where service resides :param port: The port where service resides @@ -102,9 +119,7 @@ class Client(object): self.logger = logger def get_connection_type(self): - """ - Returns the proper connection type - """ + """ Returns the proper connection type """ if self.testing_stub: return self.testing_stub elif self.use_ssl: @@ -114,8 +129,7 @@ class Client(object): def do_request(self, method, action, body=None, headers=None, params=None): - """ - Connects to the server and issues a request. + """ Connects to the server and issues a request. Returns the result data, or raises an appropriate exception if HTTP status code is not 2xx @@ -168,6 +182,10 @@ class Client(object): self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \ % (str(status_code), data)) + if status_code == httplib.NOT_FOUND: + raise QuantumNotFoundException( + _("Quantum entity not found: %s" % data)) + if status_code in (httplib.OK, httplib.CREATED, httplib.ACCEPTED, @@ -175,15 +193,16 @@ class Client(object): if data is not None and len(data): return self.deserialize(data, status_code) else: - raise Exception(_("Server returned error: %s" % res.read())) + raise QuantumServerException( + _("Server %(status_code)s error: %(data)s" + % locals())) except (socket.error, IOError), e: - raise Exception(_("Unable to connect to " + raise QuantumIOException(_("Unable to connect to " "server. Got error: %s" % e)) def get_status_code(self, response): - """ - Returns the integer status code from the response, which + """ Returns the integer status code from the response, which can be either a Webob.Response (used in testing) or httplib.Response """ if hasattr(response, 'status_int'): @@ -212,99 +231,73 @@ class Client(object): @api_call def list_networks(self): - """ - Fetches a list of all networks for a tenant - """ + """ Fetches a list of all networks for a tenant """ return self.do_request("GET", self.networks_path) @api_call def show_network_details(self, network): - """ - Fetches the details of a certain network - """ + """ Fetches the details of a certain network """ return self.do_request("GET", self.network_path % (network)) @api_call def create_network(self, body=None): - """ - Creates a new network - """ + """ Creates a new network """ body = self.serialize(body) return self.do_request("POST", self.networks_path, body=body) @api_call def update_network(self, network, body=None): - """ - Updates a network - """ + """ Updates a network """ body = self.serialize(body) return self.do_request("PUT", self.network_path % (network), body=body) @api_call def delete_network(self, network): - """ - Deletes the specified network - """ + """ Deletes the specified network """ return self.do_request("DELETE", self.network_path % (network)) @api_call def list_ports(self, network): - """ - Fetches a list of ports on a given network - """ + """ Fetches a list of ports on a given network """ return self.do_request("GET", self.ports_path % (network)) @api_call def show_port_details(self, network, port): - """ - Fetches the details of a certain port - """ + """ Fetches the details of a certain port """ return self.do_request("GET", self.port_path % (network, port)) @api_call def create_port(self, network, body=None): - """ - Creates a new port on a given network - """ + """ Creates a new port on a given network """ body = self.serialize(body) return self.do_request("POST", self.ports_path % (network), body=body) @api_call def delete_port(self, network, port): - """ - Deletes the specified port from a network - """ + """ Deletes the specified port from a network """ return self.do_request("DELETE", self.port_path % (network, port)) @api_call def set_port_state(self, network, port, body=None): - """ - Sets the state of the specified port - """ + """ Sets the state of the specified port """ body = self.serialize(body) return self.do_request("PUT", self.port_path % (network, port), body=body) @api_call def show_port_attachment(self, network, port): - """ - Fetches the attachment-id associated with the specified port - """ + """ Fetches the attachment-id associated with the specified port """ return self.do_request("GET", self.attachment_path % (network, port)) @api_call def attach_resource(self, network, port, body=None): - """ - Sets the attachment-id of the specified port - """ + """ Sets the attachment-id of the specified port """ body = self.serialize(body) return self.do_request("PUT", self.attachment_path % (network, port), body=body) @api_call def detach_resource(self, network, port): - """ - Removes the attachment-id of the specified port - """ + """ Removes the attachment-id of the specified port """ return self.do_request("DELETE", self.attachment_path % (network, port)) diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index e2218c68d..90d5e8e04 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -70,11 +70,10 @@ class QuantumClientConnection(object): """ try: self.client.show_network_details(net_id, tenant=tenant_id) - except: - # FIXME(danwent): client lib should expose granular exceptions - # so we can confirm we're getting a 404 and not some other error + return True + except client.QuantumNotFoundException: + # Not really an error. Real errors will be propogated to caller return False - return True def create_and_attach_port(self, tenant_id, net_id, interface_id): """ Creates a Quantum port on the specified network, sets -- cgit From 02093fbe185e52a3c22b748811e60e988150a352 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Wed, 31 Aug 2011 23:37:26 -0700 Subject: more review cleanup --- nova/network/quantum/manager.py | 16 ++++++++-------- nova/network/quantum/nova_ipam_lib.py | 8 +++----- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 1d2c7c664..153f6c0f2 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -103,16 +103,16 @@ class QuantumManager(manager.FlatManager): priority, cidr, gateway_v6, cidr_v6, dns1, dns2) def delete_network(self, context, fixed_range): - """ Lookup network by IPv4 cidr, delete both the IPAM - subnet and the corresponding Quantum network. - """ - project_id = context.project_id - quantum_net_id = self.ipam.get_network_id_by_cidr( + """ Lookup network by IPv4 cidr, delete both the IPAM + subnet and the corresponding Quantum network. + """ + project_id = context.project_id + quantum_net_id = self.ipam.get_network_id_by_cidr( context, fixed_range, project_id) - self.ipam.delete_subnets_by_net_id(context, quantum_net_id, + self.ipam.delete_subnets_by_net_id(context, quantum_net_id, project_id) - q_tenant_id = project_id or FLAGS.quantum_default_tenant_id - self.q_conn.delete_network(q_tenant_id, quantum_net_id) + q_tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.q_conn.delete_network(q_tenant_id, quantum_net_id) def allocate_for_instance(self, context, **kwargs): """ Called by compute when it is creating a new VM. diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 4f62887d1..4b08b906a 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -15,7 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -import math +import netaddr from nova import db from nova import exception @@ -60,8 +60,7 @@ class QuantumNovaIPAMLib(object): are needed by Quantum but not the FlatManager. """ admin_context = context.elevated() - # FIXME(danwent): Use the netaddr library here - subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) + subnet_size = len(netaddr.IPNetwork(cidr)) networks = manager.FlatManager.create_networks(self.net_manager, admin_context, label, cidr, False, 1, subnet_size, cidr_v6, @@ -114,8 +113,7 @@ class QuantumNovaIPAMLib(object): return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): - """ Allocates a single fixed IPv4 address for a virtual interface. - """ + """ Allocates a single fixed IPv4 address for a virtual interface.""" admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, quantum_net_id) if network['cidr']: -- cgit From 76a60bf27cc8864e397139a3497b1f571ce38d88 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Wed, 31 Aug 2011 23:42:24 -0700 Subject: typo --- nova/network/quantum/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 22a4b4d63..67582d8bc 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -42,7 +42,7 @@ class JSONSerializer(object): # FIXME: (danwent) the full client lib will expose more # granular exceptions, for now, just try to distinguish # between the cases we care about. -class QuantumNotFoundException(Exception); +class QuantumNotFoundException(Exception): """ Indicates that Quantum Server returned 404""" pass -- cgit From 5b3b3d1b357c085c2088df7d76df8392118fb82e Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 1 Sep 2011 09:39:36 -0700 Subject: additional review cleanup --- .mailmap | 2 +- nova/network/quantum/client.py | 12 ++++++++---- nova/network/quantum/fake.py | 10 ++++++---- nova/network/quantum/manager.py | 2 +- nova/network/quantum/melange_connection.py | 4 +++- nova/network/quantum/melange_ipam_lib.py | 3 ++- nova/network/quantum/nova_ipam_lib.py | 2 +- nova/network/quantum/quantum_connection.py | 8 ++++---- nova/tests/test_quantum.py | 21 +++++++++++---------- 9 files changed, 37 insertions(+), 27 deletions(-) diff --git a/.mailmap b/.mailmap index a46dd6673..f2f59d81b 100644 --- a/.mailmap +++ b/.mailmap @@ -37,7 +37,7 @@ - + diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 67582d8bc..455bb8a79 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -22,11 +22,15 @@ import socket import urllib +#FIXME(danwent): All content in this file should be removed once the +# packaging work for the quantum client libraries is complete. +# At that point, we will be able to just install the libraries as a +# dependency and import from quantum.client.* and quantum.common.* +# Until then, we have simplified versions of these classes in this file. + class JSONSerializer(object): - """ - This is a simple json-only serializer to use until we can just grab + """ This is a simple json-only serializer to use until we can just grab the standard serializer from the quantum library. - TODO(danwent): replace serializer with quantum implementation """ def serialize(self, data, content_type): try: @@ -39,7 +43,7 @@ class JSONSerializer(object): return json.loads(data) -# FIXME: (danwent) the full client lib will expose more +# The full client lib will expose more # granular exceptions, for now, just try to distinguish # between the cases we care about. class QuantumNotFoundException(Exception): diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index f668edfed..6a4005c59 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -65,8 +65,9 @@ class FakeQuantumClientConnection(object): def create_and_attach_port(self, tenant_id, net_id, interface_id): if not self.network_exists(tenant_id, net_id): - raise Exception(_("network %s does not exist for tenant %s" % - (net_id, tenant_id))) + raise Exception( + _("network %(net_id)s does not exist for tenant %(tenant_id)" + % locals())) self._confirm_not_attached(interface_id) uuid = str(utils.gen_uuid()) @@ -76,8 +77,9 @@ class FakeQuantumClientConnection(object): def detach_and_delete_port(self, tenant_id, net_id, port_id): if not self.network_exists(tenant_id, net_id): - raise Exception(_("network %s does not exist for tenant %s" %\ - (net_id, tenant_id))) + raise exception.NotFound( + _("network %s does not exist for tenant %s" % + (net_id, tenant_id))) del self.nets[net_id]['ports'][port_id] def get_port_by_attachment(self, tenant_id, attachment_id): diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 153f6c0f2..709299dd2 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -86,7 +86,7 @@ class QuantumManager(manager.FlatManager): if num_networks != 1: raise Exception(_("QuantumManager requires that only one" " network is created per call")) - q_tenant_id = kwargs["project_id"] or FLAGS.quantum_default_tenant_id + q_tenant_id = kwargs.get("project_id", FLAGS.quantum_default_tenant_id) quantum_net_id = uuid if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index 1ee0c29a2..4dc35d15d 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -35,7 +35,9 @@ flags.DEFINE_string('melange_port', json_content_type = {'Content-type': "application/json"} - +#FIXME(danwent): talk to the Melange folks about creating a +# client lib that we can import as a library, instead of +# have to have all of the client code in here. class MelangeConnection(object): def __init__(self, host=None, port=None, use_ssl=False): diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 24a7c5404..71c0d7ce6 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -17,6 +17,7 @@ from netaddr import IPNetwork +from nova import exception from nova import flags from nova import log as logging from nova.network.quantum import melange_connection @@ -85,7 +86,7 @@ class QuantumMelangeIPAMLib(object): for b in all_blocks['ip_blocks']: if b['cidr'] == cidr: return b['network_id'] - raise Exception(_("No network found for cidr %s" % cidr)) + raise exception.NotFound(_("No network found for cidr %s" % cidr)) def delete_subnets_by_net_id(self, context, net_id, project_id): """ Find Melange block associated with the Quantum UUID, diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 4b08b906a..17236a976 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -154,7 +154,7 @@ class QuantumNovaIPAMLib(object): vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) fixed_ips = db.fixed_ip_get_by_virtual_interface(context, vif_rec['id']) - return [f['address'] for f in fixed_ips] + return [fixed_ip['address'] for fixed_ip in fixed_ips] def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): """ Returns a list containing a single IPv6 address strings diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index 90d5e8e04..93892a843 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -80,8 +80,8 @@ class QuantumClientConnection(object): status to ACTIVE to enable traffic, and attaches the vNIC with the specified interface-id. """ - LOG.debug(_("Connecting interface %s to net %s for %s" % - (interface_id, net_id, tenant_id))) + LOG.debug(_("Connecting interface %(interface_id)s to " + "net %(net_id)s for %(tenant_id)s" % locals())) port_data = {'port': {'state': 'ACTIVE'}} resdict = self.client.create_port(net_id, port_data, tenant=tenant_id) port_id = resdict["port"]["id"] @@ -92,8 +92,8 @@ class QuantumClientConnection(object): def detach_and_delete_port(self, tenant_id, net_id, port_id): """ Detach and delete the specified Quantum port. """ - LOG.debug("Deleting port %s on net %s for %s" % \ - (port_id, net_id, tenant_id)) + LOG.debug(_("Deleting port %(port_id)s on net %(net_id)s" + " for %(tenant_id)s" % locals())) self.client.detach_resource(net_id, port_id, tenant=tenant_id) self.client.delete_port(net_id, port_id, tenant=tenant_id) diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 2cc83adf1..0fa4184b1 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -189,29 +189,29 @@ class QuantumTestCaseBase(object): # we don't know which order the NICs will be in until we # introduce the notion of priority # v4 cidr - self.assertTrue(nw_info[0][0]['cidr'].startswith("9.") or \ + self.assertTrue(nw_info[0][0]['cidr'].startswith("9.") or nw_info[1][0]['cidr'].startswith("9.")) - self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or \ + self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or nw_info[1][0]['cidr'].startswith("192.")) # v4 address - self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("9.") or \ + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("9.") or nw_info[1][1]['ips'][0]['ip'].startswith("9.")) - self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or \ + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or nw_info[1][1]['ips'][0]['ip'].startswith("192.")) # v6 cidr - self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dbb:") or \ + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dbb:") or nw_info[1][0]['cidr_v6'].startswith("2001:1dbb:")) - self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db9:") or \ + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db9:") or nw_info[1][0]['cidr_v6'].startswith("2001:1db9:")) # v6 address self.assertTrue(\ - nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dbb:") or \ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dbb:") or nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dbb:")) self.assertTrue(\ - nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db9:") or \ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db9:") or nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db9:")) self.net_man.deallocate_for_instance(ctx, @@ -240,9 +240,10 @@ class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): for n in db.network_get_all(ctx): db.network_delete_safe(ctx, n['id']) - # NOTE(danwent): I've found that other unit tests have a nasty + # Other unit tests (e.g., test_compute.py) have a nasty # habit of of creating fixed IPs and not cleaning up, which - # can confuse these tests, so we clean them all. + # can confuse these tests, so we remove all existing fixed + # ips before starting. session = get_session() result = session.query(models.FixedIp).all() with session.begin(): -- cgit From 35c4cecc8d29da32bd816bb68f8b45c2d03f892f Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 1 Sep 2011 09:42:44 -0700 Subject: undo change in setting q_tenant_id in quantum_manager.create_network --- nova/network/quantum/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 709299dd2..153f6c0f2 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -86,7 +86,7 @@ class QuantumManager(manager.FlatManager): if num_networks != 1: raise Exception(_("QuantumManager requires that only one" " network is created per call")) - q_tenant_id = kwargs.get("project_id", FLAGS.quantum_default_tenant_id) + q_tenant_id = kwargs["project_id"] or FLAGS.quantum_default_tenant_id quantum_net_id = uuid if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): -- cgit From dd5eeafbfe1013fd9acdb119933cb5bf986706e6 Mon Sep 17 00:00:00 2001 From: Christopher MacGown Date: Thu, 1 Sep 2011 12:05:21 -0700 Subject: Adds test for image.glance.GlanceImageService._is_image_available --- nova/tests/image/test_glance.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index 0ff508ffa..81a54346e 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -20,6 +20,7 @@ import datetime import unittest from nova import context +from nova import exception from nova import test from nova.image import glance @@ -96,6 +97,31 @@ class TestGlanceImageServiceProperties(BaseGlanceTest): 'properties': {'prop1': 'propvalue1', 'foo': 'bar'}} self.assertEqual(image_meta, expected) + def test_show_raises_when_no_authtoken_in_the_context(self): + fixtures = {'image1': {'name': 'image1', 'is_public': False, + 'foo': 'bar', + 'properties': {'prop1': 'propvalue1'}}} + self.client.images = fixtures + self.context.auth_token = False + + expected = {'name': 'image1', 'is_public': True, + 'properties': {'prop1': 'propvalue1', 'foo': 'bar'}} + self.assertRaises(exception.ImageNotFound, + self.service.show, self.context, 'image1') + + def test_show_passes_through_to_client_with_authtoken_in_context(self): + fixtures = {'image1': {'name': 'image1', 'is_public': False, + 'foo': 'bar', + 'properties': {'prop1': 'propvalue1'}}} + self.client.images = fixtures + self.context.auth_token = True + + expected = {'name': 'image1', 'is_public': False, + 'properties': {'prop1': 'propvalue1', 'foo': 'bar'}} + + image_meta = self.service.show(self.context, 'image1') + self.assertEqual(image_meta, expected) + def test_detail_passes_through_to_client(self): fixtures = {'image1': {'name': 'image1', 'is_public': True, 'foo': 'bar', -- cgit From 59be9be68c0fd9b33b72257b8a1eb8c357ce9217 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 1 Sep 2011 12:22:32 -0700 Subject: remove extra references to state_description --- bin/nova-manage | 6 +++--- nova/api/ec2/admin.py | 5 ++--- nova/api/openstack/contrib/simple_tenant_usage.py | 2 +- nova/tests/test_libvirt.py | 10 ++++++---- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index c9cf4266d..c3b2c71ce 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -166,7 +166,7 @@ class VpnCommands(object): print address, print vpn['host'], print ec2utils.id_to_ec2_id(vpn['id']), - print vpn['state_description'], + print vpn['vm_state'], print state else: print None @@ -869,7 +869,7 @@ class VmCommands(object): instance['hostname'], instance['host'], instance['instance_type'].name, - instance['state_description'], + instance['vm_state'], instance['launched_at'], instance['image_ref'], instance['kernel_id'], @@ -1223,7 +1223,7 @@ class VsaCommands(object): type=vc['instance_type']['name'], fl_ip=floating_addr, fx_ip=fixed_addr, - stat=vc['state_description'], + stat=vc['vm_state'], host=vc['host'], time=str(vc['created_at'])) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index dfbbc0a2b..75e029509 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -21,7 +21,6 @@ Admin API controller, exposed through http via the api worker. """ import base64 -import datetime import netaddr import urllib @@ -33,6 +32,7 @@ from nova import log as logging from nova import utils from nova.api.ec2 import ec2utils from nova.auth import manager +from nova.compute import vm_states FLAGS = flags.FLAGS @@ -273,8 +273,7 @@ class AdminController(object): """Get the VPN instance for a project ID.""" for instance in db.instance_get_all_by_project(context, project_id): if (instance['image_id'] == str(FLAGS.vpn_image_id) - and not instance['state_description'] in - ['shutting_down', 'shutdown']): + and not instance['vm_state'] in [vm_states.DELETED]): return instance def start_vpn(self, context, project): diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index 69b38e229..42691a9fa 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -116,7 +116,7 @@ class SimpleTenantUsageController(object): if info['ended_at']: info['state'] = 'terminated' else: - info['state'] = instance['state_description'] + info['state'] = instance['vm_state'] now = datetime.utcnow() diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 6a213b4f0..8c6775b29 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -34,6 +34,7 @@ from nova import test from nova import utils from nova.api.ec2 import cloud from nova.compute import power_state +from nova.compute import vm_states from nova.virt.libvirt import connection from nova.virt.libvirt import firewall @@ -674,8 +675,9 @@ class LibvirtConnTestCase(test.TestCase): # Preparing data self.compute = utils.import_object(FLAGS.compute_manager) - instance_dict = {'host': 'fake', 'state': power_state.RUNNING, - 'state_description': 'running'} + instance_dict = {'host': 'fake', + 'power_state': power_state.RUNNING, + 'vm_state': vm_states.ACTIVE} instance_ref = db.instance_create(self.context, self.test_instance) instance_ref = db.instance_update(self.context, instance_ref['id'], instance_dict) @@ -713,8 +715,8 @@ class LibvirtConnTestCase(test.TestCase): self.compute.rollback_live_migration) instance_ref = db.instance_get(self.context, instance_ref['id']) - self.assertTrue(instance_ref['state_description'] == 'running') - self.assertTrue(instance_ref['state'] == power_state.RUNNING) + self.assertTrue(instance_ref['vm_state'] == vm_states.ACTIVE) + self.assertTrue(instance_ref['power_state'] == power_state.RUNNING) volume_ref = db.volume_get(self.context, volume_ref['id']) self.assertTrue(volume_ref['status'] == 'in-use') -- cgit From a36e0a1cbc5645ab47041c2627dba80b39b23cc2 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 1 Sep 2011 15:42:01 -0400 Subject: Fixes for minor network manager issues centered around deleting/accessing instances which don't have network information set. --- nova/api/openstack/views/addresses.py | 1 - nova/network/manager.py | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index 8f07a2289..8d38bc9c3 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -88,7 +88,6 @@ class ViewBuilderV11(ViewBuilder): try: return interface['network']['label'] except (TypeError, KeyError) as exc: - LOG.exception(exc) raise TypeError def _extract_ipv4_addresses(self, interface): diff --git a/nova/network/manager.py b/nova/network/manager.py index e6b30d1a0..d7b0abdda 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -448,7 +448,7 @@ class NetworkManager(manager.SchedulerDependentManager): try: fixed_ips = kwargs.get('fixed_ips') or \ self.db.fixed_ip_get_by_instance(context, instance_id) - except exceptions.FixedIpNotFoundForInstance: + except exception.FixedIpNotFoundForInstance: fixed_ips = [] LOG.debug(_("network deallocation for instance |%s|"), instance_id, context=context) @@ -484,6 +484,9 @@ class NetworkManager(manager.SchedulerDependentManager): for vif in vifs: network = vif['network'] + if network is None: + continue + # determine which of the instance's IPs belong to this network network_IPs = [fixed_ip['address'] for fixed_ip in fixed_ips if fixed_ip['network_id'] == network['id']] -- cgit -- cgit From 833ac1674d33cde3721b2d10a3d9545cc8320b37 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 1 Sep 2011 16:09:23 -0400 Subject: Added test for NULL network. --- nova/network/manager.py | 4 ++-- nova/tests/test_network.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index d7b0abdda..e8c5c0654 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -484,8 +484,8 @@ class NetworkManager(manager.SchedulerDependentManager): for vif in vifs: network = vif['network'] - if network is None: - continue + #if network is None: + # continue # determine which of the instance's IPs belong to this network network_IPs = [fixed_ip['address'] for fixed_ip in fixed_ips if diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 25ff940f0..2ae5a35e3 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -118,9 +118,14 @@ vifs = [{'id': 0, {'id': 1, 'address': 'DE:AD:BE:EF:00:01', 'uuid': '00000000-0000-0000-0000-0000000000000001', - 'network_id': 0, 'network_id': 1, 'network': FakeModel(**networks[1]), + 'instance_id': 0}, + {'id': 2, + 'address': 'DE:AD:BE:EF:00:02', + 'uuid': '00000000-0000-0000-0000-0000000000000002', + 'network_id': 2, + 'network': None, 'instance_id': 0}] -- cgit From aef85a2596e943299542f05e165774250476bc5b Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 1 Sep 2011 16:11:14 -0400 Subject: Probably shouldn't leave that commented out. --- nova/network/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index e8c5c0654..d7b0abdda 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -484,8 +484,8 @@ class NetworkManager(manager.SchedulerDependentManager): for vif in vifs: network = vif['network'] - #if network is None: - # continue + if network is None: + continue # determine which of the instance's IPs belong to this network network_IPs = [fixed_ip['address'] for fixed_ip in fixed_ips if -- cgit From ab8cfda10ed12dd592ef3b06806a80b1922707c1 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 1 Sep 2011 15:29:00 -0500 Subject: moved cidr_v6 back --- nova/network/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index 8fd176c26..b4605eea5 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -499,6 +499,7 @@ class NetworkManager(manager.SchedulerDependentManager): 'bridge': network['bridge'], 'id': network['id'], 'cidr': network['cidr'], + 'cidr_v6': network['cidr_v6'], 'injected': network['injected'], 'vlan': network['vlan'], 'bridge_interface': network['bridge_interface'], @@ -524,7 +525,6 @@ class NetworkManager(manager.SchedulerDependentManager): if network['cidr_v6']: info['ip6s'] = [ip6_dict()] - network_dict['cidr_v6'] = network['cidr_v6'] # TODO(tr3buchet): handle ip6 routes here as well if network['gateway_v6']: info['gateway6'] = network['gateway_v6'] -- cgit From 04cfb6356b920941080fbc58301b6d005d21ac5f Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 1 Sep 2011 15:50:38 -0500 Subject: renamed fake_network_info.py --- nova/tests/fake_network.py | 144 ++++++++++++++++++++++++++++++++++++++++ nova/tests/fake_network_info.py | 144 ---------------------------------------- nova/tests/test_libvirt.py | 6 +- nova/tests/test_network.py | 4 +- 4 files changed, 149 insertions(+), 149 deletions(-) create mode 100644 nova/tests/fake_network.py delete mode 100644 nova/tests/fake_network_info.py diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py new file mode 100644 index 000000000..2e8cf60ec --- /dev/null +++ b/nova/tests/fake_network.py @@ -0,0 +1,144 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Rackspace +# 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 db +from nova import flags +from nova import test +from nova.network import manager as network_manager + + +HOST = "testhost" +FLAGS = flags.FLAGS + + +class FakeModel(dict): + """Represent a model from the db""" + def __init__(self, *args, **kwargs): + self.update(kwargs) + + def __getattr__(self, name): + return self[name] + + +def fake_network(n, ipv6=None): + if ipv6 == None: + ipv6 = FLAGS.use_ipv6 + rval = {'id': n, + 'label': 'test%d' % n, + 'injected': False, + 'multi_host': False, + 'cidr': '192.168.%d.0/24' % n, + 'cidr_v6': None, + 'netmask': '255.255.255.0', + 'netmask_v6': None, + 'bridge': 'fake_br%d' % n, + 'bridge_interface': 'fake_eth%d' % n, + 'gateway': '192.168.%d.1' % n, + 'gateway_v6': None, + 'broadcast': '192.168.%d.255' % n, + 'dns1': '192.168.%d.3' % n, + 'dns2': '192.168.%d.4' % n, + 'vlan': None, + 'host': None, + 'project_id': 'fake_project', + 'vpn_public_address': '192.168.%d.2' % n} + if ipv6: + rval['cidr_v6'] = '2001:db8:0:%x::/64' % n + rval['gateway_v6'] = '2001:db8:0:%x::1' % n + rval['netmask_v6'] = '64' + + return rval + + +def fixed_ips(num_networks, num_ips, num_floating_ips=0): + for network in xrange(num_networks): + for ip in xrange(num_ips): + id = network * num_ips + ip + f_ips = [floating_ips(id).next() for i in xrange(num_floating_ips)] + yield {'id': id, + 'network_id': network, + 'address': '192.168.%d.1%02d' % (network, ip), + 'instance_id': 0, + 'allocated': False, + # and since network_id and vif_id happen to be equivalent + 'virtual_interface_id': network, + 'floating_ips': [FakeModel(**ip) for ip in f_ips]} + + +flavor = {'id': 0, + 'name': 'fake_flavor', + 'memory_mb': 2048, + 'vcpus': 2, + 'local_gb': 10, + 'flavor_id': 0, + 'swap': 0, + 'rxtx_quota': 0, + 'rxtx_cap': 3} + + +def floating_ips(fixed_ip_id): + for i in xrange(154): + yield {'id': 0, + 'address': '10.10.10.%d' % (i + 100), + 'fixed_ip_id': fixed_ip_id, + 'project_id': None, + 'auto_assigned': False} + + +def vifs(n): + for x in xrange(n): + yield {'id': x, + 'address': 'DE:AD:BE:EF:00:%02x' % x, + 'uuid': '00000000-0000-0000-0000-00000000000000%02d' % x, + 'network_id': x, + 'network': FakeModel(**fake_network(x)), + 'instance_id': 0} + + +def ipv4_like(ip, s): + ip = ip.split('.') + s = s.split('.') + + for i, octet in enumerate(s): + if octet == '*': + continue + if octet != ip[i]: + return False + return True + + +def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): + # stubs is the self.stubs from the test + # ips_per_vif is the number of ips each vif will have + # num_floating_ips is number of float ips for each fixed ip + network = network_manager.FlatManager(host=HOST) + network.db = db + + def fixed_ips_fake(*args, **kwargs): + return list(fixed_ips(n, ips_per_vif)) + + def virtual_interfaces_fake(*args, **kwargs): + return [vif for vif in vifs(n)] + + def instance_type_fake(*args, **kwargs): + return flavor + + stubs.Set(db, 'fixed_ip_get_by_instance', fixed_ips_fake) + stubs.Set(db, 'virtual_interface_get_by_instance', virtual_interfaces_fake) + stubs.Set(db, 'instance_type_get', instance_type_fake) + + return network.get_instance_nw_info(None, 0, 0, None) diff --git a/nova/tests/fake_network_info.py b/nova/tests/fake_network_info.py deleted file mode 100644 index 2e8cf60ec..000000000 --- a/nova/tests/fake_network_info.py +++ /dev/null @@ -1,144 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Rackspace -# 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 db -from nova import flags -from nova import test -from nova.network import manager as network_manager - - -HOST = "testhost" -FLAGS = flags.FLAGS - - -class FakeModel(dict): - """Represent a model from the db""" - def __init__(self, *args, **kwargs): - self.update(kwargs) - - def __getattr__(self, name): - return self[name] - - -def fake_network(n, ipv6=None): - if ipv6 == None: - ipv6 = FLAGS.use_ipv6 - rval = {'id': n, - 'label': 'test%d' % n, - 'injected': False, - 'multi_host': False, - 'cidr': '192.168.%d.0/24' % n, - 'cidr_v6': None, - 'netmask': '255.255.255.0', - 'netmask_v6': None, - 'bridge': 'fake_br%d' % n, - 'bridge_interface': 'fake_eth%d' % n, - 'gateway': '192.168.%d.1' % n, - 'gateway_v6': None, - 'broadcast': '192.168.%d.255' % n, - 'dns1': '192.168.%d.3' % n, - 'dns2': '192.168.%d.4' % n, - 'vlan': None, - 'host': None, - 'project_id': 'fake_project', - 'vpn_public_address': '192.168.%d.2' % n} - if ipv6: - rval['cidr_v6'] = '2001:db8:0:%x::/64' % n - rval['gateway_v6'] = '2001:db8:0:%x::1' % n - rval['netmask_v6'] = '64' - - return rval - - -def fixed_ips(num_networks, num_ips, num_floating_ips=0): - for network in xrange(num_networks): - for ip in xrange(num_ips): - id = network * num_ips + ip - f_ips = [floating_ips(id).next() for i in xrange(num_floating_ips)] - yield {'id': id, - 'network_id': network, - 'address': '192.168.%d.1%02d' % (network, ip), - 'instance_id': 0, - 'allocated': False, - # and since network_id and vif_id happen to be equivalent - 'virtual_interface_id': network, - 'floating_ips': [FakeModel(**ip) for ip in f_ips]} - - -flavor = {'id': 0, - 'name': 'fake_flavor', - 'memory_mb': 2048, - 'vcpus': 2, - 'local_gb': 10, - 'flavor_id': 0, - 'swap': 0, - 'rxtx_quota': 0, - 'rxtx_cap': 3} - - -def floating_ips(fixed_ip_id): - for i in xrange(154): - yield {'id': 0, - 'address': '10.10.10.%d' % (i + 100), - 'fixed_ip_id': fixed_ip_id, - 'project_id': None, - 'auto_assigned': False} - - -def vifs(n): - for x in xrange(n): - yield {'id': x, - 'address': 'DE:AD:BE:EF:00:%02x' % x, - 'uuid': '00000000-0000-0000-0000-00000000000000%02d' % x, - 'network_id': x, - 'network': FakeModel(**fake_network(x)), - 'instance_id': 0} - - -def ipv4_like(ip, s): - ip = ip.split('.') - s = s.split('.') - - for i, octet in enumerate(s): - if octet == '*': - continue - if octet != ip[i]: - return False - return True - - -def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): - # stubs is the self.stubs from the test - # ips_per_vif is the number of ips each vif will have - # num_floating_ips is number of float ips for each fixed ip - network = network_manager.FlatManager(host=HOST) - network.db = db - - def fixed_ips_fake(*args, **kwargs): - return list(fixed_ips(n, ips_per_vif)) - - def virtual_interfaces_fake(*args, **kwargs): - return [vif for vif in vifs(n)] - - def instance_type_fake(*args, **kwargs): - return flavor - - stubs.Set(db, 'fixed_ip_get_by_instance', fixed_ips_fake) - stubs.Set(db, 'virtual_interface_get_by_instance', virtual_interfaces_fake) - stubs.Set(db, 'instance_type_get', instance_type_fake) - - return network.get_instance_nw_info(None, 0, 0, None) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index fe5470a6f..190e197f5 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -36,13 +36,13 @@ from nova.api.ec2 import cloud from nova.compute import power_state from nova.virt.libvirt import connection from nova.virt.libvirt import firewall -from nova.tests import fake_network_info +from nova.tests import fake_network libvirt = None FLAGS = flags.FLAGS -_fake_network_info = fake_network_info.fake_get_instance_nw_info -_ipv4_like = fake_network_info.ipv4_like +_fake_network_info = fake_network.fake_get_instance_nw_info +_ipv4_like = fake_network.ipv4_like def _concurrency(wait, done, target): diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 803868cd9..a0079e120 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -22,7 +22,7 @@ from nova import exception from nova import log as logging from nova import test from nova.network import manager as network_manager -from nova.tests import fake_network_info +from nova.tests import fake_network LOG = logging.getLogger('nova.tests.network') @@ -132,7 +132,7 @@ class FlatNetworkTestCase(test.TestCase): is_admin=False) def test_get_instance_nw_info(self): - fake_get_instance_nw_info = fake_network_info.fake_get_instance_nw_info + fake_get_instance_nw_info = fake_network.fake_get_instance_nw_info nw_info = fake_get_instance_nw_info(self.stubs, 0, 2) self.assertFalse(nw_info) -- cgit From 43a392814150e49769e935f4972c9901612570af Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Thu, 1 Sep 2011 14:03:22 -0700 Subject: added unit tests and cleanup of import statements --- .../api/openstack/contrib/test_createserverext.py | 113 +++++++++++++++++---- 1 file changed, 95 insertions(+), 18 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index e5eed14fe..739312399 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -15,7 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -import base64 +import datetime import json import unittest from xml.dom import minidom @@ -26,15 +26,7 @@ import webob from nova import exception from nova import flags from nova import test -from nova import utils import nova.api.openstack -from nova.api.openstack import servers -from nova.api.openstack.contrib import createserverext -import nova.compute.api - -import nova.scheduler.api -import nova.image.fake -import nova.rpc from nova.tests.api.openstack import fakes @@ -51,22 +43,41 @@ DUPLICATE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'), INVALID_NETWORKS = [('invalid', 'invalid-ip-address')] +INSTANCE = { + "id": 1, + "display_name": "test_server", + "uuid": FAKE_UUID, + "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), + "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0), + "security_groups": [{"id": 1, "name": "test"}] + } + +def return_server_by_id(context, id, session=None): + INSTANCE['id'] = id + return INSTANCE + + +def return_security_group_non_existing(context, project_id, group_name): + raise exception.SecurityGroupNotFoundForProject(project_id=project_id, + security_group_id=group_name) + +def return_security_group_get_by_name(context, project_id, group_name): + return {'id': 1, 'name': group_name} + + +def return_security_group_get(context, security_group_id, session): + return {'id': security_group_id} + + +def return_instance_add_security_group(context, instance_id, security_group_id): + pass class CreateserverextTest(test.TestCase): def setUp(self): super(CreateserverextTest, self).setUp() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.auth_data = {} - fakes.FakeAuthDatabase.data = {} - fakes.stub_out_auth(self.stubs) - fakes.stub_out_image_service(self.stubs) - fakes.stub_out_key_pair_funcs(self.stubs) - self.allow_admin = FLAGS.allow_admin_api def tearDown(self): - self.stubs.UnsetAll() - FLAGS.allow_admin_api = self.allow_admin super(CreateserverextTest, self).tearDown() def _setup_mock_compute_api(self): @@ -87,6 +98,7 @@ class CreateserverextTest(test.TestCase): self.networks = kwargs['requested_networks'] else: self.networks = None + return [{'id': '1234', 'display_name': 'fakeinstance', 'uuid': FAKE_UUID, 'created_at': "", @@ -107,6 +119,18 @@ class CreateserverextTest(test.TestCase): '_get_kernel_ramdisk_from_image', make_stub_method((1, 1))) return compute_api + def _create_security_group_request_dict(self, security_groups): + server = {} + server['name'] = 'new-server-test' + server['imageRef'] = 1 + server['flavorRef'] = 1 + if security_groups is not None: + sg_list = [] + for name in security_groups: + sg_list.append({'name': name}) + server['security_groups'] = sg_list + return {'server': server} + def _create_networks_request_dict(self, networks): server = {} server['name'] = 'new-server-test' @@ -304,3 +328,56 @@ class CreateserverextTest(test.TestCase): self.assertEquals(response.status_int, 202) self.assertEquals(compute_api.networks, [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)]) + + def test_create_instance_with_security_group_empty_list_json(self): + security_groups = [] + body_dict = self._create_security_group_request_dict(security_groups) + request = self._get_create_request_json(body_dict) + response = request.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + res_dict = json.loads(response.body) + self.assertEquals([{'name': "default"}], res_dict['server']['security_groups']) + + def test_create_instance_with_security_group_non_existing_json(self): + security_groups = ['non-existing'] + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group_non_existing) + body_dict = self._create_security_group_request_dict(security_groups) + request = self._get_create_request_json(body_dict) + response = request.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_create_instance_with_security_group_json(self): + security_groups = ['test', 'test1'] + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group_get_by_name) + self.stubs.Set(nova.db.api, 'instance_add_security_group', + return_instance_add_security_group) + body_dict = self._create_security_group_request_dict(security_groups) + request = self._get_create_request_json(body_dict) + response = request.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + + def test_get_server_by_id_verify_security_groups_json(self): + self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) + req = webob.Request.blank('/v1.1/123/os-create-server-ext/1') + req.headers['Content-Type'] = 'application/json' + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 200) + res_dict = json.loads(response.body) + expected_security_group = [{"name": "test"}] + self.assertEquals(res_dict['server']['security_groups'], + expected_security_group) + + def test_get_server_by_id_verify_security_groups_xml(self): + self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) + req = webob.Request.blank('/v1.1/123/os-create-server-ext/1') + req.headers['Accept'] = 'application/xml' + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 200) + dom = minidom.parseString(response.body) + server = dom.childNodes[0] + security_groups = server.getElementsByTagName('security_groups')[0] + security_group = security_groups.getElementsByTagName('security_group')[0] + self.assertEqual(INSTANCE['security_groups'][0]['name'], security_group.getAttribute("name")) + -- cgit From 2d2d9a5f5caed27d9ade06b2dbc56b793b7e5d3b Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Thu, 1 Sep 2011 14:32:48 -0700 Subject: Deleted debug messages --- nova/tests/api/openstack/contrib/test_createserverext.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index d6600d054..ba8fb925e 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -32,8 +32,6 @@ import nova.api.openstack from nova.tests.api.openstack import fakes -from nova import log as logging -LOG = logging.getLogger("api.nova.openstack.etere") FLAGS = flags.FLAGS FLAGS.verbose = True @@ -388,7 +386,6 @@ class CreateserverextTest(test.TestCase): body_dict = self._create_security_group_request_dict(security_groups) request = self._get_create_request_json(body_dict) response = request.get_response(fakes.wsgi_app()) - LOG.debug(response) self.assertEquals(response.status_int, 202) def test_get_server_by_id_verify_security_groups_json(self): -- cgit From f0a6c35149a1b9cc278cd3ba960861da9189b5bf Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 1 Sep 2011 14:39:34 -0700 Subject: remove references to MelangeIPAMTest, as they cannot be used yet --- nova/tests/test_quantum.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 0fa4184b1..42acf03e7 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -249,13 +249,3 @@ class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): with session.begin(): for fip_ref in result: session.delete(fip_ref) - -# FIXME(danwent): Cannot run this unit tests automatically for now, as -# it requires melange to be running locally. -# -#class QuantumMelangeIPAMTestCase(QuantumTestCaseBase, test.TestCase): -# -# def setUp(self): -# super(QuantumMelangeIPAMTestCase, self).setUp() -# self.net_man = quantum_manager.QuantumManager( \ -# ipam_lib="nova.network.quantum.melange_ipam_lib") -- cgit From 527670d632788d20aca7a3f12495d4c97e036d51 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 1 Sep 2011 14:40:30 -0700 Subject: melange testing cleanup, localization cleanup --- nova/network/quantum/client.py | 5 +++-- nova/network/quantum/fake.py | 4 ++-- nova/network/quantum/manager.py | 19 +++++++++---------- nova/network/quantum/melange_connection.py | 19 +++++++++++-------- nova/network/quantum/melange_ipam_lib.py | 7 ++++--- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 455bb8a79..f4936695e 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -172,8 +172,9 @@ class Client(object): c = connection_type(self.host, self.port) if self.logger: - self.logger.debug(_("Quantum Client Request:\n%s %s\n" % - (method, action))) + self.logger.debug( + _("Quantum Client Request:\n%(method)s %(action)s\n" % + locals())) if body: self.logger.debug(body) diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index 6a4005c59..4ecddd2ae 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -78,8 +78,8 @@ class FakeQuantumClientConnection(object): def detach_and_delete_port(self, tenant_id, net_id, port_id): if not self.network_exists(tenant_id, net_id): raise exception.NotFound( - _("network %s does not exist for tenant %s" % - (net_id, tenant_id))) + _("network %(net_id)s does not exist " + "for tenant %(tenant_id)s" % locals())) del self.nets[net_id]['ports'][port_id] def get_port_by_attachment(self, tenant_id, attachment_id): diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 153f6c0f2..fa16475ac 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -91,8 +91,8 @@ class QuantumManager(manager.FlatManager): if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): raise Exception(_("Unable to find existing quantum " \ - " network for tenant '%s' with net-id '%s'" % \ - (q_tenant_id, quantum_net_id))) + " network for tenant '%(q_tenant_id)s' with " + "net-id '%(quantum_net_id)s'" % locals())) else: # otherwise, create network from default quantum pool quantum_net_id = self.q_conn.create_network(q_tenant_id, label) @@ -252,17 +252,18 @@ class QuantumManager(manager.FlatManager): 'dns': [], 'ips': [ip_dict(ip, v4_subnet) for ip in v4_ips]} - if v6_subnet['cidr']: - network_dict['cidr_v6'] = v6_subnet['cidr'] - info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips] + if v6_subnet: + if v6_subnet['cidr']: + network_dict['cidr_v6'] = v6_subnet['cidr'] + info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips] - if v6_subnet['gateway']: - info['gateway6'] = v6_subnet['gateway'] + if v6_subnet['gateway']: + info['gateway6'] = v6_subnet['gateway'] dns_dict = {} for s in [v4_subnet, v6_subnet]: for k in ['dns1', 'dns2']: - if s[k]: + if s and s[k]: dns_dict[s[k]] = None info['dns'] = [d for d in dns_dict.keys()] @@ -308,8 +309,6 @@ class QuantumManager(manager.FlatManager): except exception.InstanceNotFound: LOG.error(_("Attempted to deallocate non-existent instance: %s" % (instance_id))) - self._do_trigger_security_group_members_refresh_for_instance( - instance_id) def validate_networks(self, context, networks): """ Validates that this tenant has quantum networks with the associated diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index 4dc35d15d..5a79eff77 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -48,6 +48,7 @@ class MelangeConnection(object): self.host = host self.port = port self.use_ssl = use_ssl + self.version = "v0.1" def get(self, path, params={}, headers={}): return self.do_request("GET", path, params=params, headers=headers) @@ -66,7 +67,9 @@ class MelangeConnection(object): def do_request(self, method, path, body=None, headers={}, params={}): - url = path + '.json?' + urllib.urlencode(params) + url = "/%s/%s.json?%s" % (self.version, + path, + urllib.urlencode(params)) try: connection = self._get_connection() @@ -75,7 +78,7 @@ class MelangeConnection(object): response_str = response.read() if response.status < 400: return response_str - raise Exception(_("Server returned error: %s", response_str)) + raise Exception(_("Server returned error: %s" % response_str)) except (socket.error, IOError), e: raise Exception(_("Unable to connect to " "server. Got error: %s" % e)) @@ -86,7 +89,7 @@ class MelangeConnection(object): request_body = (json.dumps(dict(network=dict(mac_address=mac_address, tenant_id=project_id))) if mac_address else None) - url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + url = ("ipam%(tenant_scope)s/networks/%(network_id)s/" "interfaces/%(vif_id)s/ip_allocations" % locals()) response = self.post(url, body=request_body, headers=json_content_type) @@ -96,7 +99,7 @@ class MelangeConnection(object): project_id=None, dns1=None, dns2=None): tenant_scope = "/tenants/%s" % project_id if project_id else "" - url = "/ipam%(tenant_scope)s/ip_blocks" % locals() + url = "ipam%(tenant_scope)s/ip_blocks" % locals() req_params = dict(ip_block=dict(cidr=cidr, network_id=network_id, type='private', dns1=dns1, dns2=dns2)) @@ -106,14 +109,14 @@ class MelangeConnection(object): def delete_block(self, block_id, project_id=None): tenant_scope = "/tenants/%s" % project_id if project_id else "" - url = "/ipam%(tenant_scope)s/ip_blocks/%(block_id)s" % locals() + url = "ipam%(tenant_scope)s/ip_blocks/%(block_id)s" % locals() self.delete(url, headers=json_content_type) def get_blocks(self, project_id=None): tenant_scope = "/tenants/%s" % project_id if project_id else "" - url = "/ipam%(tenant_scope)s/ip_blocks" % locals() + url = "ipam%(tenant_scope)s/ip_blocks" % locals() response = self.get(url, headers=json_content_type) return json.loads(response) @@ -121,7 +124,7 @@ class MelangeConnection(object): def get_allocated_ips(self, network_id, vif_id, project_id=None): tenant_scope = "/tenants/%s" % project_id if project_id else "" - url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + url = ("ipam%(tenant_scope)s/networks/%(network_id)s/" "interfaces/%(vif_id)s/ip_allocations" % locals()) response = self.get(url, headers=json_content_type) @@ -130,7 +133,7 @@ class MelangeConnection(object): def deallocate_ips(self, network_id, vif_id, project_id=None): tenant_scope = "/tenants/%s" % project_id if project_id else "" - url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + url = ("ipam%(tenant_scope)s/networks/%(network_id)s/" "interfaces/%(vif_id)s/ip_allocations" % locals()) self.delete(url, headers=json_content_type) diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 71c0d7ce6..dcee0a3f9 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -17,6 +17,7 @@ from netaddr import IPNetwork +from nova import db from nova import exception from nova import flags from nova import log as logging @@ -68,7 +69,8 @@ class QuantumMelangeIPAMLib(object): "project_id": project_id, "priority": priority, "label": label} - network = self.db.network_create_safe(context, net) + admin_context = context.elevated() + network = db.network_create_safe(admin_context, net) def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): """ Pass call to allocate fixed IP on to Melange""" @@ -100,8 +102,7 @@ class QuantumMelangeIPAMLib(object): self.m_conn.delete_block(b['id'], tenant_id) network = db.network_get_by_uuid(admin_context, net_id) - if network is not None: - db.network_delete_safe(context, network['id']) + db.network_delete_safe(context, network['id']) def get_project_and_global_net_ids(self, context, project_id): """ Fetches all networks associated with this project, or -- cgit From 38373bf8f60dd068dec69933d1456a8deb75bf8e Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 1 Sep 2011 15:02:09 -0700 Subject: move content of quantum/fake.py to test_quantum.py in unit testing class (most original content has been removed anyway) --- nova/network/quantum/fake.py | 92 ----------------------------------------- nova/network/quantum/manager.py | 9 ++-- nova/tests/test_quantum.py | 76 +++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 99 deletions(-) delete mode 100644 nova/network/quantum/fake.py diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py deleted file mode 100644 index 4ecddd2ae..000000000 --- a/nova/network/quantum/fake.py +++ /dev/null @@ -1,92 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Nicira Networks, Inc -# 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 exception -from nova import ipv6 -from nova import log as logging -from nova import utils - - -LOG = logging.getLogger("network.quantum.fake") - - -# this class can be used for unit functional/testing on nova, -# as it does not actually make remote calls to the Quantum service -class FakeQuantumClientConnection(object): - - def __init__(self): - self.nets = {} - - def get_networks_for_tenant(self, tenant_id): - net_ids = [] - for net_id, n in self.nets.items(): - if n['tenant-id'] == tenant_id: - net_ids.append(net_id) - return net_ids - - def create_network(self, tenant_id, network_name): - - uuid = str(utils.gen_uuid()) - self.nets[uuid] = {'net-name': network_name, - 'tenant-id': tenant_id, - 'ports': {}} - return uuid - - def delete_network(self, tenant_id, net_id): - if self.nets[net_id]['tenant-id'] == tenant_id: - del self.nets[net_id] - - def network_exists(self, tenant_id, net_id): - try: - return self.nets[net_id]['tenant-id'] == tenant_id - except KeyError: - return False - - def _confirm_not_attached(self, interface_id): - for n in self.nets.values(): - for p in n['ports'].values(): - if p['attachment-id'] == interface_id: - raise Exception(_("interface '%s' is already attached" % - interface_id)) - - def create_and_attach_port(self, tenant_id, net_id, interface_id): - if not self.network_exists(tenant_id, net_id): - raise Exception( - _("network %(net_id)s does not exist for tenant %(tenant_id)" - % locals())) - - self._confirm_not_attached(interface_id) - uuid = str(utils.gen_uuid()) - self.nets[net_id]['ports'][uuid] = \ - {"port-state": "ACTIVE", - "attachment-id": interface_id} - - def detach_and_delete_port(self, tenant_id, net_id, port_id): - if not self.network_exists(tenant_id, net_id): - raise exception.NotFound( - _("network %(net_id)s does not exist " - "for tenant %(tenant_id)s" % locals())) - del self.nets[net_id]['ports'][port_id] - - def get_port_by_attachment(self, tenant_id, attachment_id): - for net_id, n in self.nets.items(): - if n['tenant-id'] == tenant_id: - for port_id, p in n['ports'].items(): - if p['attachment-id'] == attachment_id: - return (net_id, port_id) - - return (None, None) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index fa16475ac..db2b000de 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -50,17 +50,16 @@ class QuantumManager(manager.FlatManager): Support for these capabilities are targted for future releases. """ - def __init__(self, ipam_lib=None, *args, **kwargs): + def __init__(self, q_conn=None, ipam_lib=None, *args, **kwargs): """ Initialize two key libraries, the connection to a Quantum service, and the library for implementing IPAM. Calls inherited FlatManager constructor. """ - if FLAGS.fake_network: - self.q_conn = fake.FakeQuantumClientConnection() - else: - self.q_conn = quantum_connection.QuantumClientConnection() + if not q_conn: + q_conn = quantum_connection.QuantumClientConnection() + self.q_conn = q_conn if not ipam_lib: ipam_lib = FLAGS.quantum_ipam_lib diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 42acf03e7..0b1a1f204 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -20,12 +20,82 @@ from nova import db from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from nova import exception +from nova import ipv6 from nova import log as logging from nova.network.quantum import manager as quantum_manager from nova import test +from nova import utils LOG = logging.getLogger('nova.tests.quantum_network') + +# this class can be used for unit functional/testing on nova, +# as it does not actually make remote calls to the Quantum service +class FakeQuantumClientConnection(object): + + def __init__(self): + self.nets = {} + + def get_networks_for_tenant(self, tenant_id): + net_ids = [] + for net_id, n in self.nets.items(): + if n['tenant-id'] == tenant_id: + net_ids.append(net_id) + return net_ids + + def create_network(self, tenant_id, network_name): + + uuid = str(utils.gen_uuid()) + self.nets[uuid] = {'net-name': network_name, + 'tenant-id': tenant_id, + 'ports': {}} + return uuid + + def delete_network(self, tenant_id, net_id): + if self.nets[net_id]['tenant-id'] == tenant_id: + del self.nets[net_id] + + def network_exists(self, tenant_id, net_id): + try: + return self.nets[net_id]['tenant-id'] == tenant_id + except KeyError: + return False + + def _confirm_not_attached(self, interface_id): + for n in self.nets.values(): + for p in n['ports'].values(): + if p['attachment-id'] == interface_id: + raise Exception(_("interface '%s' is already attached" % + interface_id)) + + def create_and_attach_port(self, tenant_id, net_id, interface_id): + if not self.network_exists(tenant_id, net_id): + raise Exception( + _("network %(net_id)s does not exist for tenant %(tenant_id)" + % locals())) + + self._confirm_not_attached(interface_id) + uuid = str(utils.gen_uuid()) + self.nets[net_id]['ports'][uuid] = \ + {"port-state": "ACTIVE", + "attachment-id": interface_id} + + def detach_and_delete_port(self, tenant_id, net_id, port_id): + if not self.network_exists(tenant_id, net_id): + raise exception.NotFound( + _("network %(net_id)s does not exist " + "for tenant %(tenant_id)s" % locals())) + del self.nets[net_id]['ports'][port_id] + + def get_port_by_attachment(self, tenant_id, attachment_id): + for net_id, n in self.nets.items(): + if n['tenant-id'] == tenant_id: + for port_id, p in n['ports'].items(): + if p['attachment-id'] == attachment_id: + return (net_id, port_id) + + return (None, None) + networks = [{'label': 'project1-net1', 'injected': False, 'multi_host': False, @@ -230,8 +300,10 @@ class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): def setUp(self): super(QuantumNovaIPAMTestCase, self).setUp() - self.net_man = quantum_manager.QuantumManager( \ - ipam_lib="nova.network.quantum.nova_ipam_lib") + + self.net_man = quantum_manager.QuantumManager( + ipam_lib="nova.network.quantum.nova_ipam_lib", + q_conn=FakeQuantumClientConnection()) # Tests seem to create some networks by default, which # we don't want. So we delete them. -- cgit From e6e3f46bf449fa371a584720c12c21e0832f4160 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Thu, 1 Sep 2011 17:15:21 -0500 Subject: import filters in scheduler/host_filter.py so default_host_filter gets added to FLAGS; rework SchedulerManager() to only catch missing 'schedule_' attribute and report other missing attributes --- nova/scheduler/host_filter.py | 1 + nova/scheduler/manager.py | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 826a99b0a..4024ec854 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -31,6 +31,7 @@ import types from nova import exception from nova import flags import nova.scheduler +from nova.scheduler import filters FLAGS = flags.FLAGS diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 0e395ee79..92bb1ed6e 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -93,12 +93,14 @@ class SchedulerManager(manager.Manager): driver_method = 'schedule_%s' % method elevated = context.elevated() try: - host = getattr(self.driver, driver_method)(elevated, *args, - **kwargs) + real_meth = getattr(self.driver, driver_method) + args = (elevated,) + args except AttributeError, e: LOG.warning(_("Driver Method %(driver_method)s missing: %(e)s." - "Reverting to schedule()") % locals()) - host = self.driver.schedule(elevated, topic, *args, **kwargs) + "Reverting to schedule()") % locals()) + real_meth = self.driver.schedule + args = (elevated, topic) + args + real_meth(*args, **kwargs) if not host: LOG.debug(_("%(topic)s %(method)s handled in Scheduler") -- cgit From 81f3c9e0d1657783edf8f2ab7e055c5628af33ec Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Fri, 2 Sep 2011 04:53:10 +0000 Subject: Launchpad automatic translations update. --- po/pt_BR.po | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/po/pt_BR.po b/po/pt_BR.po index e10b0c0de..c4eb487d6 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-09-01 04:15+0000\n" -"Last-Translator: Daniel Negri \n" +"PO-Revision-Date: 2011-09-01 20:56+0000\n" +"Last-Translator: Robson Negreiros Bezerra \n" "Language-Team: Brazilian Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-09-01 05:02+0000\n" -"X-Generator: Launchpad (build 13827)\n" +"X-Launchpad-Export-Date: 2011-09-02 04:53+0000\n" +"X-Generator: Launchpad (build 13830)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -453,11 +453,13 @@ msgid "" "Detach volume %(volume_id)s from mountpoint %(mp)s on instance " "%(instance_id)s" msgstr "" +"Desconectando volume %(volume_id)s do ponto de montagem %(mp)s na instância " +"%(instance_id)s" #: ../nova/compute/manager.py:588 #, python-format msgid "Detaching volume from unknown instance %s" -msgstr "" +msgstr "Desconectando volume da instância desconhecida %s" #: ../nova/scheduler/simple.py:53 #, python-format -- cgit From 1081b9d52026afb84128c15a1df0998f80810ce9 Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Fri, 2 Sep 2011 15:48:32 +0200 Subject: Correct tests associated. --- nova/tests/api/openstack/test_servers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 3559e6de5..e5ebedf0e 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3490,10 +3490,14 @@ class TestGetKernelRamdiskFromImage(test.TestCase): self.assertRaises(exception.NotFound, self._get_k_r, image_meta) def test_ami_no_ramdisk(self): - """If an ami is missing a ramdisk it should raise NotFound""" + """If an ami is missing a ramdisk, return kernel ID and None for + ramdisk ID + """ image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', 'properties': {'kernel_id': 1}} - self.assertRaises(exception.NotFound, self._get_k_r, image_meta) + kernel_id, ramdisk_id = self._get_k_r(image_meta) + self.assertEqual(kernel_id, 1) + self.assertEqual(ramdisk_id, None) def test_ami_kernel_ramdisk_present(self): """Return IDs if both kernel and ramdisk are present""" -- cgit From c5a90f842c99f94af2e321051d85e81f49c1e692 Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Fri, 2 Sep 2011 16:01:13 +0200 Subject: Update Authors file. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index b9e7a7d23..2961b90ef 100644 --- a/Authors +++ b/Authors @@ -30,6 +30,7 @@ Devendra Modium Devin Carlen Donal Lafferty Ed Leafe +Edouard Thuleau Eldar Nugaev Eric Day Eric Windisch -- cgit From 78bb09eacedb91f0b5bf294ede9372768409590b Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Fri, 2 Sep 2011 16:17:18 +0200 Subject: Email contact error. --- Authors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authors b/Authors index 2961b90ef..eff58692e 100644 --- a/Authors +++ b/Authors @@ -30,7 +30,7 @@ Devendra Modium Devin Carlen Donal Lafferty Ed Leafe -Edouard Thuleau +Edouard Thuleau Eldar Nugaev Eric Day Eric Windisch -- cgit From f119805aa7c8e2dd7f0bafe666d976f3a0c08795 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Fri, 2 Sep 2011 11:00:03 -0500 Subject: Forgot to handle return value --- nova/scheduler/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 92bb1ed6e..bf18abc6c 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -100,7 +100,7 @@ class SchedulerManager(manager.Manager): "Reverting to schedule()") % locals()) real_meth = self.driver.schedule args = (elevated, topic) + args - real_meth(*args, **kwargs) + host = real_meth(*args, **kwargs) if not host: LOG.debug(_("%(topic)s %(method)s handled in Scheduler") -- cgit From 666f7152910838f866ca4b76258b025c27744ffb Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Fri, 2 Sep 2011 12:31:10 -0500 Subject: Add documentation comment --- nova/scheduler/host_filter.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 4024ec854..9f7d34ea7 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -31,6 +31,11 @@ import types from nova import exception from nova import flags import nova.scheduler + +# NOTE(Vek): Even though we don't use filters in here anywhere, we +# depend on default_host_filter being available in FLAGS, +# and that happens only when filters/abstract_filter.py is +# imported. from nova.scheduler import filters -- cgit From c5acb6186318541b1743bf62daa0510c9dba9c48 Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Fri, 2 Sep 2011 20:25:18 +0200 Subject: Email error. --- Authors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authors b/Authors index eff58692e..1160a0741 100644 --- a/Authors +++ b/Authors @@ -30,7 +30,7 @@ Devendra Modium Devin Carlen Donal Lafferty Ed Leafe -Edouard Thuleau +Édouard Thuleau Eldar Nugaev Eric Day Eric Windisch -- cgit From 55f0a6a058546f0ffbf4bee0e92eea2e70d8c76f Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Fri, 2 Sep 2011 20:29:58 +0200 Subject: Email error again. Tired. --- Authors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authors b/Authors index 1160a0741..0beab16a4 100644 --- a/Authors +++ b/Authors @@ -30,7 +30,7 @@ Devendra Modium Devin Carlen Donal Lafferty Ed Leafe -Édouard Thuleau +Édouard Thuleau Eldar Nugaev Eric Day Eric Windisch -- cgit From 5fe5c5dc26276a10b7dc766104a7e2d6c7793dc3 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 11:51:55 -0700 Subject: remove import of 'fake' from nova manager, now that we've moved that to test_quantum.py --- nova/network/quantum/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index db2b000de..c10dc90de 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -22,7 +22,6 @@ from nova import log as logging from nova import manager from nova.network import manager from nova.network.quantum import quantum_connection -from nova.network.quantum import fake from nova import utils LOG = logging.getLogger("quantum_manager") -- cgit From bd1bc5e3c6f52963ce088e2e0a6da41f125d29f1 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 12:11:28 -0700 Subject: more review cleanup --- .../migrate_repo/versions/044_add_network_priority.py | 4 +--- nova/network/quantum/melange_connection.py | 17 +++++++++-------- nova/tests/test_quantum.py | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py index e69380199..e3ee6a85f 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py @@ -25,13 +25,11 @@ meta = MetaData() # Add priority column to networks table priority = Column('priority', Integer()) +networks = Table('networks', meta, autoload=True) def upgrade(migrate_engine): meta.bind = migrate_engine - # grab tables and (column for dropping later) - networks = Table('networks', meta, autoload=True) - try: networks.create_column(priority) except Exception: diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index 5a79eff77..d5a1901aa 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -50,13 +50,13 @@ class MelangeConnection(object): self.use_ssl = use_ssl self.version = "v0.1" - def get(self, path, params={}, headers={}): + def get(self, path, params=None, headers=None): return self.do_request("GET", path, params=params, headers=headers) - def post(self, path, body=None, headers={}): + def post(self, path, body=None, headers=None): return self.do_request("POST", path, body=body, headers=headers) - def delete(self, path, headers={}): + def delete(self, path, headers=None): return self.do_request("DELETE", path, headers=headers) def _get_connection(self): @@ -65,12 +65,13 @@ class MelangeConnection(object): else: return httplib.HTTPConnection(self.host, self.port) - def do_request(self, method, path, body=None, headers={}, params={}): - - url = "/%s/%s.json?%s" % (self.version, - path, - urllib.urlencode(params)) + def do_request(self, method, path, body=None, headers=None, params=None): + headers = headers or {} + params = params or {} + url = "/%s/%s.json" % (self.version, path) + if params: + url += "?%s" % urllib.urlencode(params) try: connection = self._get_connection() connection.request(method, url, body, headers) diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 0b1a1f204..0feec9b99 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -277,10 +277,10 @@ class QuantumTestCaseBase(object): nw_info[1][0]['cidr_v6'].startswith("2001:1db9:")) # v6 address - self.assertTrue(\ + self.assertTrue( nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dbb:") or nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dbb:")) - self.assertTrue(\ + self.assertTrue( nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db9:") or nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db9:")) -- cgit From 435016f27ea36a6780897efe1289328c51e1463f Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 12:31:14 -0700 Subject: move networks declarations within upgrade/downgrade methods --- nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py index e3ee6a85f..9db950c9b 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py @@ -25,11 +25,11 @@ meta = MetaData() # Add priority column to networks table priority = Column('priority', Integer()) -networks = Table('networks', meta, autoload=True) def upgrade(migrate_engine): meta.bind = migrate_engine + networks = Table('networks', meta, autoload=True) try: networks.create_column(priority) except Exception: @@ -39,4 +39,6 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): meta.bind = migrate_engine + + networks = Table('networks', meta, autoload=True) networks.drop_column(priority) -- cgit From bcb6f7d570ed24e0bf083cd4f4c8be0f20e69918 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 12:40:55 -0700 Subject: change db migrate script again to match other similar scripts --- .../db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py index 9db950c9b..b9b0ea37c 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py @@ -22,6 +22,9 @@ from nova import utils meta = MetaData() +networks = Table('networks', meta, + Column("id", Integer(), primary_key=True, nullable=False)) + # Add priority column to networks table priority = Column('priority', Integer()) @@ -29,7 +32,6 @@ priority = Column('priority', Integer()) def upgrade(migrate_engine): meta.bind = migrate_engine - networks = Table('networks', meta, autoload=True) try: networks.create_column(priority) except Exception: @@ -39,6 +41,4 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): meta.bind = migrate_engine - - networks = Table('networks', meta, autoload=True) networks.drop_column(priority) -- cgit From cc3bd1da5edc368871d2c8de0e498ab2649ae0dd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 2 Sep 2011 12:52:02 -0700 Subject: revert description changes, use metadata['description'] if it is set to populate field in db --- nova/api/openstack/create_instance_helper.py | 9 ++++++--- nova/api/openstack/schemas/v1.1/server.rng | 1 - nova/api/openstack/servers.py | 6 +----- nova/api/openstack/views/servers.py | 1 - nova/tests/api/openstack/test_servers.py | 28 ---------------------------- 5 files changed, 7 insertions(+), 38 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index ff3be4a01..289f87921 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -161,6 +161,10 @@ class CreateInstanceHelper(object): 'config_drive': config_drive, 'password': password} + # NOTE(vish): This is solely for compatibility with anything + # that is using the display description field. + metadata = server_dict.get('metadata') or {} + display_description = metadata.get('description') or '' return (extra_values, create_method(context, inst_type, @@ -168,10 +172,9 @@ class CreateInstanceHelper(object): kernel_id=kernel_id, ramdisk_id=ramdisk_id, display_name=name, - display_description=server_dict.\ - get('description', ''), + display_description=display_description, key_name=key_name, - metadata=server_dict.get('metadata', {}), + metadata=metadata, access_ip_v4=server_dict.get('accessIPv4'), access_ip_v6=server_dict.get('accessIPv6'), injected_files=injected_files, diff --git a/nova/api/openstack/schemas/v1.1/server.rng b/nova/api/openstack/schemas/v1.1/server.rng index 203728f48..ef835e408 100644 --- a/nova/api/openstack/schemas/v1.1/server.rng +++ b/nova/api/openstack/schemas/v1.1/server.rng @@ -3,7 +3,6 @@ - diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 7f5463e70..46a111cf5 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -185,9 +185,6 @@ class Controller(object): self.helper._validate_server_name(name) update_dict['display_name'] = name.strip() - if 'description' in body['server']: - description = body['server']['description'] - update_dict['display_description'] = description.strip() if 'accessIPv4' in body['server']: access_ipv4 = body['server']['accessIPv4'] update_dict['access_ip_v4'] = access_ipv4.strip() @@ -881,7 +878,6 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): node.setAttribute('uuid', str(server['uuid'])) node.setAttribute('hostId', str(server['hostId'])) node.setAttribute('name', server['name']) - node.setAttribute('description', server['description']) node.setAttribute('created', str(server['created'])) node.setAttribute('updated', str(server['updated'])) node.setAttribute('status', server['status']) @@ -997,7 +993,7 @@ def create_resource(version='1.0'): "attributes": { "server": ["id", "imageId", "name", "flavorId", "hostId", "status", "progress", "adminPass", "flavorRef", - "imageRef", "userId", "tenantId", "description"], + "imageRef", "userId", "tenantId"], "link": ["rel", "type", "href"], }, "dict_collections": { diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 98ccd817a..ac09b5864 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -68,7 +68,6 @@ class ViewBuilder(object): 'name': inst['display_name'], 'user_id': inst.get('user_id', ''), 'tenant_id': inst.get('project_id', ''), - 'description': inst.get('display_description', ''), 'status': common.status_from_state(vm_state, task_state)} # Return the metadata as a dictionary diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index d5de1aa3c..45ad6e5a8 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2268,34 +2268,6 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict['server']['id'], 1) self.assertEqual(res_dict['server']['name'], 'server_test') - def test_update_server_description_v1_1(self): - DESC = 'updated_desc' - - def server_update(context, id, params): - # assert that parameter conversion from description - # to display_description worked correctly - self.assertEqual(params.get('display_description'), DESC) - return stub_instance(1, - name='server_test', - description=params['display_description']) - - self.stubs.Set(nova.db.api, 'instance_get', - return_server_with_attributes(name='server_test', - description=DESC)) - - self.stubs.Set(nova.db.api, 'instance_update', - server_update) - - req = webob.Request.blank('/v1.1/fake/servers/1') - req.method = 'PUT' - req.content_type = 'application/json' - req.body = json.dumps({'server': {'description': DESC}}) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - res_dict = json.loads(res.body) - self.assertEqual(res_dict['server']['id'], 1) - self.assertEqual(res_dict['server']['description'], DESC) - def test_update_server_access_ipv4_v1_1(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_with_attributes(access_ipv4='0.0.0.0')) -- cgit From 752b6c9e26b718ab86f04c25a8c7f977bbea4a22 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 13:05:24 -0700 Subject: feedback from jk0's review, including removing a lot of spaces from docstrings --- bin/nova-manage | 3 +- nova/db/sqlalchemy/api.py | 2 +- nova/network/manager.py | 2 +- nova/network/quantum/client.py | 45 +++++++++--------- nova/network/quantum/manager.py | 76 +++++++++++++++--------------- nova/network/quantum/melange_connection.py | 2 +- nova/network/quantum/melange_ipam_lib.py | 63 +++++++++++++------------ nova/network/quantum/nova_ipam_lib.py | 56 +++++++++++----------- nova/network/quantum/quantum_connection.py | 34 ++++++------- 9 files changed, 141 insertions(+), 142 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 3a17818b2..6dd2920d1 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -779,8 +779,7 @@ class NetworkCommands(object): def list(self): """List all created networks""" - _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s"\ - "\t%-15s\t%-15s" + _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" print _fmt % (_('id'), _('IPv4'), _('IPv6'), diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 09356e966..e0da2269d 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -946,7 +946,7 @@ def virtual_interface_get_by_address(context, address): def virtual_interface_get_by_uuid(context, vif_uuid): """Gets a virtual interface from the table. - :param vif_uuid: = the uuid of the interface you're looking to get + :param vif_uuid: the uuid of the interface you're looking to get """ session = get_session() vif_ref = session.query(models.VirtualInterface).\ diff --git a/nova/network/manager.py b/nova/network/manager.py index 426ff2f33..6730e808f 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -547,7 +547,7 @@ class NetworkManager(manager.SchedulerDependentManager): 'network_id': network_id, 'uuid': str(utils.gen_uuid())} # try FLAG times to create a vif record with a unique mac_address - for i in xrange(FLAGS.create_unique_mac_address_attempts): + for _ in xrange(FLAGS.create_unique_mac_address_attempts): try: return self.db.virtual_interface_create(context, vif) except exception.VirtualInterfaceCreateException: diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index f4936695e..40c68dfdc 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -22,14 +22,14 @@ import socket import urllib -#FIXME(danwent): All content in this file should be removed once the +# FIXME(danwent): All content in this file should be removed once the # packaging work for the quantum client libraries is complete. # At that point, we will be able to just install the libraries as a # dependency and import from quantum.client.* and quantum.common.* # Until then, we have simplified versions of these classes in this file. class JSONSerializer(object): - """ This is a simple json-only serializer to use until we can just grab + """This is a simple json-only serializer to use until we can just grab the standard serializer from the quantum library. """ def serialize(self, data, content_type): @@ -47,17 +47,17 @@ class JSONSerializer(object): # granular exceptions, for now, just try to distinguish # between the cases we care about. class QuantumNotFoundException(Exception): - """ Indicates that Quantum Server returned 404""" + """Indicates that Quantum Server returned 404""" pass class QuantumServerException(Exception): - """ Indicates any non-404 error from Quantum Server""" + """Indicates any non-404 error from Quantum Server""" pass class QuantumIOException(Exception): - """ Indicates network IO trouble reaching Quantum Server""" + """Indicates network IO trouble reaching Quantum Server""" pass @@ -100,7 +100,7 @@ class Client(object): def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, format="xml", testing_stub=None, key_file=None, cert_file=None, logger=None): - """ Creates a new client to some service. + """Creates a new client to some service. :param host: The host where service resides :param port: The port where service resides @@ -123,7 +123,7 @@ class Client(object): self.logger = logger def get_connection_type(self): - """ Returns the proper connection type """ + """Returns the proper connection type""" if self.testing_stub: return self.testing_stub elif self.use_ssl: @@ -133,7 +133,7 @@ class Client(object): def do_request(self, method, action, body=None, headers=None, params=None): - """ Connects to the server and issues a request. + """Connects to the server and issues a request. Returns the result data, or raises an appropriate exception if HTTP status code is not 2xx @@ -142,7 +142,6 @@ class Client(object): :param headers: mapping of key/value pairs to add as headers :param params: dictionary of key/value pairs to add to append to action - """ # Ensure we have a tenant id @@ -207,7 +206,7 @@ class Client(object): "server. Got error: %s" % e)) def get_status_code(self, response): - """ Returns the integer status code from the response, which + """Returns the integer status code from the response, which can be either a Webob.Response (used in testing) or httplib.Response """ if hasattr(response, 'status_int'): @@ -236,73 +235,73 @@ class Client(object): @api_call def list_networks(self): - """ Fetches a list of all networks for a tenant """ + """Fetches a list of all networks for a tenant""" return self.do_request("GET", self.networks_path) @api_call def show_network_details(self, network): - """ Fetches the details of a certain network """ + """Fetches the details of a certain network""" return self.do_request("GET", self.network_path % (network)) @api_call def create_network(self, body=None): - """ Creates a new network """ + """Creates a new network""" body = self.serialize(body) return self.do_request("POST", self.networks_path, body=body) @api_call def update_network(self, network, body=None): - """ Updates a network """ + """Updates a network""" body = self.serialize(body) return self.do_request("PUT", self.network_path % (network), body=body) @api_call def delete_network(self, network): - """ Deletes the specified network """ + """Deletes the specified network""" return self.do_request("DELETE", self.network_path % (network)) @api_call def list_ports(self, network): - """ Fetches a list of ports on a given network """ + """Fetches a list of ports on a given network""" return self.do_request("GET", self.ports_path % (network)) @api_call def show_port_details(self, network, port): - """ Fetches the details of a certain port """ + """Fetches the details of a certain port""" return self.do_request("GET", self.port_path % (network, port)) @api_call def create_port(self, network, body=None): - """ Creates a new port on a given network """ + """Creates a new port on a given network""" body = self.serialize(body) return self.do_request("POST", self.ports_path % (network), body=body) @api_call def delete_port(self, network, port): - """ Deletes the specified port from a network """ + """Deletes the specified port from a network""" return self.do_request("DELETE", self.port_path % (network, port)) @api_call def set_port_state(self, network, port, body=None): - """ Sets the state of the specified port """ + """Sets the state of the specified port""" body = self.serialize(body) return self.do_request("PUT", self.port_path % (network, port), body=body) @api_call def show_port_attachment(self, network, port): - """ Fetches the attachment-id associated with the specified port """ + """Fetches the attachment-id associated with the specified port""" return self.do_request("GET", self.attachment_path % (network, port)) @api_call def attach_resource(self, network, port, body=None): - """ Sets the attachment-id of the specified port """ + """Sets the attachment-id of the specified port""" body = self.serialize(body) return self.do_request("PUT", self.attachment_path % (network, port), body=body) @api_call def detach_resource(self, network, port): - """ Removes the attachment-id of the specified port """ + """Removes the attachment-id of the specified port""" return self.do_request("DELETE", self.attachment_path % (network, port)) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index c10dc90de..23a9aba0d 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -24,7 +24,7 @@ from nova.network import manager from nova.network.quantum import quantum_connection from nova import utils -LOG = logging.getLogger("quantum_manager") +LOG = logging.getLogger("nova.network.quantum.manager") FLAGS = flags.FLAGS @@ -34,26 +34,26 @@ flags.DEFINE_string('quantum_ipam_lib', class QuantumManager(manager.FlatManager): - """ NetworkManager class that communicates with a Quantum service - via a web services API to provision VM network connectivity. + """NetworkManager class that communicates with a Quantum service + via a web services API to provision VM network connectivity. - For IP Address management, QuantumManager can be configured to - use either Nova's local DB or the Melange IPAM service. + For IP Address management, QuantumManager can be configured to + use either Nova's local DB or the Melange IPAM service. - Currently, the QuantumManager does NOT support any of the 'gateway' - functionality implemented by the Nova VlanManager, including: + Currently, the QuantumManager does NOT support any of the 'gateway' + functionality implemented by the Nova VlanManager, including: * floating IPs * DHCP * NAT gateway - Support for these capabilities are targted for future releases. + Support for these capabilities are targted for future releases. """ def __init__(self, q_conn=None, ipam_lib=None, *args, **kwargs): - """ Initialize two key libraries, the connection to a - Quantum service, and the library for implementing IPAM. + """Initialize two key libraries, the connection to a + Quantum service, and the library for implementing IPAM. - Calls inherited FlatManager constructor. + Calls inherited FlatManager constructor. """ if not q_conn: @@ -70,16 +70,16 @@ class QuantumManager(manager.FlatManager): network_size, cidr_v6, gateway_v6, bridge, bridge_interface, dns1=None, dns2=None, uuid=None, **kwargs): - """ Unlike other NetworkManagers, with QuantumManager, each - create_networks calls should create only a single network. + """Unlike other NetworkManagers, with QuantumManager, each + create_networks calls should create only a single network. - Two scenarios exist: + Two scenarios exist: - no 'uuid' is specified, in which case we contact Quantum and create a new network. - an existing 'uuid' is specified, corresponding to a Quantum network created out of band. - In both cases, we initialize a subnet using the IPAM lib. + In both cases, we initialize a subnet using the IPAM lib. """ if num_networks != 1: raise Exception(_("QuantumManager requires that only one" @@ -101,8 +101,8 @@ class QuantumManager(manager.FlatManager): priority, cidr, gateway_v6, cidr_v6, dns1, dns2) def delete_network(self, context, fixed_range): - """ Lookup network by IPv4 cidr, delete both the IPAM - subnet and the corresponding Quantum network. + """Lookup network by IPv4 cidr, delete both the IPAM + subnet and the corresponding Quantum network. """ project_id = context.project_id quantum_net_id = self.ipam.get_network_id_by_cidr( @@ -113,14 +113,14 @@ class QuantumManager(manager.FlatManager): self.q_conn.delete_network(q_tenant_id, quantum_net_id) def allocate_for_instance(self, context, **kwargs): - """ Called by compute when it is creating a new VM. + """Called by compute when it is creating a new VM. - There are three key tasks: + There are three key tasks: - Determine the number and order of vNICs to create - Allocate IP addresses - Create ports on a Quantum network and attach vNICs. - We support two approaches to determining vNICs: + We support two approaches to determining vNICs: - By default, a VM gets a vNIC for any network belonging to the VM's project, and a vNIC for any "global" network that has a NULL project_id. vNIC order is determined @@ -130,10 +130,10 @@ class QuantumManager(manager.FlatManager): create vNICs, and the vNIC order is determiend by the order in the requested_networks array. - For each vNIC, use the FlatManager to create the entries - in the virtual_interfaces table, contact Quantum to - create a port and attachment the vNIC, and use the IPAM - lib to allocate IP addresses. + For each vNIC, use the FlatManager to create the entries + in the virtual_interfaces table, contact Quantum to + create a port and attachment the vNIC, and use the IPAM + lib to allocate IP addresses. """ instance_id = kwargs.pop('instance_id') instance_type_id = kwargs['instance_type_id'] @@ -181,17 +181,17 @@ class QuantumManager(manager.FlatManager): def get_instance_nw_info(self, context, instance_id, instance_type_id, host): - """ This method is used by compute to fetch all network data - that should be used when creating the VM. + """This method is used by compute to fetch all network data + that should be used when creating the VM. - The method simply loops through all virtual interfaces - stored in the nova DB and queries the IPAM lib to get - the associated IP data. + The method simply loops through all virtual interfaces + stored in the nova DB and queries the IPAM lib to get + the associated IP data. - The format of returned data is 'defined' by the initial - set of NetworkManagers found in nova/network/manager.py . - Ideally this 'interface' will be more formally defined - in the future. + The format of returned data is 'defined' by the initial + set of NetworkManagers found in nova/network/manager.py . + Ideally this 'interface' will be more formally defined + in the future. """ network_info = [] instance = db.instance_get(context, instance_id) @@ -269,10 +269,10 @@ class QuantumManager(manager.FlatManager): return network_info def deallocate_for_instance(self, context, **kwargs): - """ Called when a VM is terminated. Loop through each virtual - interface in the Nova DB and remove the Quantum port and - clear the IP allocation using the IPAM. Finally, remove - the virtual interfaces from the Nova DB. + """Called when a VM is terminated. Loop through each virtual + interface in the Nova DB and remove the Quantum port and + clear the IP allocation using the IPAM. Finally, remove + the virtual interfaces from the Nova DB. """ instance_id = kwargs.get('instance_id') project_id = kwargs.pop('project_id', None) @@ -309,7 +309,7 @@ class QuantumManager(manager.FlatManager): (instance_id))) def validate_networks(self, context, networks): - """ Validates that this tenant has quantum networks with the associated + """Validates that this tenant has quantum networks with the associated UUIDs. This is called by the 'os-create-server-ext' API extension code so that we can return an API error code to the caller if they request an invalid network. diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index d5a1901aa..0c744f080 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -35,7 +35,7 @@ flags.DEFINE_string('melange_port', json_content_type = {'Content-type': "application/json"} -#FIXME(danwent): talk to the Melange folks about creating a +# FIXME(danwent): talk to the Melange folks about creating a # client lib that we can import as a library, instead of # have to have all of the client code in here. class MelangeConnection(object): diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index dcee0a3f9..a0ac10fd3 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -24,7 +24,7 @@ from nova import log as logging from nova.network.quantum import melange_connection -LOG = logging.getLogger("quantum_melange_ipam") +LOG = logging.getLogger("nova.network.quantum.melange_ipam_lib") FLAGS = flags.FLAGS @@ -34,26 +34,26 @@ def get_ipam_lib(net_man): class QuantumMelangeIPAMLib(object): - """ Implements Quantum IP Address Management (IPAM) interface - using the Melange service, which is access using the Melange - web services API. + """Implements Quantum IP Address Management (IPAM) interface + using the Melange service, which is access using the Melange + web services API. """ def __init__(self): - """ Initialize class used to connect to Melange server""" + """Initialize class used to connect to Melange server""" self.m_conn = melange_connection.MelangeConnection() def create_subnet(self, context, label, project_id, quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): - """ Contact Melange and create a subnet for any non-NULL - IPv4 or IPv6 subnets. + """Contact Melange and create a subnet for any non-NULL + IPv4 or IPv6 subnets. - Also create a entry in the Nova networks DB, but only - to store values not represented in Melange or to - temporarily provide compatibility with Nova code that - accesses IPAM data directly via the DB (e.g., nova-api) + Also create a entry in the Nova networks DB, but only + to store values not represented in Melange or to + temporarily provide compatibility with Nova code that + accesses IPAM data directly via the DB (e.g., nova-api) """ tenant_id = project_id or FLAGS.quantum_default_tenant_id if cidr: @@ -73,15 +73,15 @@ class QuantumMelangeIPAMLib(object): network = db.network_create_safe(admin_context, net) def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): - """ Pass call to allocate fixed IP on to Melange""" + """Pass call to allocate fixed IP on to Melange""" tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.allocate_ip(quantum_net_id, vif_ref['uuid'], project_id=tenant_id, mac_address=vif_ref['address']) def get_network_id_by_cidr(self, context, cidr, project_id): - """ Find the Quantum UUID associated with a IPv4 CIDR - address for the specified tenant. + """Find the Quantum UUID associated with a IPv4 CIDR + address for the specified tenant. """ tenant_id = project_id or FLAGS.quantum_default_tenant_id all_blocks = self.m_conn.get_blocks(tenant_id) @@ -91,8 +91,8 @@ class QuantumMelangeIPAMLib(object): raise exception.NotFound(_("No network found for cidr %s" % cidr)) def delete_subnets_by_net_id(self, context, net_id, project_id): - """ Find Melange block associated with the Quantum UUID, - then tell Melange to delete that block. + """Find Melange block associated with the Quantum UUID, + then tell Melange to delete that block. """ admin_context = context.elevated() tenant_id = project_id or FLAGS.quantum_default_tenant_id @@ -105,9 +105,10 @@ class QuantumMelangeIPAMLib(object): db.network_delete_safe(context, network['id']) def get_project_and_global_net_ids(self, context, project_id): - """ Fetches all networks associated with this project, or - that are "global" (i.e., have no project set). - Returns list sorted by 'priority'. + """Fetches all networks associated with this project, or + that are "global" (i.e., have no project set). + Returns list sorted by 'priority' (lowest integer value + is highest priority). """ if project_id is None: raise Exception(_("get_project_and_global_net_ids must be called" @@ -134,8 +135,8 @@ class QuantumMelangeIPAMLib(object): for priority, network_id, tenant_id in priority_nets] def get_subnets_by_net_id(self, context, project_id, net_id): - """ Returns information about the IPv4 and IPv6 subnets - associated with a Quantum Network UUID. + """Returns information about the IPv4 and IPv6 subnets + associated with a Quantum Network UUID. """ # FIXME(danwent): Melange actually returns the subnet info @@ -164,23 +165,23 @@ class QuantumMelangeIPAMLib(object): return (subnet_v4, subnet_v6) def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): - """ Returns a list of IPv4 address strings associated with - the specified virtual interface. + """Returns a list of IPv4 address strings associated with + the specified virtual interface. """ return self._get_ips_by_interface(context, net_id, vif_id, project_id, 4) def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): - """ Returns a list of IPv6 address strings associated with - the specified virtual interface. + """Returns a list of IPv6 address strings associated with + the specified virtual interface. """ return self._get_ips_by_interface(context, net_id, vif_id, project_id, 6) def _get_ips_by_interface(self, context, net_id, vif_id, project_id, ip_version): - """ Helper method to fetch v4 or v6 addresses for a particular - virtual interface. + """Helper method to fetch v4 or v6 addresses for a particular + virtual interface. """ tenant_id = project_id or FLAGS.quantum_default_tenant_id ip_list = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id) @@ -188,8 +189,8 @@ class QuantumMelangeIPAMLib(object): if IPNetwork(ip['address']).version == ip_version] def verify_subnet_exists(self, context, project_id, quantum_net_id): - """ Confirms that a subnet exists that is associated with the - specified Quantum Network UUID. + """Confirms that a subnet exists that is associated with the + specified Quantum Network UUID. """ tenant_id = project_id or FLAGS.quantum_default_tenant_id v4_subnet, v6_subnet = self.get_subnets_by_net_id(context, tenant_id, @@ -197,8 +198,8 @@ class QuantumMelangeIPAMLib(object): return v4_subnet is not None def deallocate_ips_by_vif(self, context, project_id, net_id, vif_ref): - """ Deallocate all fixed IPs associated with the specified - virtual interface. + """Deallocate all fixed IPs associated with the specified + virtual interface. """ tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.deallocate_ips(net_id, vif_ref['uuid'], tenant_id) diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 17236a976..21dee8f6a 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -27,7 +27,7 @@ from nova.network.quantum import melange_connection as melange from nova import utils -LOG = logging.getLogger("quantum_nova_ipam_lib") +LOG = logging.getLogger("nova.network.quantum.nova_ipam_lib") FLAGS = flags.FLAGS @@ -37,15 +37,15 @@ def get_ipam_lib(net_man): class QuantumNovaIPAMLib(object): - """ Implements Quantum IP Address Management (IPAM) interface - using the local Nova database. This implementation is inline - with how IPAM is used by other NetworkManagers. + """Implements Quantum IP Address Management (IPAM) interface + using the local Nova database. This implementation is inline + with how IPAM is used by other NetworkManagers. """ def __init__(self, net_manager): - """ Holds a reference to the "parent" network manager, used - to take advantage of various FlatManager methods to avoid - code duplication. + """Holds a reference to the "parent" network manager, used + to take advantage of various FlatManager methods to avoid + code duplication. """ self.net_manager = net_manager @@ -53,11 +53,11 @@ class QuantumNovaIPAMLib(object): quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): - """ Re-use the basic FlatManager create_networks method to - initialize the networks and fixed_ips tables in Nova DB. + """Re-use the basic FlatManager create_networks method to + initialize the networks and fixed_ips tables in Nova DB. - Also stores a few more fields in the networks table that - are needed by Quantum but not the FlatManager. + Also stores a few more fields in the networks table that + are needed by Quantum but not the FlatManager. """ admin_context = context.elevated() subnet_size = len(netaddr.IPNetwork(cidr)) @@ -85,8 +85,8 @@ class QuantumNovaIPAMLib(object): return network['uuid'] def delete_subnets_by_net_id(self, context, net_id, project_id): - """ Deletes a network based on Quantum UUID. Uses FlatManager - delete_network to avoid duplication. + """Deletes a network based on Quantum UUID. Uses FlatManager + delete_network to avoid duplication. """ admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, net_id) @@ -97,9 +97,9 @@ class QuantumNovaIPAMLib(object): require_disassociated=False) def get_project_and_global_net_ids(self, context, project_id): - """ Fetches all networks associated with this project, or - that are "global" (i.e., have no project set). - Returns list sorted by 'priority'. + """Fetches all networks associated with this project, or + that are "global" (i.e., have no project set). + Returns list sorted by 'priority'. """ admin_context = context.elevated() networks = db.project_get_networks(admin_context, project_id, False) @@ -113,7 +113,7 @@ class QuantumNovaIPAMLib(object): return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): - """ Allocates a single fixed IPv4 address for a virtual interface.""" + """Allocates a single fixed IPv4 address for a virtual interface.""" admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, quantum_net_id) if network['cidr']: @@ -125,8 +125,8 @@ class QuantumNovaIPAMLib(object): db.fixed_ip_update(admin_context, address, values) def get_subnets_by_net_id(self, context, tenant_id, net_id): - """ Returns information about the IPv4 and IPv6 subnets - associated with a Quantum Network UUID. + """Returns information about the IPv4 and IPv6 subnets + associated with a Quantum Network UUID. """ n = db.network_get_by_uuid(context.elevated(), net_id) subnet_data_v4 = { @@ -148,8 +148,8 @@ class QuantumNovaIPAMLib(object): return (subnet_data_v4, subnet_data_v6) def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): - """ Returns a list of IPv4 address strings associated with - the specified virtual interface, based on the fixed_ips table. + """Returns a list of IPv4 address strings associated with + the specified virtual interface, based on the fixed_ips table. """ vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) fixed_ips = db.fixed_ip_get_by_virtual_interface(context, @@ -157,8 +157,8 @@ class QuantumNovaIPAMLib(object): return [fixed_ip['address'] for fixed_ip in fixed_ips] def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): - """ Returns a list containing a single IPv6 address strings - associated with the specified virtual interface. + """Returns a list containing a single IPv6 address strings + associated with the specified virtual interface. """ admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, net_id) @@ -171,16 +171,16 @@ class QuantumNovaIPAMLib(object): return [] def verify_subnet_exists(self, context, tenant_id, quantum_net_id): - """ Confirms that a subnet exists that is associated with the - specified Quantum Network UUID. Raises an exception if no - such subnet exists. + """Confirms that a subnet exists that is associated with the + specified Quantum Network UUID. Raises an exception if no + such subnet exists. """ admin_context = context.elevated() db.network_get_by_uuid(admin_context, quantum_net_id) def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): - """ Deallocate all fixed IPs associated with the specified - virtual interface. + """Deallocate all fixed IPs associated with the specified + virtual interface. """ try: admin_context = context.elevated() diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index 93892a843..21917653c 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -21,7 +21,7 @@ from nova.network.quantum import client as quantum_client from nova import utils -LOG = logging.getLogger("nova.network.quantum") +LOG = logging.getLogger("nova.network.quantum.quantum_connection") FLAGS = flags.FLAGS flags.DEFINE_string('quantum_connection_host', @@ -38,35 +38,35 @@ flags.DEFINE_string('quantum_default_tenant_id', class QuantumClientConnection(object): - """ Abstracts connection to Quantum service into higher level - operations performed by the QuantumManager. + """Abstracts connection to Quantum service into higher level + operations performed by the QuantumManager. - Separating this out as a class also let's us create a 'fake' - version of this class for unit tests. + Separating this out as a class also let's us create a 'fake' + version of this class for unit tests. """ def __init__(self): - """ Initialize Quantum client class based on flags. """ + """Initialize Quantum client class based on flags.""" self.client = quantum_client.Client(FLAGS.quantum_connection_host, FLAGS.quantum_connection_port, format="json", logger=LOG) def create_network(self, tenant_id, network_name): - """ Create network using specified name, return Quantum - network UUID. + """Create network using specified name, return Quantum + network UUID. """ data = {'network': {'name': network_name}} resdict = self.client.create_network(data, tenant=tenant_id) return resdict["network"]["id"] def delete_network(self, tenant_id, net_id): - """ Deletes Quantum network with specified UUID. """ + """Deletes Quantum network with specified UUID.""" self.client.delete_network(net_id, tenant=tenant_id) def network_exists(self, tenant_id, net_id): - """ Determine if a Quantum network exists for the - specified tenant. + """Determine if a Quantum network exists for the + specified tenant. """ try: self.client.show_network_details(net_id, tenant=tenant_id) @@ -76,9 +76,9 @@ class QuantumClientConnection(object): return False def create_and_attach_port(self, tenant_id, net_id, interface_id): - """ Creates a Quantum port on the specified network, sets - status to ACTIVE to enable traffic, and attaches the - vNIC with the specified interface-id. + """Creates a Quantum port on the specified network, sets + status to ACTIVE to enable traffic, and attaches the + vNIC with the specified interface-id. """ LOG.debug(_("Connecting interface %(interface_id)s to " "net %(net_id)s for %(tenant_id)s" % locals())) @@ -91,7 +91,7 @@ class QuantumClientConnection(object): tenant=tenant_id) def detach_and_delete_port(self, tenant_id, net_id, port_id): - """ Detach and delete the specified Quantum port. """ + """Detach and delete the specified Quantum port.""" LOG.debug(_("Deleting port %(port_id)s on net %(net_id)s" " for %(tenant_id)s" % locals())) @@ -99,8 +99,8 @@ class QuantumClientConnection(object): self.client.delete_port(net_id, port_id, tenant=tenant_id) def get_port_by_attachment(self, tenant_id, attachment_id): - """ Given a tenant, search for the Quantum network and port - UUID that has the specified interface-id attachment. + """Given a tenant, search for the Quantum network and port + UUID that has the specified interface-id attachment. """ # FIXME(danwent): this will be inefficient until the Quantum # API implements querying a port by the interface-id -- cgit From e5e3b306985a3b1fdd8a971f48b76eaf8f923f21 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 13:24:38 -0700 Subject: fix pep8 violation --- nova/network/quantum/melange_connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index 0c744f080..71ac9b5f1 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -35,6 +35,7 @@ flags.DEFINE_string('melange_port', json_content_type = {'Content-type': "application/json"} + # FIXME(danwent): talk to the Melange folks about creating a # client lib that we can import as a library, instead of # have to have all of the client code in here. -- cgit From fc0ee0d01320d81b5bb6cd1bc6cb23c90c8246a7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 2 Sep 2011 13:24:40 -0700 Subject: remove extra description stuff --- .../api/openstack/contrib/test_createserverext.py | 1 - nova/tests/api/openstack/test_servers.py | 19 +------------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index a6da9abfd..0881efcfe 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -98,7 +98,6 @@ class CreateserverextTest(test.TestCase): 'uuid': FAKE_UUID, 'user_id': 'fake', 'project_id': 'fake', - 'display_description': 'fakedescription', 'created_at': "", 'updated_at': ""}] diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 45ad6e5a8..a716af0e5 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -156,7 +156,6 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, vm_state=None, task_state=None, reservation_id="", uuid=FAKE_UUID, image_ref="10", flavor_id="1", interfaces=None, name=None, key_name='', - description='fakedescription', access_ipv4=None, access_ipv6=None): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -211,7 +210,7 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, "terminated_at": utils.utcnow(), "availability_zone": "", "display_name": server_name, - "display_description": description, + "display_description": "", "locked": False, "metadata": metadata, "access_ip_v4": access_ipv4, @@ -354,7 +353,6 @@ class ServersTest(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "server1", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "", "accessIPv6": "", @@ -455,7 +453,6 @@ class ServersTest(test.TestCase): xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" name="server1" - description="fakedescription" updated="%(expected_updated)s" created="%(expected_created)s" hostId="" @@ -528,7 +525,6 @@ class ServersTest(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "server1", - "description": "fakedescription", "status": "ACTIVE", "accessIPv4": "", "accessIPv6": "", @@ -626,7 +622,6 @@ class ServersTest(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "server1", - "description": "fakedescription", "status": "ACTIVE", "accessIPv4": "", "accessIPv6": "", @@ -1488,7 +1483,6 @@ class ServersTest(test.TestCase): 'access_ip_v4': '1.2.3.4', 'access_ip_v6': 'fead::1234', 'image_ref': image_ref, - 'display_description': 'fakedescription', 'user_id': 'fake', 'project_id': 'fake', "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), @@ -3368,7 +3362,6 @@ class TestServerInstanceCreation(test.TestCase): return [{'id': '1234', 'display_name': 'fakeinstance', 'user_id': 'fake', 'project_id': 'fake', - 'display_description': 'fakedescription', 'uuid': FAKE_UUID}] def set_admin_password(self, *args, **kwargs): @@ -3686,7 +3679,6 @@ class ServersViewBuilderV11Test(test.TestCase): "terminated_at": utils.utcnow(), "availability_zone": "", "display_name": "test_server", - "display_description": "fakedescription", "locked": False, "metadata": [], "accessIPv4": "1.2.3.4", @@ -3775,7 +3767,6 @@ class ServersViewBuilderV11Test(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "", "accessIPv6": "", @@ -3833,7 +3824,6 @@ class ServersViewBuilderV11Test(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "test_server", - "description": "fakedescription", "status": "ACTIVE", "accessIPv4": "", "accessIPv6": "", @@ -4011,7 +4001,6 @@ class ServersViewBuilderV11Test(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "", "accessIPv6": "", @@ -4083,7 +4072,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', "key_name": '', @@ -4222,7 +4210,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "1.2.3.4", "accessIPv6": "fead::1234", @@ -4425,7 +4412,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "1.2.3.4", "accessIPv6": "fead::1234", @@ -4483,7 +4469,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 100, "name": "test_server_2", - "description": "fakedescription", "status": "ACTIVE", "accessIPv4": "1.2.3.4", "accessIPv6": "fead::1234", @@ -4606,7 +4591,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', "accessIPv4": "1.2.3.4", @@ -4744,7 +4728,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "1.2.3.4", "accessIPv6": "fead::1234", -- cgit From 6eb28b5748a829d058fd35888f03f2ee1f26f5b5 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 2 Sep 2011 13:31:19 -0700 Subject: default description to name --- nova/api/openstack/create_instance_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 289f87921..9b2928bc8 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -164,7 +164,7 @@ class CreateInstanceHelper(object): # NOTE(vish): This is solely for compatibility with anything # that is using the display description field. metadata = server_dict.get('metadata') or {} - display_description = metadata.get('description') or '' + display_description = metadata.get('description') or name return (extra_values, create_method(context, inst_type, -- cgit From 53f1aafc546b165cfdd74aa6620fe4c288a9359a Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 2 Sep 2011 17:28:58 -0500 Subject: alex meade issues --- nova/tests/fake_network.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 2e8cf60ec..36aebe6b0 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -34,31 +34,31 @@ class FakeModel(dict): return self[name] -def fake_network(n, ipv6=None): +def fake_network(network_id, ipv6=None): if ipv6 == None: ipv6 = FLAGS.use_ipv6 - rval = {'id': n, - 'label': 'test%d' % n, + rval = {'id': network_id, + 'label': 'test%d' % network_id, 'injected': False, 'multi_host': False, - 'cidr': '192.168.%d.0/24' % n, + 'cidr': '192.168.%d.0/24' % network_id, 'cidr_v6': None, 'netmask': '255.255.255.0', 'netmask_v6': None, - 'bridge': 'fake_br%d' % n, - 'bridge_interface': 'fake_eth%d' % n, - 'gateway': '192.168.%d.1' % n, + 'bridge': 'fake_br%d' % network_id, + 'bridge_interface': 'fake_eth%d' % network_id, + 'gateway': '192.168.%d.1' % network_id, 'gateway_v6': None, - 'broadcast': '192.168.%d.255' % n, - 'dns1': '192.168.%d.3' % n, - 'dns2': '192.168.%d.4' % n, + 'broadcast': '192.168.%d.255' % network_id, + 'dns1': '192.168.%d.3' % network_id, + 'dns2': '192.168.%d.4' % network_id, 'vlan': None, 'host': None, 'project_id': 'fake_project', - 'vpn_public_address': '192.168.%d.2' % n} + 'vpn_public_address': '192.168.%d.2' % network_id} if ipv6: - rval['cidr_v6'] = '2001:db8:0:%x::/64' % n - rval['gateway_v6'] = '2001:db8:0:%x::1' % n + rval['cidr_v6'] = '2001:db8:0:%x::/64' % network_id + rval['gateway_v6'] = '2001:db8:0:%x::1' % network_id rval['netmask_v6'] = '64' return rval @@ -109,11 +109,11 @@ def vifs(n): 'instance_id': 0} -def ipv4_like(ip, s): +def ipv4_like(ip, match_string): ip = ip.split('.') - s = s.split('.') + match_octets = match_string.split('.') - for i, octet in enumerate(s): + for i, octet in enumerate(match_octets): if octet == '*': continue if octet != ip[i]: @@ -121,7 +121,7 @@ def ipv4_like(ip, s): return True -def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): +def fake_get_instance_nw_info(stubs, num_networks=1, ips_per_vif=2): # stubs is the self.stubs from the test # ips_per_vif is the number of ips each vif will have # num_floating_ips is number of float ips for each fixed ip @@ -129,10 +129,10 @@ def fake_get_instance_nw_info(stubs, n=1, ips_per_vif=2): network.db = db def fixed_ips_fake(*args, **kwargs): - return list(fixed_ips(n, ips_per_vif)) + return list(fixed_ips(num_networks, ips_per_vif)) def virtual_interfaces_fake(*args, **kwargs): - return [vif for vif in vifs(n)] + return [vif for vif in vifs(num_networks)] def instance_type_fake(*args, **kwargs): return flavor -- cgit From cfdc4642bfa3d96e2335079d187945b9ca4c0141 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 2 Sep 2011 18:00:34 -0500 Subject: rick nits --- nova/tests/fake_network.py | 68 ++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 36aebe6b0..10565f83c 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -14,6 +14,7 @@ # 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 itertools from nova import db from nova import flags @@ -35,48 +36,49 @@ class FakeModel(dict): def fake_network(network_id, ipv6=None): - if ipv6 == None: + if ipv6 is None: ipv6 = FLAGS.use_ipv6 - rval = {'id': network_id, - 'label': 'test%d' % network_id, - 'injected': False, - 'multi_host': False, - 'cidr': '192.168.%d.0/24' % network_id, - 'cidr_v6': None, - 'netmask': '255.255.255.0', - 'netmask_v6': None, - 'bridge': 'fake_br%d' % network_id, - 'bridge_interface': 'fake_eth%d' % network_id, - 'gateway': '192.168.%d.1' % network_id, - 'gateway_v6': None, - 'broadcast': '192.168.%d.255' % network_id, - 'dns1': '192.168.%d.3' % network_id, - 'dns2': '192.168.%d.4' % network_id, - 'vlan': None, - 'host': None, - 'project_id': 'fake_project', - 'vpn_public_address': '192.168.%d.2' % network_id} + fake_network = {'id': network_id, + 'label': 'test%d' % network_id, + 'injected': False, + 'multi_host': False, + 'cidr': '192.168.%d.0/24' % network_id, + 'cidr_v6': None, + 'netmask': '255.255.255.0', + 'netmask_v6': None, + 'bridge': 'fake_br%d' % network_id, + 'bridge_interface': 'fake_eth%d' % network_id, + 'gateway': '192.168.%d.1' % network_id, + 'gateway_v6': None, + 'broadcast': '192.168.%d.255' % network_id, + 'dns1': '192.168.%d.3' % network_id, + 'dns2': '192.168.%d.4' % network_id, + 'vlan': None, + 'host': None, + 'project_id': 'fake_project', + 'vpn_public_address': '192.168.%d.2' % network_id} if ipv6: - rval['cidr_v6'] = '2001:db8:0:%x::/64' % network_id - rval['gateway_v6'] = '2001:db8:0:%x::1' % network_id - rval['netmask_v6'] = '64' + fake_network['cidr_v6'] = '2001:db8:0:%x::/64' % network_id + fake_network['gateway_v6'] = '2001:db8:0:%x::1' % network_id + fake_network['netmask_v6'] = '64' - return rval + return fake_network def fixed_ips(num_networks, num_ips, num_floating_ips=0): - for network in xrange(num_networks): - for ip in xrange(num_ips): - id = network * num_ips + ip - f_ips = [floating_ips(id).next() for i in xrange(num_floating_ips)] - yield {'id': id, - 'network_id': network, - 'address': '192.168.%d.1%02d' % (network, ip), + for network_index in xrange(num_networks): + for ip_index in xrange(num_ips): + fixed_ip_id = network_index * num_ips + ip_index + f_ips = [FakeModel(**floating_ips(fixed_ip_id).next()) + for i in xrange(num_floating_ips)] + yield {'id': fixed_ip_id, + 'network_id': network_index, + 'address': '192.168.%d.1%02d' % (network_index, ip_index), 'instance_id': 0, 'allocated': False, # and since network_id and vif_id happen to be equivalent - 'virtual_interface_id': network, - 'floating_ips': [FakeModel(**ip) for ip in f_ips]} + 'virtual_interface_id': network_index, + 'floating_ips': f_ips} flavor = {'id': 0, -- cgit From f970ce0fbb3d4de560f73a01b508d8f0f7ac9117 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 2 Sep 2011 18:02:41 -0500 Subject: removed unneeded import --- nova/tests/fake_network.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 10565f83c..73a117c33 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -14,7 +14,6 @@ # 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 itertools from nova import db from nova import flags -- cgit From 494eb94192a971f64fa6aa78092074f8ed437a7f Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Fri, 2 Sep 2011 17:09:09 -0700 Subject: Added unit tests to check instance record is not inserted in db when security groups passed to the instances are not existing --- nova/tests/test_compute.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 766a7da9b..65fdffbd6 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -161,6 +161,19 @@ class ComputeTestCase(test.TestCase): db.security_group_destroy(self.context, group['id']) db.instance_destroy(self.context, ref[0]['id']) + def test_create_instance_with_invalid_security_group_raises(self): + instance_type = instance_types.get_default_instance_type() + + pre_build_len = len(db.instance_get_all(context.get_admin_context())) + self.assertRaises(exception.SecurityGroupNotFoundForProject, + self.compute_api.create, + self.context, + instance_type=instance_type, + image_href=None, + security_group=['this_is_a_fake_sec_group']) + self.assertEqual(pre_build_len, + len(db.instance_get_all(context.get_admin_context()))) + def test_create_instance_associates_config_drive(self): """Make sure create associates a config drive.""" -- cgit From 2abdd4c2fab39b6373a1562fb260168e515e43e9 Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Sat, 3 Sep 2011 05:50:53 +0000 Subject: Launchpad automatic translations update. --- po/pt_BR.po | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/po/pt_BR.po b/po/pt_BR.po index c4eb487d6..48a718808 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-09-01 20:56+0000\n" +"PO-Revision-Date: 2011-09-02 12:17+0000\n" "Last-Translator: Robson Negreiros Bezerra \n" "Language-Team: Brazilian Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-09-02 04:53+0000\n" +"X-Launchpad-Export-Date: 2011-09-03 05:50+0000\n" "X-Generator: Launchpad (build 13830)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 @@ -882,58 +882,59 @@ msgstr "" #: ../nova/virt/xenapi/vm_utils.py:590 #, python-format msgid "No VDIs found for VM %s" -msgstr "" +msgstr "Nenhum VDIs encontrado para MV %s" #: ../nova/virt/xenapi/vm_utils.py:594 #, python-format msgid "Unexpected number of VDIs (%(num_vdis)s) found for VM %(vm_ref)s" msgstr "" +"Número de VDIs inesperado (%(num_vdis)s) encontrado para MV %(vm_ref)s" #: ../nova/virt/xenapi/vm_utils.py:653 #: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:188 #, python-format msgid "Creating VBD for VDI %s ... " -msgstr "" +msgstr "Criando VBD para VDI %s ... " #: ../nova/virt/xenapi/vm_utils.py:655 #: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:190 #, python-format msgid "Creating VBD for VDI %s done." -msgstr "" +msgstr "O VBD para VDI %s foi criado." #: ../nova/virt/xenapi/vm_utils.py:657 #: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:192 #, python-format msgid "Plugging VBD %s ... " -msgstr "" +msgstr "Conectando VBD %s ... " #: ../nova/virt/xenapi/vm_utils.py:659 #: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:194 #, python-format msgid "Plugging VBD %s done." -msgstr "" +msgstr "O VDB %s foi conectado." #: ../nova/virt/xenapi/vm_utils.py:661 #, python-format msgid "VBD %(vbd)s plugged as %(orig_dev)s" -msgstr "" +msgstr "VBD %(vbd)s conectado como %(orig_dev)s" #: ../nova/virt/xenapi/vm_utils.py:664 #, python-format msgid "VBD %(vbd)s plugged into wrong dev, remapping to %(dev)s" -msgstr "" +msgstr "VBD %(vbd)s conectado no device errado, remapeando para %(dev)s" #: ../nova/virt/xenapi/vm_utils.py:668 #: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:197 #, python-format msgid "Destroying VBD for VDI %s ... " -msgstr "" +msgstr "Destruindo VBD para o VDI %s ... " #: ../nova/virt/xenapi/vm_utils.py:671 #: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:200 #, python-format msgid "Destroying VBD for VDI %s done." -msgstr "" +msgstr "O VBD para o VDI %s foi destruído." #: ../nova/virt/xenapi/vm_utils.py:683 #: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:211 @@ -954,7 +955,7 @@ msgstr "" #: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:223 #, python-format msgid "Ignoring XenAPI.Failure in VBD.unplug: %s" -msgstr "" +msgstr "Ignorando XenAPI.Failure em VBD.unplug: %s" #: ../nova/virt/xenapi/vm_utils.py:704 #: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:66 -- cgit From 864f3b24c6cd0753474ac152bce73c2df64bbdd1 Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Sat, 3 Sep 2011 09:36:06 +0200 Subject: Change non E ascii characte. --- Authors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authors b/Authors index 0beab16a4..f647a7c00 100644 --- a/Authors +++ b/Authors @@ -30,7 +30,7 @@ Devendra Modium Devin Carlen Donal Lafferty Ed Leafe -Édouard Thuleau +Edouard Thuleau Eldar Nugaev Eric Day Eric Windisch -- cgit From 80059b3e87f6ce7ab2ba18a135e5c469d2be8f88 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Sun, 4 Sep 2011 01:19:21 -0500 Subject: correct floating ip id to increment in fake_network --- nova/tests/fake_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 73a117c33..99b027cf3 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -93,7 +93,7 @@ flavor = {'id': 0, def floating_ips(fixed_ip_id): for i in xrange(154): - yield {'id': 0, + yield {'id': i, 'address': '10.10.10.%d' % (i + 100), 'fixed_ip_id': fixed_ip_id, 'project_id': None, -- cgit From 6b61a6be4a14444723e3728fb0fcdd77bac8fe74 Mon Sep 17 00:00:00 2001 From: Keisuke Tagami Date: Mon, 5 Sep 2011 14:45:41 +0900 Subject: Fix bug #835919 that output a option file for dnsmasq not to offer a default gateway on second vif. --- nova/db/sqlalchemy/api.py | 15 +++++++++++++ nova/network/linux_net.py | 55 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 2 deletions(-) mode change 100644 => 100755 nova/db/sqlalchemy/api.py mode change 100644 => 100755 nova/network/linux_net.py diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py old mode 100644 new mode 100755 index b99667afc..aee1c2a55 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1415,6 +1415,21 @@ def instance_get_all_by_reservation(context, reservation_id): filter_by(project_id=context.project_id).\ filter_by(deleted=False).\ all() + +@require_admin_context +def instance_get_all_by_network(context, network_id): + session = get_session() + return session.query(models.Instance).\ + options(joinedload_all('fixed_ips.floating_ips')).\ + options(joinedload('virtual_interfaces')).\ + options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ips.network')).\ + options(joinedload('metadata')).\ + options(joinedload('instance_type')).\ + filter_by(deleted=can_read_deleted(context)).\ + filter_by(network_id=network_id).\ + all() + @require_context diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py old mode 100644 new mode 100755 index 57c1d0c28..8d5a0bbbd --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -511,6 +511,35 @@ def get_dhcp_hosts(context, network_ref): return '\n'.join(hosts) +def get_dhcp_opts(context, network_ref): + """Get network's hosts config in dhcp-opts format.""" + # create a decision dictionary for default gateway for eache instance + default_gateway_network_node = dict() + network_id = network_ref['id'] + instance_refs = db.instance_get_all_by_network(context, network_id) + ips_ref = db.network_get_associated_fixed_ips(context, network_id) + + for instance_ref in instance_refs: + instance_id = instance_ref['id'] + # nic number is decided by ‡”Ô from this function in xxx function + vifs = db.virtual_interface_get_by_instance(context, instance_id) + if not vifs: + continue + # offer a default gateway to the first virtual interface of instance + first_vif = vifs[0] + default_gateway_network_node[instance_id] = first_vif['network_id'] + + for fixed_ip_ref in ips_ref: + instance_id = fixed_ip_ref['instance_id'] + target_network_id = default_gateway_network_node[instance_id] + if target_network_id == fixed_ip_ref['network_id']: + hosts.append(_host_dhcp_opts(fixed_ip_ref, gw=True)) + else: + hosts.append(_host_dhcp_opts(fixed_ip_ref, gw=None)) + + return '\n'.join(hosts) + + # NOTE(ja): Sending a HUP only reloads the hostfile, so any # configuration options (like dchp-range, vlan, ...) # aren't reloaded. @@ -526,8 +555,13 @@ def update_dhcp(context, dev, network_ref): with open(conffile, 'w') as f: f.write(get_dhcp_hosts(context, network_ref)) + optsfile = _dhcp_file(dev, 'opts') + with open(optsfile, 'w') as f: + f.write(get_dhcp_opts(context, network_ref)) + # Make sure dnsmasq can actually read it (it setuid()s to "nobody") os.chmod(conffile, 0644) + os.chmod(optsfile, 0644) pid = _dnsmasq_pid_for(dev) @@ -559,6 +593,7 @@ def update_dhcp(context, dev, network_ref): '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(network_ref['cidr'])), '--dhcp-hostsfile=%s' % _dhcp_file(dev, 'conf'), '--dhcp-script=%s' % FLAGS.dhcpbridge, + '--dhcp-optsfile=%s' % _dhcp_file(dev, 'opts'), '--leasefile-ro'] if FLAGS.dns_server: cmd += ['-h', '-R', '--server=%s' % FLAGS.dns_server] @@ -625,13 +660,29 @@ def _host_lease(fixed_ip_ref): instance_ref['hostname'] or '*') +def _host_dhcp_network(fixed_ip_ref): + instance_ref = fixed_ip_ref['instance'] + return 'NW-i%08d-%s' % (instance_ref['id'], + fixed_ip_ref['network_id']) + + def _host_dhcp(fixed_ip_ref): """Return a host string for an address in dhcp-host format.""" instance_ref = fixed_ip_ref['instance'] - return '%s,%s.%s,%s' % (fixed_ip_ref['virtual_interface']['address'], + return '%s,%s.%s,%s,%s' % (fixed_ip_ref['virtual_interface']['address'], instance_ref['hostname'], FLAGS.dhcp_domain, - fixed_ip_ref['address']) + fixed_ip_ref['address'], + "net:" + _host_dhcp_network(fixed_ip_ref)) + + +def _host_dhcp_opts(fixed_ip_ref, gw): + """Return a host string for an address in dhcp-host format.""" + if not gw: + return '%s,%s' % (_host_dhcp_network(fixed_ip_ref), + 3) + else: + return '' def _execute(*cmd, **kwargs): -- cgit From d6f1a31d56de84398246498d0f2676d9741cdccf Mon Sep 17 00:00:00 2001 From: Keisuke Tagami Date: Mon, 5 Sep 2011 20:23:28 +0900 Subject: implement unit test for linux_net --- nova/db/api.py | 5 + nova/network/linux_net.py | 17 ++-- nova/tests/test_linux_net.py | 232 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+), 7 deletions(-) mode change 100644 => 100755 nova/db/api.py create mode 100755 nova/tests/test_linux_net.py diff --git a/nova/db/api.py b/nova/db/api.py old mode 100644 new mode 100755 index 148887635..85e9c7669 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -532,6 +532,11 @@ def instance_get_all_by_reservation(context, reservation_id): return IMPL.instance_get_all_by_reservation(context, reservation_id) +def instance_get_all_by_network(context, network_id): + """Get all instances belonging to a network.""" + return IMPL.instance_get_all_by_network(context, network_id) + + def instance_get_by_fixed_ip(context, address): """Get an instance for a fixed ip by address.""" return IMPL.instance_get_by_fixed_ip(context, address) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 8d5a0bbbd..50d9ccf19 100755 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -514,6 +514,7 @@ def get_dhcp_hosts(context, network_ref): def get_dhcp_opts(context, network_ref): """Get network's hosts config in dhcp-opts format.""" # create a decision dictionary for default gateway for eache instance + hosts = [] default_gateway_network_node = dict() network_id = network_ref['id'] instance_refs = db.instance_get_all_by_network(context, network_id) @@ -521,9 +522,10 @@ def get_dhcp_opts(context, network_ref): for instance_ref in instance_refs: instance_id = instance_ref['id'] - # nic number is decided by ‡”Ô from this function in xxx function + # nic number is decided by xxx from this function in xxx function vifs = db.virtual_interface_get_by_instance(context, instance_id) - if not vifs: + if vifs == None: + default_gateway_network_node[instance_id] = None continue # offer a default gateway to the first virtual interface of instance first_vif = vifs[0] @@ -531,11 +533,12 @@ def get_dhcp_opts(context, network_ref): for fixed_ip_ref in ips_ref: instance_id = fixed_ip_ref['instance_id'] - target_network_id = default_gateway_network_node[instance_id] - if target_network_id == fixed_ip_ref['network_id']: - hosts.append(_host_dhcp_opts(fixed_ip_ref, gw=True)) - else: - hosts.append(_host_dhcp_opts(fixed_ip_ref, gw=None)) + if default_gateway_network_node.has_key(instance_id): + target_network_id = default_gateway_network_node[instance_id] + if target_network_id == fixed_ip_ref['network_id']: + hosts.append(_host_dhcp_opts(fixed_ip_ref, gw=True)) + else: + hosts.append(_host_dhcp_opts(fixed_ip_ref, gw=None)) return '\n'.join(hosts) diff --git a/nova/tests/test_linux_net.py b/nova/tests/test_linux_net.py new file mode 100755 index 000000000..3c3cdd0d9 --- /dev/null +++ b/nova/tests/test_linux_net.py @@ -0,0 +1,232 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 NTT +# 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 context +from nova import db +from nova import exception +from nova import log as logging +from nova import test +from nova import utils +from nova import flags +from nova.network import manager as network_manager + +import mox + +FLAGS = flags.FLAGS + +LOG = logging.getLogger('nova.tests.network') + + +HOST = "testhost" + +instances = [{'id': 0, + 'host': 'fake_instance00', + 'hostname': 'fake_instance00'}, + {'id': 1, + 'host': 'fake_instance01', + 'hostname': 'fake_instance01'}] + + +addresses = [{"address" : "10.0.0.1" }, + {"address" : "10.0.0.2" }, + {"address" : "10.0.0.3" }, + {"address" : "10.0.0.4" }] + + +networks = [{'id': 0, + 'uuid': "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + 'label': 'test0', + 'injected': False, + 'multi_host': False, + 'cidr': '192.168.0.0/24', + 'cidr_v6': '2001:db8::/64', + 'gateway_v6': '2001:db8::1', + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': 'fa0', + 'bridge_interface': 'fake_fa0', + 'gateway': '192.168.0.1', + 'broadcast': '192.168.0.255', + 'dns1': '192.168.0.1', + 'dns2': '192.168.0.2', + 'dhcp_server' : '0.0.0.0', + 'dhcp_start' : '192.168.100.1', + 'vlan': None, + 'host': None, + 'project_id': 'fake_project', + 'vpn_public_address': '192.168.0.2'}, + {'id': 1, + 'uuid': "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + 'label': 'test1', + 'injected': False, + 'multi_host': False, + 'cidr': '192.168.1.0/24', + 'cidr_v6': '2001:db9::/64', + 'gateway_v6': '2001:db9::1', + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': 'fa1', + 'bridge_interface': 'fake_fa1', + 'gateway': '192.168.1.1', + 'broadcast': '192.168.1.255', + 'dns1': '192.168.0.1', + 'dns2': '192.168.0.2', + 'dhcp_server' : '0.0.0.0', + 'dhcp_start' : '192.168.100.1', + 'vlan': None, + 'host': None, + 'project_id': 'fake_project', + 'vpn_public_address': '192.168.1.2'}] + + +fixed_ips = [{'id': 0, + 'network_id': 0, + 'address': '192.168.0.100', + 'instance_id': 0, + 'allocated': True, + 'virtual_interface_id': 0, + 'virtual_interface' : addresses[0], + 'instance': instances[0], + 'floating_ips': []}, + {'id': 1, + 'network_id': 1, + 'address': '192.168.1.100', + 'instance_id': 0, + 'allocated': True, + 'virtual_interface_id': 1, + 'virtual_interface' : addresses[1], + 'instance': instances[0], + 'floating_ips': []}, + {'id': 2, + 'network_id': 0, + 'address': '192.168.0.101', + 'instance_id': 1, + 'allocated': True, + 'virtual_interface_id': 2, + 'virtual_interface' : addresses[2], + 'instance': instances[1], + 'floating_ips': []}, + {'id': 3, + 'network_id': 1, + 'address': '192.168.1.101', + 'instance_id': 1, + 'allocated': True, + 'virtual_interface_id': 3, + 'virtual_interface' : addresses[3], + 'instance': instances[1], + 'floating_ips': []}] + + + + +vifs = [{'id': 0, + 'address': 'DE:AD:BE:EF:00:00', + 'uuid': '00000000-0000-0000-0000-0000000000000000', + 'network_id': 0, + 'network': networks[0], + 'instance_id': 0}, + {'id': 1, + 'address': 'DE:AD:BE:EF:00:01', + 'uuid': '00000000-0000-0000-0000-0000000000000001', + 'network_id': 1, + 'network': networks[1], + 'instance_id': 0}, + {'id': 2, + 'address': 'DE:AD:BE:EF:00:02', + 'uuid': '00000000-0000-0000-0000-0000000000000002', + 'network_id': 1, + 'network': networks[1], + 'instance_id': 1}, + {'id': 3, + 'address': 'DE:AD:BE:EF:00:03', + 'uuid': '00000000-0000-0000-0000-0000000000000003', + 'network_id': 0, + 'network': networks[0], + 'instance_id': 1}] + + +class LinuxNetworkTestCase(test.TestCase): + + def setUp(self): + super(LinuxNetworkTestCase, self).setUp() + network_driver = FLAGS.network_driver + self.driver = utils.import_object(network_driver) + self.driver.db = db + + def test_update_dhcp(self): + self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') + self.mox.StubOutWithMock(db, 'instance_get_all_by_network') + self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') + + fixed_ips[1]['instance'] = instances[0] + db.network_get_associated_fixed_ips(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(fixed_ips) + + db.instance_get_all_by_network(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(instances) + db.network_get_associated_fixed_ips(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(fixed_ips) + db.virtual_interface_get_by_instance(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn([vifs[0],vifs[1]]) + db.virtual_interface_get_by_instance(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn([vifs[2],vifs[3]]) + + self.mox.ReplayAll() + self.driver.update_dhcp(None, "eth0", networks[0]) + + + def test_get_dhcp_hosts(self): + self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') + + fixed_ips[1]['instance'] = instances[0] + db.network_get_associated_fixed_ips(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(fixed_ips) + + self.mox.ReplayAll() + + hosts = self.driver.get_dhcp_hosts(None, networks[0]) + + self.assertEquals(hosts, + "10.0.0.1,fake_instance00.novalocal,192.168.0.100,net:NW-i00000000-0\n" \ + "10.0.0.2,fake_instance00.novalocal,192.168.1.100,net:NW-i00000000-1\n" \ + "10.0.0.3,fake_instance01.novalocal,192.168.0.101,net:NW-i00000001-0\n" \ + "10.0.0.4,fake_instance01.novalocal,192.168.1.101,net:NW-i00000001-1") + + + def test_get_dhcp_opts(self): + self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') + self.mox.StubOutWithMock(db, 'instance_get_all_by_network') + self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') + + fixed_ips[1]['instance'] = instances[0] + + db.instance_get_all_by_network(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(instances) + db.network_get_associated_fixed_ips(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(fixed_ips) + db.virtual_interface_get_by_instance(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn([vifs[0],vifs[1]]) + db.virtual_interface_get_by_instance(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn([vifs[2],vifs[3]]) + + self.mox.ReplayAll() + + opts = self.driver.get_dhcp_opts(None, networks[0]) + self.assertEquals(opts, '\nNW-i00000000-1,3\nNW-i00000001-0,3\n') + + + -- cgit From 418923c385a34265dd33a0c2ada3aa97a5749a06 Mon Sep 17 00:00:00 2001 From: Keisuke Tagami Date: Mon, 5 Sep 2011 21:13:00 +0900 Subject: format for pep8 --- nova/db/api.py | 4 +- nova/db/sqlalchemy/api.py | 5 +- nova/network/linux_net.py | 6 +- nova/tests/test_linux_net.py | 177 ++++++++++++++++++++++++++++++++----------- 4 files changed, 141 insertions(+), 51 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index 85e9c7669..faac13966 100755 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -535,8 +535,8 @@ def instance_get_all_by_reservation(context, reservation_id): def instance_get_all_by_network(context, network_id): """Get all instances belonging to a network.""" return IMPL.instance_get_all_by_network(context, network_id) - - + + def instance_get_by_fixed_ip(context, address): """Get an instance for a fixed ip by address.""" return IMPL.instance_get_by_fixed_ip(context, address) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index aee1c2a55..3194a5231 100755 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1415,8 +1415,8 @@ def instance_get_all_by_reservation(context, reservation_id): filter_by(project_id=context.project_id).\ filter_by(deleted=False).\ all() - -@require_admin_context + + def instance_get_all_by_network(context, network_id): session = get_session() return session.query(models.Instance).\ @@ -1431,7 +1431,6 @@ def instance_get_all_by_network(context, network_id): all() - @require_context def instance_get_by_fixed_ip(context, address): """Return instance ref by exact match of FixedIP""" diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 50d9ccf19..4ebc3df23 100755 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -438,7 +438,7 @@ def ensure_vpn_forward(public_ip, port, private_ip): def ensure_floating_forward(floating_ip, fixed_ip): """Ensure floating ip forwarding rule.""" - for chain, rule in floating_forward_rules(floating_ip, fixed_ip): + for chain, rule in floating_forward_rules(floaing_ip, fixed_ip): iptables_manager.ipv4['nat'].add_rule(chain, rule) iptables_manager.apply() @@ -518,7 +518,7 @@ def get_dhcp_opts(context, network_ref): default_gateway_network_node = dict() network_id = network_ref['id'] instance_refs = db.instance_get_all_by_network(context, network_id) - ips_ref = db.network_get_associated_fixed_ips(context, network_id) + ips_ref = db.network_get_associated_fixed_ips(context, network_id) for instance_ref in instance_refs: instance_id = instance_ref['id'] @@ -533,7 +533,7 @@ def get_dhcp_opts(context, network_ref): for fixed_ip_ref in ips_ref: instance_id = fixed_ip_ref['instance_id'] - if default_gateway_network_node.has_key(instance_id): + if instance_id in default_gateway_network_node: target_network_id = default_gateway_network_node[instance_id] if target_network_id == fixed_ip_ref['network_id']: hosts.append(_host_dhcp_opts(fixed_ip_ref, gw=True)) diff --git a/nova/tests/test_linux_net.py b/nova/tests/test_linux_net.py index 3c3cdd0d9..94c53817b 100755 --- a/nova/tests/test_linux_net.py +++ b/nova/tests/test_linux_net.py @@ -41,10 +41,10 @@ instances = [{'id': 0, 'hostname': 'fake_instance01'}] -addresses = [{"address" : "10.0.0.1" }, - {"address" : "10.0.0.2" }, - {"address" : "10.0.0.3" }, - {"address" : "10.0.0.4" }] +addresses = [{"address": "10.0.0.1"}, + {"address": "10.0.0.2"}, + {"address": "10.0.0.3"}, + {"address": "10.0.0.4"}] networks = [{'id': 0, @@ -63,8 +63,8 @@ networks = [{'id': 0, 'broadcast': '192.168.0.255', 'dns1': '192.168.0.1', 'dns2': '192.168.0.2', - 'dhcp_server' : '0.0.0.0', - 'dhcp_start' : '192.168.100.1', + 'dhcp_server': '0.0.0.0', + 'dhcp_start': '192.168.100.1', 'vlan': None, 'host': None, 'project_id': 'fake_project', @@ -85,8 +85,8 @@ networks = [{'id': 0, 'broadcast': '192.168.1.255', 'dns1': '192.168.0.1', 'dns2': '192.168.0.2', - 'dhcp_server' : '0.0.0.0', - 'dhcp_start' : '192.168.100.1', + 'dhcp_server': '0.0.0.0', + 'dhcp_start': '192.168.100.1', 'vlan': None, 'host': None, 'project_id': 'fake_project', @@ -99,7 +99,7 @@ fixed_ips = [{'id': 0, 'instance_id': 0, 'allocated': True, 'virtual_interface_id': 0, - 'virtual_interface' : addresses[0], + 'virtual_interface': addresses[0], 'instance': instances[0], 'floating_ips': []}, {'id': 1, @@ -108,7 +108,7 @@ fixed_ips = [{'id': 0, 'instance_id': 0, 'allocated': True, 'virtual_interface_id': 1, - 'virtual_interface' : addresses[1], + 'virtual_interface': addresses[1], 'instance': instances[0], 'floating_ips': []}, {'id': 2, @@ -117,7 +117,7 @@ fixed_ips = [{'id': 0, 'instance_id': 1, 'allocated': True, 'virtual_interface_id': 2, - 'virtual_interface' : addresses[2], + 'virtual_interface': addresses[2], 'instance': instances[1], 'floating_ips': []}, {'id': 3, @@ -126,13 +126,11 @@ fixed_ips = [{'id': 0, 'instance_id': 1, 'allocated': True, 'virtual_interface_id': 3, - 'virtual_interface' : addresses[3], + 'virtual_interface': addresses[3], 'instance': instances[1], 'floating_ips': []}] - - vifs = [{'id': 0, 'address': 'DE:AD:BE:EF:00:00', 'uuid': '00000000-0000-0000-0000-0000000000000000', @@ -167,66 +165,159 @@ class LinuxNetworkTestCase(test.TestCase): self.driver = utils.import_object(network_driver) self.driver.db = db - def test_update_dhcp(self): + def test_update_dhcp_for_nw00(self): self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') self.mox.StubOutWithMock(db, 'instance_get_all_by_network') self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') - fixed_ips[1]['instance'] = instances[0] db.network_get_associated_fixed_ips(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(fixed_ips) + mox.IgnoreArg())\ + .AndReturn([fixed_ips[0], + fixed_ips[3]]) db.instance_get_all_by_network(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(instances) + mox.IgnoreArg())\ + .AndReturn(instances) db.network_get_associated_fixed_ips(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(fixed_ips) + mox.IgnoreArg())\ + .AndReturn([fixed_ips[0], + fixed_ips[3]]) db.virtual_interface_get_by_instance(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn([vifs[0],vifs[1]]) + mox.IgnoreArg())\ + .AndReturn([vifs[0], vifs[1]]) db.virtual_interface_get_by_instance(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn([vifs[2],vifs[3]]) - + mox.IgnoreArg())\ + .AndReturn([vifs[2], vifs[3]]) self.mox.ReplayAll() + self.driver.update_dhcp(None, "eth0", networks[0]) + def test_update_dhcp_for_nw01(self): + self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') + self.mox.StubOutWithMock(db, 'instance_get_all_by_network') + self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') + + db.network_get_associated_fixed_ips(mox.IgnoreArg(), + mox.IgnoreArg())\ + .AndReturn([fixed_ips[1], + fixed_ips[2]]) - def test_get_dhcp_hosts(self): + db.instance_get_all_by_network(mox.IgnoreArg(), + mox.IgnoreArg())\ + .AndReturn(instances) + db.network_get_associated_fixed_ips(mox.IgnoreArg(), + mox.IgnoreArg())\ + .AndReturn([fixed_ips[1], + fixed_ips[2]]) + db.virtual_interface_get_by_instance(mox.IgnoreArg(), + mox.IgnoreArg())\ + .AndReturn([vifs[0], vifs[1]]) + db.virtual_interface_get_by_instance(mox.IgnoreArg(), + mox.IgnoreArg())\ + .AndReturn([vifs[2], vifs[3]]) + self.mox.ReplayAll() + + self.driver.update_dhcp(None, "eth0", networks[0]) + + def test_get_dhcp_hosts_for_nw00(self): self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') - fixed_ips[1]['instance'] = instances[0] db.network_get_associated_fixed_ips(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(fixed_ips) + mox.IgnoreArg())\ + .AndReturn([fixed_ips[0], + fixed_ips[3]]) + self.mox.ReplayAll() + + expected = \ + "10.0.0.1,fake_instance00.novalocal,"\ + "192.168.0.100,net:NW-i00000000-0\n"\ + "10.0.0.4,fake_instance01.novalocal,"\ + "192.168.1.101,net:NW-i00000001-1" + actual_hosts = self.driver.get_dhcp_hosts(None, networks[1]) + self.assertEquals(actual_hosts, expected) + + def test_get_dhcp_hosts_for_nw01(self): + self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') + + db.network_get_associated_fixed_ips(mox.IgnoreArg(), + mox.IgnoreArg())\ + .AndReturn([fixed_ips[1], + fixed_ips[2]]) self.mox.ReplayAll() - hosts = self.driver.get_dhcp_hosts(None, networks[0]) - - self.assertEquals(hosts, - "10.0.0.1,fake_instance00.novalocal,192.168.0.100,net:NW-i00000000-0\n" \ - "10.0.0.2,fake_instance00.novalocal,192.168.1.100,net:NW-i00000000-1\n" \ - "10.0.0.3,fake_instance01.novalocal,192.168.0.101,net:NW-i00000001-0\n" \ - "10.0.0.4,fake_instance01.novalocal,192.168.1.101,net:NW-i00000001-1") + expected = \ + "10.0.0.2,fake_instance00.novalocal,"\ + "192.168.1.100,net:NW-i00000000-1\n"\ + "10.0.0.3,fake_instance01.novalocal,"\ + "192.168.0.101,net:NW-i00000001-0" + actual_hosts = self.driver.get_dhcp_hosts(None, networks[0]) + + self.assertEquals(actual_hosts, expected) - - def test_get_dhcp_opts(self): + def test_get_dhcp_opts_for_nw00(self): self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') self.mox.StubOutWithMock(db, 'instance_get_all_by_network') self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') - fixed_ips[1]['instance'] = instances[0] - db.instance_get_all_by_network(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(instances) + mox.IgnoreArg())\ + .AndReturn(instances) db.network_get_associated_fixed_ips(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(fixed_ips) + mox.IgnoreArg())\ + .AndReturn([fixed_ips[0], + fixed_ips[3]]) db.virtual_interface_get_by_instance(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn([vifs[0],vifs[1]]) + mox.IgnoreArg())\ + .AndReturn([vifs[0], + vifs[1]]) db.virtual_interface_get_by_instance(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn([vifs[2],vifs[3]]) + mox.IgnoreArg())\ + .AndReturn([vifs[2], + vifs[3]]) + self.mox.ReplayAll() + + expected_opts = '\n'\ + '' + actual_opts = self.driver.get_dhcp_opts(None, networks[0]) + self.assertEquals(actual_opts, expected_opts) + + def test_get_dhcp_opts_for_nw01(self): + self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') + self.mox.StubOutWithMock(db, 'instance_get_all_by_network') + self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') + + db.instance_get_all_by_network(mox.IgnoreArg(), + mox.IgnoreArg())\ + .AndReturn(instances) + db.network_get_associated_fixed_ips(mox.IgnoreArg(), + mox.IgnoreArg())\ + .AndReturn([fixed_ips[1], + fixed_ips[2]]) + db.virtual_interface_get_by_instance(mox.IgnoreArg(), + mox.IgnoreArg())\ + .AndReturn([vifs[0], + vifs[1]]) + db.virtual_interface_get_by_instance(mox.IgnoreArg(), + mox.IgnoreArg())\ + .AndReturn([vifs[2], + vifs[3]]) self.mox.ReplayAll() - opts = self.driver.get_dhcp_opts(None, networks[0]) - self.assertEquals(opts, '\nNW-i00000000-1,3\nNW-i00000001-0,3\n') + expected_opts = 'NW-i00000000-1,3\n'\ + 'NW-i00000001-0,3' + actual_opts = self.driver.get_dhcp_opts(None, networks[1]) + + self.assertEquals(actual_opts, expected_opts) + + def test_dhcp_opts_default_gateway_network(self): + expected = "" + actual = self.driver._host_dhcp_opts(fixed_ips[0], True) + self.assertEquals(actual, expected) - + def test_dhcp_opts_not_default_gateway_network(self): + expected = "NW-i00000000-0,3" + actual = self.driver._host_dhcp_opts(fixed_ips[0], False) + self.assertEquals(actual, expected) -- cgit From 14314701bac27ce0e913f8e0587c354819a4bd9e Mon Sep 17 00:00:00 2001 From: Keisuke Tagami Date: Mon, 5 Sep 2011 21:32:52 +0900 Subject: format for pep8 --- nova/tests/test_linux_net.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/test_linux_net.py b/nova/tests/test_linux_net.py index 94c53817b..93d9b02c9 100755 --- a/nova/tests/test_linux_net.py +++ b/nova/tests/test_linux_net.py @@ -316,7 +316,6 @@ class LinuxNetworkTestCase(test.TestCase): actual = self.driver._host_dhcp_opts(fixed_ips[0], True) self.assertEquals(actual, expected) - def test_dhcp_opts_not_default_gateway_network(self): expected = "NW-i00000000-0,3" actual = self.driver._host_dhcp_opts(fixed_ips[0], False) -- cgit From b9ef3dcaa241088e205e170ca0a236afb0ea34dd Mon Sep 17 00:00:00 2001 From: Keisuke Tagami Date: Tue, 6 Sep 2011 10:10:25 +0900 Subject: added me to Authors --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index b9e7a7d23..f775c6f0c 100644 --- a/Authors +++ b/Authors @@ -60,6 +60,7 @@ Joshua McKenty Justin Santa Barbara Justin Shepherd Kei Masumoto +Keisuke Tagami masumoto Ken Pepple Kevin Bringard -- cgit From b922e08a6c15eeaab1f7ec342c00673b610d0e76 Mon Sep 17 00:00:00 2001 From: Keisuke Tagami Date: Tue, 6 Sep 2011 10:14:27 +0900 Subject: correct a method to collect instances from db add interface data to test --- nova/db/api.py | 5 --- nova/db/sqlalchemy/api.py | 14 --------- nova/network/linux_net.py | 8 +++-- nova/tests/test_linux_net.py | 74 +++++++++++++++++++++++++++++--------------- 4 files changed, 54 insertions(+), 47 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index faac13966..148887635 100755 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -532,11 +532,6 @@ def instance_get_all_by_reservation(context, reservation_id): return IMPL.instance_get_all_by_reservation(context, reservation_id) -def instance_get_all_by_network(context, network_id): - """Get all instances belonging to a network.""" - return IMPL.instance_get_all_by_network(context, network_id) - - def instance_get_by_fixed_ip(context, address): """Get an instance for a fixed ip by address.""" return IMPL.instance_get_by_fixed_ip(context, address) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 3194a5231..b99667afc 100755 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1417,20 +1417,6 @@ def instance_get_all_by_reservation(context, reservation_id): all() -def instance_get_all_by_network(context, network_id): - session = get_session() - return session.query(models.Instance).\ - options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ - options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ips.network')).\ - options(joinedload('metadata')).\ - options(joinedload('instance_type')).\ - filter_by(deleted=can_read_deleted(context)).\ - filter_by(network_id=network_id).\ - all() - - @require_context def instance_get_by_fixed_ip(context, address): """Return instance ref by exact match of FixedIP""" diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 4ebc3df23..c26e3574e 100755 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -517,11 +517,13 @@ def get_dhcp_opts(context, network_ref): hosts = [] default_gateway_network_node = dict() network_id = network_ref['id'] - instance_refs = db.instance_get_all_by_network(context, network_id) + instance_set = set() ips_ref = db.network_get_associated_fixed_ips(context, network_id) - for instance_ref in instance_refs: - instance_id = instance_ref['id'] + for fixed_ip_ref in ips_ref: + instance_set.add(fixed_ip_ref['instance_id']) + + for instance_id in instance_set: # nic number is decided by xxx from this function in xxx function vifs = db.virtual_interface_get_by_instance(context, instance_id) if vifs == None: diff --git a/nova/tests/test_linux_net.py b/nova/tests/test_linux_net.py index 93d9b02c9..3f44fbd05 100755 --- a/nova/tests/test_linux_net.py +++ b/nova/tests/test_linux_net.py @@ -18,10 +18,10 @@ 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 test from nova import utils -from nova import flags from nova.network import manager as network_manager import mox @@ -44,7 +44,9 @@ instances = [{'id': 0, addresses = [{"address": "10.0.0.1"}, {"address": "10.0.0.2"}, {"address": "10.0.0.3"}, - {"address": "10.0.0.4"}] + {"address": "10.0.0.4"}, + {"address": "10.0.0.5"}, + {"address": "10.0.0.6"}] networks = [{'id': 0, @@ -128,6 +130,24 @@ fixed_ips = [{'id': 0, 'virtual_interface_id': 3, 'virtual_interface': addresses[3], 'instance': instances[1], + 'floating_ips': []}, + {'id': 4, + 'network_id': 0, + 'address': '192.168.0.102', + 'instance_id': 0, + 'allocated': True, + 'virtual_interface_id': 4, + 'virtual_interface': addresses[4], + 'instance': instances[0], + 'floating_ips': []}, + {'id': 5, + 'network_id': 1, + 'address': '192.168.1.102', + 'instance_id': 1, + 'allocated': True, + 'virtual_interface_id': 5, + 'virtual_interface': addresses[5], + 'instance': instances[1], 'floating_ips': []}] @@ -154,6 +174,18 @@ vifs = [{'id': 0, 'uuid': '00000000-0000-0000-0000-0000000000000003', 'network_id': 0, 'network': networks[0], + 'instance_id': 1}, + {'id': 4, + 'address': 'DE:AD:BE:EF:00:04', + 'uuid': '00000000-0000-0000-0000-0000000000000004', + 'network_id': 0, + 'network': networks[0], + 'instance_id': 0}, + {'id': 5, + 'address': 'DE:AD:BE:EF:00:05', + 'uuid': '00000000-0000-0000-0000-0000000000000005', + 'network_id': 1, + 'network': networks[1], 'instance_id': 1}] @@ -167,7 +199,6 @@ class LinuxNetworkTestCase(test.TestCase): def test_update_dhcp_for_nw00(self): self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') - self.mox.StubOutWithMock(db, 'instance_get_all_by_network') self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') db.network_get_associated_fixed_ips(mox.IgnoreArg(), @@ -175,9 +206,6 @@ class LinuxNetworkTestCase(test.TestCase): .AndReturn([fixed_ips[0], fixed_ips[3]]) - db.instance_get_all_by_network(mox.IgnoreArg(), - mox.IgnoreArg())\ - .AndReturn(instances) db.network_get_associated_fixed_ips(mox.IgnoreArg(), mox.IgnoreArg())\ .AndReturn([fixed_ips[0], @@ -194,7 +222,6 @@ class LinuxNetworkTestCase(test.TestCase): def test_update_dhcp_for_nw01(self): self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') - self.mox.StubOutWithMock(db, 'instance_get_all_by_network') self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') db.network_get_associated_fixed_ips(mox.IgnoreArg(), @@ -202,9 +229,6 @@ class LinuxNetworkTestCase(test.TestCase): .AndReturn([fixed_ips[1], fixed_ips[2]]) - db.instance_get_all_by_network(mox.IgnoreArg(), - mox.IgnoreArg())\ - .AndReturn(instances) db.network_get_associated_fixed_ips(mox.IgnoreArg(), mox.IgnoreArg())\ .AndReturn([fixed_ips[1], @@ -257,27 +281,27 @@ class LinuxNetworkTestCase(test.TestCase): def test_get_dhcp_opts_for_nw00(self): self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') - self.mox.StubOutWithMock(db, 'instance_get_all_by_network') self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') - db.instance_get_all_by_network(mox.IgnoreArg(), - mox.IgnoreArg())\ - .AndReturn(instances) db.network_get_associated_fixed_ips(mox.IgnoreArg(), mox.IgnoreArg())\ .AndReturn([fixed_ips[0], - fixed_ips[3]]) + fixed_ips[3], + fixed_ips[4]]) db.virtual_interface_get_by_instance(mox.IgnoreArg(), mox.IgnoreArg())\ .AndReturn([vifs[0], - vifs[1]]) + vifs[1], + vifs[4]]) db.virtual_interface_get_by_instance(mox.IgnoreArg(), mox.IgnoreArg())\ .AndReturn([vifs[2], - vifs[3]]) + vifs[3], + vifs[5]]) self.mox.ReplayAll() expected_opts = '\n'\ + '\n'\ '' actual_opts = self.driver.get_dhcp_opts(None, networks[0]) @@ -285,28 +309,28 @@ class LinuxNetworkTestCase(test.TestCase): def test_get_dhcp_opts_for_nw01(self): self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') - self.mox.StubOutWithMock(db, 'instance_get_all_by_network') self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') - db.instance_get_all_by_network(mox.IgnoreArg(), - mox.IgnoreArg())\ - .AndReturn(instances) db.network_get_associated_fixed_ips(mox.IgnoreArg(), mox.IgnoreArg())\ .AndReturn([fixed_ips[1], - fixed_ips[2]]) + fixed_ips[2], + fixed_ips[5]]) db.virtual_interface_get_by_instance(mox.IgnoreArg(), mox.IgnoreArg())\ .AndReturn([vifs[0], - vifs[1]]) + vifs[1], + vifs[4]]) db.virtual_interface_get_by_instance(mox.IgnoreArg(), mox.IgnoreArg())\ .AndReturn([vifs[2], - vifs[3]]) + vifs[3], + vifs[5]]) self.mox.ReplayAll() expected_opts = 'NW-i00000000-1,3\n'\ - 'NW-i00000001-0,3' + 'NW-i00000001-0,3\n'\ + 'NW-i00000002-1,3' actual_opts = self.driver.get_dhcp_opts(None, networks[1]) self.assertEquals(actual_opts, expected_opts) -- cgit From 00c3cbe8621918fdd60f9068df2a41db52a7f76f Mon Sep 17 00:00:00 2001 From: Keisuke Tagami Date: Tue, 6 Sep 2011 10:20:28 +0900 Subject: revert codes for db --- nova/db/api.py | 0 nova/db/sqlalchemy/api.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 nova/db/api.py mode change 100755 => 100644 nova/db/sqlalchemy/api.py diff --git a/nova/db/api.py b/nova/db/api.py old mode 100755 new mode 100644 diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py old mode 100755 new mode 100644 -- cgit From d4668c7351fab83930a6fe5ee8f67dabe8027b48 Mon Sep 17 00:00:00 2001 From: Keisuke Tagami Date: Tue, 6 Sep 2011 13:57:14 +0900 Subject: fix a mistaking of deletion in ensure_floating_forward --- 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 c26e3574e..48438760a 100755 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -438,7 +438,7 @@ def ensure_vpn_forward(public_ip, port, private_ip): def ensure_floating_forward(floating_ip, fixed_ip): """Ensure floating ip forwarding rule.""" - for chain, rule in floating_forward_rules(floaing_ip, fixed_ip): + for chain, rule in floating_forward_rules(floating_ip, fixed_ip): iptables_manager.ipv4['nat'].add_rule(chain, rule) iptables_manager.apply() -- cgit From 6c55199d3ec900cea771e4f8077c87efa968c2f0 Mon Sep 17 00:00:00 2001 From: Keisuke Tagami Date: Tue, 6 Sep 2011 17:20:10 +0900 Subject: fix a mistaking of dataset and expected values on small test. --- nova/tests/test_linux_net.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nova/tests/test_linux_net.py b/nova/tests/test_linux_net.py index 3f44fbd05..be6faa07b 100755 --- a/nova/tests/test_linux_net.py +++ b/nova/tests/test_linux_net.py @@ -114,7 +114,7 @@ fixed_ips = [{'id': 0, 'instance': instances[0], 'floating_ips': []}, {'id': 2, - 'network_id': 0, + 'network_id': 1, 'address': '192.168.0.101', 'instance_id': 1, 'allocated': True, @@ -123,7 +123,7 @@ fixed_ips = [{'id': 0, 'instance': instances[1], 'floating_ips': []}, {'id': 3, - 'network_id': 1, + 'network_id': 0, 'address': '192.168.1.101', 'instance_id': 1, 'allocated': True, @@ -256,7 +256,7 @@ class LinuxNetworkTestCase(test.TestCase): "10.0.0.1,fake_instance00.novalocal,"\ "192.168.0.100,net:NW-i00000000-0\n"\ "10.0.0.4,fake_instance01.novalocal,"\ - "192.168.1.101,net:NW-i00000001-1" + "192.168.1.101,net:NW-i00000001-0" actual_hosts = self.driver.get_dhcp_hosts(None, networks[1]) self.assertEquals(actual_hosts, expected) @@ -274,7 +274,7 @@ class LinuxNetworkTestCase(test.TestCase): "10.0.0.2,fake_instance00.novalocal,"\ "192.168.1.100,net:NW-i00000000-1\n"\ "10.0.0.3,fake_instance01.novalocal,"\ - "192.168.0.101,net:NW-i00000001-0" + "192.168.0.101,net:NW-i00000001-1" actual_hosts = self.driver.get_dhcp_hosts(None, networks[0]) self.assertEquals(actual_hosts, expected) @@ -301,7 +301,7 @@ class LinuxNetworkTestCase(test.TestCase): self.mox.ReplayAll() expected_opts = '\n'\ - '\n'\ + 'NW-i00000001-0,3\n'\ '' actual_opts = self.driver.get_dhcp_opts(None, networks[0]) @@ -329,8 +329,8 @@ class LinuxNetworkTestCase(test.TestCase): self.mox.ReplayAll() expected_opts = 'NW-i00000000-1,3\n'\ - 'NW-i00000001-0,3\n'\ - 'NW-i00000002-1,3' + '\n'\ + '' actual_opts = self.driver.get_dhcp_opts(None, networks[1]) self.assertEquals(actual_opts, expected_opts) -- cgit From 0589ecebb5e610ba6a6787fada14e96af92361c2 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 6 Sep 2011 14:26:02 -0500 Subject: Set flat_injected to False by default. --- nova/network/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index e6b30d1a0..6120137ce 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -74,7 +74,7 @@ flags.DEFINE_string('flat_network_bridge', None, 'Bridge for simple network instances') flags.DEFINE_string('flat_network_dns', '8.8.4.4', 'Dns for simple network') -flags.DEFINE_bool('flat_injected', True, +flags.DEFINE_bool('flat_injected', False, 'Whether to attempt to inject network setup into guest') flags.DEFINE_string('flat_interface', None, 'FlatDhcp will bridge into this interface if set') -- cgit From 27b052c1aa2b5ae191c2e2986788c152eef9c221 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Tue, 6 Sep 2011 14:36:58 -0500 Subject: novaclient v1_0 has an ipgroups argument, but novaclient v1_1 doesn't --- nova/scheduler/abstract_scheduler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py index 7f17b642f..be6267d3a 100644 --- a/nova/scheduler/abstract_scheduler.py +++ b/nova/scheduler/abstract_scheduler.py @@ -110,7 +110,6 @@ class AbstractScheduler(driver.Scheduler): flavor_id = instance_type['flavorid'] reservation_id = instance_properties['reservation_id'] files = kwargs['injected_files'] - ipgroup = None # Not supported in OS API ... yet child_zone = zone_info['child_zone'] child_blob = zone_info['child_blob'] zone = db.zone_get(context, child_zone) @@ -124,8 +123,9 @@ class AbstractScheduler(driver.Scheduler): except novaclient_exceptions.BadRequest, e: raise exception.NotAuthorized(_("Bad credentials attempting " "to talk to zone at %(url)s.") % locals()) - nova.servers.create(name, image_ref, flavor_id, ipgroup, meta, files, - child_blob, reservation_id=reservation_id) + nova.servers.create(name, image_ref, flavor_id, + meta=meta, files=files, zone_blob=child_blob, + reservation_id=reservation_id) def _provision_resource_from_blob(self, context, build_plan_item, instance_id, request_spec, kwargs): -- cgit From 1f3856ffb92ab690b1d630deb6fa025ae74348f7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 6 Sep 2011 12:48:41 -0700 Subject: revert changes to display description --- nova/api/openstack/create_instance_helper.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 9b2928bc8..29e071609 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -161,10 +161,6 @@ class CreateInstanceHelper(object): 'config_drive': config_drive, 'password': password} - # NOTE(vish): This is solely for compatibility with anything - # that is using the display description field. - metadata = server_dict.get('metadata') or {} - display_description = metadata.get('description') or name return (extra_values, create_method(context, inst_type, @@ -172,9 +168,9 @@ class CreateInstanceHelper(object): kernel_id=kernel_id, ramdisk_id=ramdisk_id, display_name=name, - display_description=display_description, + display_description=name, key_name=key_name, - metadata=metadata, + metadata=server_dict.get('metadata', {}), access_ip_v4=server_dict.get('accessIPv4'), access_ip_v6=server_dict.get('accessIPv6'), injected_files=injected_files, -- cgit From 9b2885076d2ed438fb3189b8528f5bec6a2cda4d Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Tue, 6 Sep 2011 15:02:04 -0500 Subject: Fix a misspelling of "exception" --- nova/network/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index e6b30d1a0..050cec250 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -448,7 +448,7 @@ class NetworkManager(manager.SchedulerDependentManager): try: fixed_ips = kwargs.get('fixed_ips') or \ self.db.fixed_ip_get_by_instance(context, instance_id) - except exceptions.FixedIpNotFoundForInstance: + except exception.FixedIpNotFoundForInstance: fixed_ips = [] LOG.debug(_("network deallocation for instance |%s|"), instance_id, context=context) -- cgit From 41f3d157c917255683ae23313704f357e061911c Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 6 Sep 2011 16:41:35 -0500 Subject: Fixed unit test. --- nova/tests/test_xenapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 45dad3516..91b4161b0 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -494,6 +494,7 @@ class XenAPIVMTestCase(test.TestCase): self.check_vm_params_for_linux_with_external_kernel() def test_spawn_netinject_file(self): + self.flags(flat_injected=True) db_fakes.stub_out_db_instance_api(self.stubs, injected=True) self._tee_executed = False @@ -611,7 +612,6 @@ class XenAPIVMTestCase(test.TestCase): str(3 * 1024)) def test_rescue(self): - self.flags(flat_injected=False) instance = self._create_instance() conn = xenapi_conn.get_connection(False) conn.rescue(self.context, instance, None, []) -- cgit From 53357516226a1c00217c742eb512b7efc0f574b2 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 6 Sep 2011 18:01:57 -0700 Subject: Added use_single_default_gateway to switch from multiple default gateways to single default gateway --- nova/network/linux_net.py | 72 ++++++++++++++++++++------------------------ nova/tests/test_linux_net.py | 17 +++-------- 2 files changed, 38 insertions(+), 51 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 48438760a..affdde701 100755 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -68,6 +68,9 @@ flags.DEFINE_string('linuxnet_interface_driver', 'Driver used to create ethernet devices.') flags.DEFINE_string('linuxnet_ovs_integration_bridge', 'br-int', 'Name of Open vSwitch bridge used with linuxnet') +flags.DEFINE_bool('use_single_default_gateway', + False, 'Use single default gateway. Only first nic of vm' + ' will get default gateway from dhcp server') binary_name = os.path.basename(inspect.stack()[-1][1]) @@ -513,35 +516,27 @@ def get_dhcp_hosts(context, network_ref): def get_dhcp_opts(context, network_ref): """Get network's hosts config in dhcp-opts format.""" - # create a decision dictionary for default gateway for eache instance hosts = [] - default_gateway_network_node = dict() - network_id = network_ref['id'] - instance_set = set() - ips_ref = db.network_get_associated_fixed_ips(context, network_id) - - for fixed_ip_ref in ips_ref: - instance_set.add(fixed_ip_ref['instance_id']) - - for instance_id in instance_set: - # nic number is decided by xxx from this function in xxx function - vifs = db.virtual_interface_get_by_instance(context, instance_id) - if vifs == None: - default_gateway_network_node[instance_id] = None - continue - # offer a default gateway to the first virtual interface of instance - first_vif = vifs[0] - default_gateway_network_node[instance_id] = first_vif['network_id'] - - for fixed_ip_ref in ips_ref: - instance_id = fixed_ip_ref['instance_id'] - if instance_id in default_gateway_network_node: - target_network_id = default_gateway_network_node[instance_id] - if target_network_id == fixed_ip_ref['network_id']: - hosts.append(_host_dhcp_opts(fixed_ip_ref, gw=True)) - else: - hosts.append(_host_dhcp_opts(fixed_ip_ref, gw=None)) - + ips_ref = db.network_get_associated_fixed_ips(context, network_ref['id']) + + if ips_ref: + #set of instance ids + instance_set = set([fixed_ip_ref['instance_id'] + for fixed_ip_ref in ips_ref]) + default_gw_network_node = {} + for instance_id in instance_set: + vifs = db.virtual_interface_get_by_instance(context, instance_id) + if vifs: + #offer a default gateway to the first virtual interface + default_gw_network_node[instance_id] = vifs[0]['network_id'] + + for fixed_ip_ref in ips_ref: + instance_id = fixed_ip_ref['instance_id'] + if instance_id in default_gw_network_node: + target_network_id = default_gw_network_node[instance_id] + # we don't want default gateway for this fixed ip + if target_network_id != fixed_ip_ref['network_id']: + hosts.append(_host_dhcp_opts(fixed_ip_ref)) return '\n'.join(hosts) @@ -560,13 +555,14 @@ def update_dhcp(context, dev, network_ref): with open(conffile, 'w') as f: f.write(get_dhcp_hosts(context, network_ref)) - optsfile = _dhcp_file(dev, 'opts') - with open(optsfile, 'w') as f: - f.write(get_dhcp_opts(context, network_ref)) + if FLAGS.use_single_default_gateway: + optsfile = _dhcp_file(dev, 'opts') + with open(optsfile, 'w') as f: + f.write(get_dhcp_opts(context, network_ref)) + os.chmod(optsfile, 0644) # Make sure dnsmasq can actually read it (it setuid()s to "nobody") os.chmod(conffile, 0644) - os.chmod(optsfile, 0644) pid = _dnsmasq_pid_for(dev) @@ -598,11 +594,13 @@ def update_dhcp(context, dev, network_ref): '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(network_ref['cidr'])), '--dhcp-hostsfile=%s' % _dhcp_file(dev, 'conf'), '--dhcp-script=%s' % FLAGS.dhcpbridge, - '--dhcp-optsfile=%s' % _dhcp_file(dev, 'opts'), '--leasefile-ro'] if FLAGS.dns_server: cmd += ['-h', '-R', '--server=%s' % FLAGS.dns_server] + if FLAGS.use_single_default_gateway: + cmd += ['--dhcp-optsfile=%s' % _dhcp_file(dev, 'opts')] + _execute(*cmd, run_as_root=True) @@ -681,13 +679,9 @@ def _host_dhcp(fixed_ip_ref): "net:" + _host_dhcp_network(fixed_ip_ref)) -def _host_dhcp_opts(fixed_ip_ref, gw): +def _host_dhcp_opts(fixed_ip_ref): """Return a host string for an address in dhcp-host format.""" - if not gw: - return '%s,%s' % (_host_dhcp_network(fixed_ip_ref), - 3) - else: - return '' + return '%s,%s' % (_host_dhcp_network(fixed_ip_ref), 3) def _execute(*cmd, **kwargs): diff --git a/nova/tests/test_linux_net.py b/nova/tests/test_linux_net.py index be6faa07b..4c72f1e0f 100755 --- a/nova/tests/test_linux_net.py +++ b/nova/tests/test_linux_net.py @@ -23,6 +23,7 @@ from nova import log as logging from nova import test from nova import utils from nova.network import manager as network_manager +from nova.network import linux_net import mox @@ -194,6 +195,7 @@ class LinuxNetworkTestCase(test.TestCase): def setUp(self): super(LinuxNetworkTestCase, self).setUp() network_driver = FLAGS.network_driver + self.flags(use_single_default_gateway=True) self.driver = utils.import_object(network_driver) self.driver.db = db @@ -300,9 +302,7 @@ class LinuxNetworkTestCase(test.TestCase): vifs[5]]) self.mox.ReplayAll() - expected_opts = '\n'\ - 'NW-i00000001-0,3\n'\ - '' + expected_opts = 'NW-i00000001-0,3' actual_opts = self.driver.get_dhcp_opts(None, networks[0]) self.assertEquals(actual_opts, expected_opts) @@ -328,19 +328,12 @@ class LinuxNetworkTestCase(test.TestCase): vifs[5]]) self.mox.ReplayAll() - expected_opts = 'NW-i00000000-1,3\n'\ - '\n'\ - '' + expected_opts = "NW-i00000000-1,3" actual_opts = self.driver.get_dhcp_opts(None, networks[1]) self.assertEquals(actual_opts, expected_opts) - def test_dhcp_opts_default_gateway_network(self): - expected = "" - actual = self.driver._host_dhcp_opts(fixed_ips[0], True) - self.assertEquals(actual, expected) - def test_dhcp_opts_not_default_gateway_network(self): expected = "NW-i00000000-0,3" - actual = self.driver._host_dhcp_opts(fixed_ips[0], False) + actual = self.driver._host_dhcp_opts(fixed_ips[0]) self.assertEquals(actual, expected) -- cgit From c1763deb23dc6dcf7ca4f32aafde47501a87083f Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 7 Sep 2011 11:11:19 -0500 Subject: updated floating_ip generation --- nova/tests/fake_network.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 99b027cf3..19be83bc9 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -14,6 +14,7 @@ # 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 itertools import islice from nova import db from nova import flags @@ -68,8 +69,8 @@ def fixed_ips(num_networks, num_ips, num_floating_ips=0): for network_index in xrange(num_networks): for ip_index in xrange(num_ips): fixed_ip_id = network_index * num_ips + ip_index - f_ips = [FakeModel(**floating_ips(fixed_ip_id).next()) - for i in xrange(num_floating_ips)] + f_ips = [FakeModel(**floating_ip_dict) for floating_ip_dict + in islice(floating_ips(fixed_ip_id), num_floating_ips)] yield {'id': fixed_ip_id, 'network_id': network_index, 'address': '192.168.%d.1%02d' % (network_index, ip_index), @@ -94,7 +95,7 @@ flavor = {'id': 0, def floating_ips(fixed_ip_id): for i in xrange(154): yield {'id': i, - 'address': '10.10.10.%d' % (i + 100), + 'address': '10.10.%d.%d' % (fixed_ip_id, i + 100), 'fixed_ip_id': fixed_ip_id, 'project_id': None, 'auto_assigned': False} -- cgit From e3cb2c82224fad59c16010c0842ebcfa1ac0dc95 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 7 Sep 2011 12:41:16 -0500 Subject: changed the fixed_ip_generator --- nova/tests/fake_network.py | 110 +++++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 39 deletions(-) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 19be83bc9..ee1a4a7c7 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -14,7 +14,6 @@ # 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 itertools import islice from nova import db from nova import flags @@ -35,6 +34,17 @@ class FakeModel(dict): return self[name] +flavor = {'id': 0, + 'name': 'fake_flavor', + 'memory_mb': 2048, + 'vcpus': 2, + 'local_gb': 10, + 'flavor_id': 0, + 'swap': 0, + 'rxtx_quota': 0, + 'rxtx_cap': 3} + + def fake_network(network_id, ipv6=None): if ipv6 is None: ipv6 = FLAGS.use_ipv6 @@ -65,42 +75,6 @@ def fake_network(network_id, ipv6=None): return fake_network -def fixed_ips(num_networks, num_ips, num_floating_ips=0): - for network_index in xrange(num_networks): - for ip_index in xrange(num_ips): - fixed_ip_id = network_index * num_ips + ip_index - f_ips = [FakeModel(**floating_ip_dict) for floating_ip_dict - in islice(floating_ips(fixed_ip_id), num_floating_ips)] - yield {'id': fixed_ip_id, - 'network_id': network_index, - 'address': '192.168.%d.1%02d' % (network_index, ip_index), - 'instance_id': 0, - 'allocated': False, - # and since network_id and vif_id happen to be equivalent - 'virtual_interface_id': network_index, - 'floating_ips': f_ips} - - -flavor = {'id': 0, - 'name': 'fake_flavor', - 'memory_mb': 2048, - 'vcpus': 2, - 'local_gb': 10, - 'flavor_id': 0, - 'swap': 0, - 'rxtx_quota': 0, - 'rxtx_cap': 3} - - -def floating_ips(fixed_ip_id): - for i in xrange(154): - yield {'id': i, - 'address': '10.10.%d.%d' % (fixed_ip_id, i + 100), - 'fixed_ip_id': fixed_ip_id, - 'project_id': None, - 'auto_assigned': False} - - def vifs(n): for x in xrange(n): yield {'id': x, @@ -111,6 +85,58 @@ def vifs(n): 'instance_id': 0} +#def fixed_ips(num_networks, num_ips, num_floating_ips=0): +# for network_index in xrange(num_networks): +# for ip_index in xrange(num_ips): +# fixed_ip_id = network_index * num_ips + ip_index +# islice = itertools.islice +# yield {'id': fixed_ip_id, +# 'network_id': network_index, +# 'address': '192.168.%d.1%02d' % (network_index, ip_index), +# 'instance_id': 0, +# 'allocated': False, +# # and since network_id and vif_id happen to be equivalent +# 'virtual_interface_id': network_index, +# 'floating_ips': f_ips} + + +def floating_ip_ids(): + for i in xrange(99): + yield i + + +def fixed_ip_ids(): + for i in xrange(99): + yield i + + +floating_ip_id = floating_ip_ids() +fixed_ip_id = fixed_ip_ids() + + +def next_fixed_ip(network_id, num_floating_ips=0): + next_id = fixed_ip_id.next() + f_ips = [FakeModel(**next_floating_ip(fixed_ip_id)) + for i in xrange(num_floating_ips)] + return {'id': next, + 'network_id': network_id, + 'address': '192.168.%d.1%02d' % (network_id, next_id), + 'instance_id': 0, + 'allocated': False, + # and since network_id and vif_id happen to be equivalent + 'virtual_interface_id': network_id, + 'floating_ips': f_ips} + + +def next_floating_ip(fixed_ip_id): + next_id = floating_ip_id.next() + return {'id': next_id, + 'address': '10.10.10.1%02d' % next_id, + 'fixed_ip_id': fixed_ip_id, + 'project_id': None, + 'auto_assigned': False} + + def ipv4_like(ip, match_string): ip = ip.split('.') match_octets = match_string.split('.') @@ -123,15 +149,21 @@ def ipv4_like(ip, match_string): return True -def fake_get_instance_nw_info(stubs, num_networks=1, ips_per_vif=2): +def fake_get_instance_nw_info(stubs, num_networks=1, ips_per_vif=2, + floating_ips_per_fixed_ip=0): # stubs is the self.stubs from the test # ips_per_vif is the number of ips each vif will have # num_floating_ips is number of float ips for each fixed ip network = network_manager.FlatManager(host=HOST) network.db = db + # reset the fixed and floating ip generators + floating_ip_id = floating_ip_ids() + fixed_ip_id = fixed_ip_ids() + def fixed_ips_fake(*args, **kwargs): - return list(fixed_ips(num_networks, ips_per_vif)) + return [next_fixed_ip(i, floating_ips_per_fixed_ip) + for i in xrange(num_networks) for j in xrange(ips_per_vif)] def virtual_interfaces_fake(*args, **kwargs): return [vif for vif in vifs(num_networks)] -- cgit From 262b5cf6e8bd577d2b08fb92e6da56f8bcdecd57 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Wed, 7 Sep 2011 13:37:29 -0500 Subject: weigh_hosts() needs to return a list of hosts for the instances, not just a list of hosts --- nova/scheduler/base_scheduler.py | 16 +++++++++++-- nova/tests/scheduler/test_abstract_scheduler.py | 32 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/nova/scheduler/base_scheduler.py b/nova/scheduler/base_scheduler.py index 35e5af035..e9c078b81 100644 --- a/nova/scheduler/base_scheduler.py +++ b/nova/scheduler/base_scheduler.py @@ -55,5 +55,17 @@ class BaseScheduler(abstract_scheduler.AbstractScheduler): scheduling objectives """ # NOTE(sirp): The default logic is the same as the NoopCostFunction - return [dict(weight=1, hostname=hostname, capabilities=capabilities) - for hostname, capabilities in hosts] + hosts = [dict(weight=1, hostname=hostname, capabilities=capabilities) + for hostname, capabilities in hosts] + + # NOTE(Vek): What we actually need to return is enough hosts + # for all the instances! + num_instances = request_spec.get('num_instances', 1) + instances = [] + while num_instances > len(hosts): + instances.extend(hosts) + num_instances -= len(hosts) + if num_instances > 0: + instances.extend(hosts[:num_instances]) + + return instances diff --git a/nova/tests/scheduler/test_abstract_scheduler.py b/nova/tests/scheduler/test_abstract_scheduler.py index aa97e2344..f47699048 100644 --- a/nova/tests/scheduler/test_abstract_scheduler.py +++ b/nova/tests/scheduler/test_abstract_scheduler.py @@ -65,6 +65,11 @@ class FakeAbstractScheduler(abstract_scheduler.AbstractScheduler): pass +class FakeBaseScheduler(base_scheduler.BaseScheduler): + # No need to stub anything at the moment + pass + + class FakeZoneManager(zone_manager.ZoneManager): def __init__(self): self.service_states = { @@ -365,3 +370,30 @@ class AbstractSchedulerTestCase(test.TestCase): self.assertEqual(fixture._decrypt_blob(test_data), json.dumps(test_data)) + + +class BaseSchedulerTestCase(test.TestCase): + """Test case for Base Scheduler.""" + + def test_weigh_hosts(self): + """ + Try to weigh a short list of hosts and make sure enough + entries for a larger number instances are returned. + """ + + sched = FakeBaseScheduler() + + # Fake out a list of hosts + zm = FakeZoneManager() + hostlist = [(host, services['compute']) + for host, services in zm.service_states + if 'compute' in services] + + # Call weigh_hosts() + num_instances = len(hostlist) * 2 + len(hostlist) / 2 + instlist = sched.weigh_hosts('compute', + dict(num_instances=num_instances), + hostlist) + + # Should be enough entries to cover all instances + self.assertEqual(len(instlist), num_instances) -- cgit From 34baac0f11ff2084caa46a533aad411988e1541e Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 7 Sep 2011 11:41:33 -0700 Subject: Fix for LP Bug #837867 --- nova/network/manager.py | 6 ++--- nova/tests/test_network.py | 58 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index 050cec250..d1791f777 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -128,8 +128,8 @@ class RPCAllocateFixedIP(object): """Calls allocate_fixed_ip once for each network.""" green_pool = greenpool.GreenPool() - vpn = kwargs.pop('vpn') - requested_networks = kwargs.pop('requested_networks') + vpn = kwargs.get('vpn') + requested_networks = kwargs.get('requested_networks') for network in networks: address = None @@ -890,7 +890,7 @@ class FlatManager(NetworkManager): def _allocate_fixed_ips(self, context, instance_id, host, networks, **kwargs): """Calls allocate_fixed_ip once for each network.""" - requested_networks = kwargs.pop('requested_networks') + requested_networks = kwargs.get('requested_networks') for network in networks: address = None if requested_networks is not None: diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 25ff940f0..05fca7bc5 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -58,7 +58,7 @@ networks = [{'id': 0, 'dns1': '192.168.0.1', 'dns2': '192.168.0.2', 'vlan': None, - 'host': None, + 'host': HOST, 'project_id': 'fake_project', 'vpn_public_address': '192.168.0.2'}, {'id': 1, @@ -78,7 +78,7 @@ networks = [{'id': 0, 'dns1': '192.168.0.1', 'dns2': '192.168.0.2', 'vlan': None, - 'host': None, + 'host': HOST, 'project_id': 'fake_project', 'vpn_public_address': '192.168.1.2'}] @@ -247,6 +247,34 @@ class FlatNetworkTestCase(test.TestCase): self.network.validate_networks(None, requested_networks) + def test_add_fixed_ip_instance_without_vpn_requested_networks(self): + self.mox.StubOutWithMock(db, 'network_get') + self.mox.StubOutWithMock(db, 'network_update') + self.mox.StubOutWithMock(db, 'fixed_ip_associate_pool') + self.mox.StubOutWithMock(db, 'instance_get') + self.mox.StubOutWithMock(db, + 'virtual_interface_get_by_instance_and_network') + self.mox.StubOutWithMock(db, 'fixed_ip_update') + + db.fixed_ip_update(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + db.virtual_interface_get_by_instance_and_network(mox.IgnoreArg(), + mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'id': 0}) + + db.instance_get(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn({'security_groups': + [{'id': 0}]}) + db.fixed_ip_associate_pool(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn('192.168.0.101') + db.network_get(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks[0]) + db.network_update(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) + self.mox.ReplayAll() + self.network.add_fixed_ip_to_instance(self.context, 1, HOST, + networks[0]['id']) + class VlanNetworkTestCase(test.TestCase): def setUp(self): @@ -387,6 +415,32 @@ class VlanNetworkTestCase(test.TestCase): mox.IgnoreArg(), mox.IgnoreArg()) + def test_add_fixed_ip_instance_without_vpn_requested_networks(self): + self.mox.StubOutWithMock(db, 'network_get') + self.mox.StubOutWithMock(db, 'fixed_ip_associate_pool') + self.mox.StubOutWithMock(db, 'instance_get') + self.mox.StubOutWithMock(db, + 'virtual_interface_get_by_instance_and_network') + self.mox.StubOutWithMock(db, 'fixed_ip_update') + + db.fixed_ip_update(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + db.virtual_interface_get_by_instance_and_network(mox.IgnoreArg(), + mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'id': 0}) + + db.instance_get(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn({'security_groups': + [{'id': 0}]}) + db.fixed_ip_associate_pool(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn('192.168.0.101') + db.network_get(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks[0]) + self.mox.ReplayAll() + self.network.add_fixed_ip_to_instance(self.context, 1, HOST, + networks[0]['id']) + class CommonNetworkTestCase(test.TestCase): -- cgit From 3e61268a350ba0ec43d28dfbac6e82503a174dba Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 7 Sep 2011 14:33:43 -0500 Subject: had used wrong variable --- nova/tests/fake_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index ee1a4a7c7..76e1b991d 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -116,7 +116,7 @@ fixed_ip_id = fixed_ip_ids() def next_fixed_ip(network_id, num_floating_ips=0): next_id = fixed_ip_id.next() - f_ips = [FakeModel(**next_floating_ip(fixed_ip_id)) + f_ips = [FakeModel(**next_floating_ip(next_id)) for i in xrange(num_floating_ips)] return {'id': next, 'network_id': network_id, -- cgit From 31ae07f06c71968deb67c5aa1c111fe9e14fb5d8 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 7 Sep 2011 14:36:22 -0500 Subject: forgot _id --- nova/tests/fake_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 76e1b991d..97231bfa1 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -118,7 +118,7 @@ def next_fixed_ip(network_id, num_floating_ips=0): next_id = fixed_ip_id.next() f_ips = [FakeModel(**next_floating_ip(next_id)) for i in xrange(num_floating_ips)] - return {'id': next, + return {'id': next_id, 'network_id': network_id, 'address': '192.168.%d.1%02d' % (network_id, next_id), 'instance_id': 0, -- cgit From a158d5721c18902b290a42bb3874b34e54dbcd7b Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 7 Sep 2011 13:10:05 -0700 Subject: exclude net tag from host_dhcp if use_single_default_gateway flag is set to false --- nova/network/linux_net.py | 17 ++++++++++++----- nova/tests/test_linux_net.py | 6 ++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index affdde701..7d89b2bcc 100755 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -672,11 +672,18 @@ def _host_dhcp_network(fixed_ip_ref): def _host_dhcp(fixed_ip_ref): """Return a host string for an address in dhcp-host format.""" instance_ref = fixed_ip_ref['instance'] - return '%s,%s.%s,%s,%s' % (fixed_ip_ref['virtual_interface']['address'], - instance_ref['hostname'], - FLAGS.dhcp_domain, - fixed_ip_ref['address'], - "net:" + _host_dhcp_network(fixed_ip_ref)) + vif = fixed_ip_ref['virtual_interface'] + if FLAGS.use_single_default_gateway: + return '%s,%s.%s,%s,%s' % (vif['address'], + instance_ref['hostname'], + FLAGS.dhcp_domain, + fixed_ip_ref['address'], + "net:" + _host_dhcp_network(fixed_ip_ref)) + else: + return '%s,%s.%s,%s' % (vif['address'], + instance_ref['hostname'], + FLAGS.dhcp_domain, + fixed_ip_ref['address']) def _host_dhcp_opts(fixed_ip_ref): diff --git a/nova/tests/test_linux_net.py b/nova/tests/test_linux_net.py index 4c72f1e0f..6d0a2b6bd 100755 --- a/nova/tests/test_linux_net.py +++ b/nova/tests/test_linux_net.py @@ -337,3 +337,9 @@ class LinuxNetworkTestCase(test.TestCase): expected = "NW-i00000000-0,3" actual = self.driver._host_dhcp_opts(fixed_ips[0]) self.assertEquals(actual, expected) + + def test_host_dhcp_without_default_gateway_network(self): + self.flags(use_single_default_gateway=False) + expected = ("10.0.0.1,fake_instance00.novalocal,192.168.0.100") + actual = self.driver._host_dhcp(fixed_ips[0]) + self.assertEquals(actual, expected) -- cgit From 4d98408d4ec605ad9a591d5166f2b8ea6e723ecb Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 7 Sep 2011 13:37:58 -0700 Subject: modified unit tests, set use_single_default_gateway flag to True whereever needed instead of setting it in the init method --- nova/tests/test_linux_net.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_linux_net.py b/nova/tests/test_linux_net.py index 6d0a2b6bd..99577b88e 100755 --- a/nova/tests/test_linux_net.py +++ b/nova/tests/test_linux_net.py @@ -195,11 +195,11 @@ class LinuxNetworkTestCase(test.TestCase): def setUp(self): super(LinuxNetworkTestCase, self).setUp() network_driver = FLAGS.network_driver - self.flags(use_single_default_gateway=True) self.driver = utils.import_object(network_driver) self.driver.db = db def test_update_dhcp_for_nw00(self): + self.flags(use_single_default_gateway=True) self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') @@ -223,6 +223,7 @@ class LinuxNetworkTestCase(test.TestCase): self.driver.update_dhcp(None, "eth0", networks[0]) def test_update_dhcp_for_nw01(self): + self.flags(use_single_default_gateway=True) self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') @@ -246,6 +247,7 @@ class LinuxNetworkTestCase(test.TestCase): self.driver.update_dhcp(None, "eth0", networks[0]) def test_get_dhcp_hosts_for_nw00(self): + self.flags(use_single_default_gateway=True) self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') db.network_get_associated_fixed_ips(mox.IgnoreArg(), @@ -264,6 +266,7 @@ class LinuxNetworkTestCase(test.TestCase): self.assertEquals(actual_hosts, expected) def test_get_dhcp_hosts_for_nw01(self): + self.flags(use_single_default_gateway=True) self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips') db.network_get_associated_fixed_ips(mox.IgnoreArg(), @@ -339,7 +342,6 @@ class LinuxNetworkTestCase(test.TestCase): self.assertEquals(actual, expected) def test_host_dhcp_without_default_gateway_network(self): - self.flags(use_single_default_gateway=False) expected = ("10.0.0.1,fake_instance00.novalocal,192.168.0.100") actual = self.driver._host_dhcp(fixed_ips[0]) self.assertEquals(actual, expected) -- cgit From 591997a76a8395a72c7316207983e1225c9c4a62 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Wed, 7 Sep 2011 20:40:48 +0000 Subject: fix a couple of typos in the added unit test --- nova/tests/scheduler/test_abstract_scheduler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/tests/scheduler/test_abstract_scheduler.py b/nova/tests/scheduler/test_abstract_scheduler.py index f47699048..9bf128b13 100644 --- a/nova/tests/scheduler/test_abstract_scheduler.py +++ b/nova/tests/scheduler/test_abstract_scheduler.py @@ -26,6 +26,7 @@ from nova import test from nova.compute import api as compute_api from nova.scheduler import driver from nova.scheduler import abstract_scheduler +from nova.scheduler import base_scheduler from nova.scheduler import zone_manager @@ -386,7 +387,7 @@ class BaseSchedulerTestCase(test.TestCase): # Fake out a list of hosts zm = FakeZoneManager() hostlist = [(host, services['compute']) - for host, services in zm.service_states + for host, services in zm.service_states.items() if 'compute' in services] # Call weigh_hosts() -- cgit From 3334fabe55c862531e3ced21b211710857c1e087 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 7 Sep 2011 15:48:31 -0500 Subject: removed vestige --- nova/tests/fake_network.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 97231bfa1..5e536a6ee 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -85,21 +85,6 @@ def vifs(n): 'instance_id': 0} -#def fixed_ips(num_networks, num_ips, num_floating_ips=0): -# for network_index in xrange(num_networks): -# for ip_index in xrange(num_ips): -# fixed_ip_id = network_index * num_ips + ip_index -# islice = itertools.islice -# yield {'id': fixed_ip_id, -# 'network_id': network_index, -# 'address': '192.168.%d.1%02d' % (network_index, ip_index), -# 'instance_id': 0, -# 'allocated': False, -# # and since network_id and vif_id happen to be equivalent -# 'virtual_interface_id': network_index, -# 'floating_ips': f_ips} - - def floating_ip_ids(): for i in xrange(99): yield i -- cgit From 2bed69e61aefdc8e2aa7eeb31fe9f338e912a01d Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 7 Sep 2011 16:06:35 -0500 Subject: properly handle the id resetters --- nova/tests/fake_network.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 5e536a6ee..1ecb99b31 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -143,6 +143,7 @@ def fake_get_instance_nw_info(stubs, num_networks=1, ips_per_vif=2, network.db = db # reset the fixed and floating ip generators + global floating_ip_id, fixed_ip_id floating_ip_id = floating_ip_ids() fixed_ip_id = fixed_ip_ids() -- cgit From d2b9299408f07f995fffc8b8559f52ee6adeeaad Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Wed, 7 Sep 2011 18:01:52 -0500 Subject: spread-first strategy --- nova/scheduler/abstract_scheduler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py index 7f17b642f..a81fa53cd 100644 --- a/nova/scheduler/abstract_scheduler.py +++ b/nova/scheduler/abstract_scheduler.py @@ -20,8 +20,9 @@ customize the behavior: filter_hosts() and weigh_hosts(). The default behavior is to simply select all hosts and weight them the same. """ -import operator import json +import operator +import random import M2Crypto @@ -40,6 +41,8 @@ from nova.scheduler import api from nova.scheduler import driver FLAGS = flags.FLAGS +flags.DEFINE_boolean('spread_first', False, + 'Use a spread-first zone scheduler strategy') LOG = logging.getLogger('nova.scheduler.abstract_scheduler') -- cgit From ad25b7aa2ad744607b20432d635f70cc645cc6f6 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Wed, 7 Sep 2011 18:08:39 -0500 Subject: actually shuffle the weighted_hosts list... --- nova/scheduler/abstract_scheduler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py index a81fa53cd..b4c2bf4f1 100644 --- a/nova/scheduler/abstract_scheduler.py +++ b/nova/scheduler/abstract_scheduler.py @@ -295,6 +295,8 @@ class AbstractScheduler(driver.Scheduler): "child_zone": child_zone, "child_blob": weighting["blob"]} weighted_hosts.append(host_dict) + if FLAGS.spread_first: + random.shuffle(weighted_hosts) weighted_hosts.sort(key=operator.itemgetter('weight')) return weighted_hosts -- cgit From b6d454762d7fdf9b202d8a580dd9bfdf069a5e80 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 7 Sep 2011 16:38:41 -0700 Subject: fix for lp844364: improve check for fixed_ip association --- nova/api/openstack/contrib/floating_ips.py | 4 +- .../api/openstack/contrib/test_floating_ips.py | 78 +++++++++++++++++++++- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index d1add8f83..9ceb5858f 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -107,7 +107,7 @@ class FloatingIPController(object): context = req.environ['nova.context'] floating_ip = self.network_api.get_floating_ip(context, id) - if 'fixed_ip' in floating_ip: + if floating_ip['fixed_ip']: self.network_api.disassociate_floating_ip(context, floating_ip['address']) @@ -161,7 +161,7 @@ class Floating_ips(extensions.ExtensionDescriptor): raise webob.exc.HTTPBadRequest(explanation=msg) floating_ip = self.network_api.get_floating_ip_by_ip(context, address) - if 'fixed_ip' in floating_ip: + if floating_ip['fixed_ip']: self.network_api.disassociate_floating_ip(context, address) return webob.Response(status_int=202) diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 642f2b841..0744f0a11 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -212,11 +212,45 @@ class FloatingIpTest(test.TestCase): "fixed_ip": None} self.assertEqual(ip, expected) - def test_floating_ip_release(self): + def test_floating_ip_release_associated(self): + self.disassociated = False + + def get_floating_ip(ignore, context, id): + return {'id': 1, 'address': '10.10.10.10', + 'fixed_ip': {'id': 1}} + + def disassociate(ignore, context, floating_address): + self.disassociated = True + + self.stubs.Set(network.api.API, "get_floating_ip", + get_floating_ip) + self.stubs.Set(network.api.API, "disassociate_floating_ip", + disassociate) + req = webob.Request.blank('/v1.1/123/os-floating-ips/1') + req.method = 'DELETE' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertTrue(self.disassociated) + + def test_floating_ip_release_disassociated(self): + self.disassociated = False + + def fake_get_floating_ip(ignore, context, id): + return {'id': 1, 'address': '10.10.10.10', + 'fixed_ip': None} + + def fake_disassociate(ignore, context, floating_address): + self.disassociated = True + + self.stubs.Set(network.api.API, "get_floating_ip", + fake_get_floating_ip) + self.stubs.Set(network.api.API, "disassociate_floating_ip", + fake_disassociate) req = webob.Request.blank('/v1.1/123/os-floating-ips/1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) + self.assertFalse(self.disassociated) def test_add_floating_ip_to_instance(self): self.stubs.Set(network.api.API, "associate_floating_ip", @@ -289,8 +323,45 @@ class FloatingIpTest(test.TestCase): self.assertEqual(resp.status_int, 202) self.assertTrue(self.disassociated) - def test_remove_floating_ip_from_instance(self): - body = dict(removeFloatingIp=dict(address='11.0.0.1')) + def test_remove_associated_floating_ip_from_instance(self): + self.disassociated = False + + def fake_get_floating_ip_by_ip(ignore, context, ip): + return {'id': 1, 'address': '10.10.10.10', + 'fixed_ip': {'id': 1}} + + def fake_disassociate(ignore, context, floating_address): + self.disassociated = True + + self.stubs.Set(network.api.API, "get_floating_ip_by_ip", + fake_get_floating_ip_by_ip) + self.stubs.Set(network.api.API, "disassociate_floating_ip", + fake_disassociate) + body = dict(removeFloatingIp=dict(address='10.10.10.10')) + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 202) + self.assertTrue(self.disassociated) + + def test_remove_disassociated_floating_ip_from_instance(self): + self.disassociated = False + + def fake_get_floating_ip_by_ip(ignore, context, ip): + return {'id': 1, 'address': '10.10.10.10', + 'fixed_ip': None} + + def fake_disassociate(ignore, context, floating_address): + self.disassociated = True + + self.stubs.Set(network.api.API, "get_floating_ip_by_ip", + fake_get_floating_ip_by_ip) + self.stubs.Set(network.api.API, "disassociate_floating_ip", + fake_disassociate) + body = dict(removeFloatingIp=dict(address='10.10.10.10')) req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = "POST" req.body = json.dumps(body) @@ -298,6 +369,7 @@ class FloatingIpTest(test.TestCase): resp = req.get_response(fakes.wsgi_app()) self.assertEqual(resp.status_int, 202) + self.assertFalse(self.disassociated) def test_bad_address_param_in_remove_floating_ip(self): body = dict(removeFloatingIp=dict(badparam='11.0.0.1')) -- cgit From b3bc07d4dcb43b6e070a136eb5532def6b623e6e Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 7 Sep 2011 20:40:47 -0700 Subject: remove the short circuit in abstract scheduler when no local hosts are available --- nova/scheduler/abstract_scheduler.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py index 7f17b642f..cb8db599f 100644 --- a/nova/scheduler/abstract_scheduler.py +++ b/nova/scheduler/abstract_scheduler.py @@ -269,9 +269,6 @@ class AbstractScheduler(driver.Scheduler): # Filter local hosts based on requirements ... filtered_hosts = self.filter_hosts(topic, request_spec, unfiltered_hosts) - if not filtered_hosts: - LOG.warn(_("No hosts available")) - return [] # weigh the selected hosts. # weighted_hosts = [{weight=weight, hostname=hostname, -- cgit From 5426687825cd64adf0524de2808eed1cca15f521 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 7 Sep 2011 20:49:42 -0700 Subject: added test to cover case where no local hosts are available but child hosts are --- nova/tests/scheduler/test_abstract_scheduler.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/nova/tests/scheduler/test_abstract_scheduler.py b/nova/tests/scheduler/test_abstract_scheduler.py index aa97e2344..a65d15e43 100644 --- a/nova/tests/scheduler/test_abstract_scheduler.py +++ b/nova/tests/scheduler/test_abstract_scheduler.py @@ -365,3 +365,25 @@ class AbstractSchedulerTestCase(test.TestCase): self.assertEqual(fixture._decrypt_blob(test_data), json.dumps(test_data)) + + def test_empty_local_hosts(self): + """ + Create a nested set of FakeZones, try to build multiple instances + and ensure that a select call returns the appropriate build plan. + """ + sched = FakeAbstractScheduler() + self.stubs.Set(sched, '_call_zone_method', fake_call_zone_method) + self.stubs.Set(nova.db, 'zone_get_all', fake_zone_get_all) + + zm = FakeZoneManager() + # patch this to have no local hosts + zm.service_states = {} + sched.set_zone_manager(zm) + + fake_context = {} + build_plan = sched.select(fake_context, + {'instance_type': {'memory_mb': 512}, + 'num_instances': 4}) + + # 0 from local zones, 12 from remotes + self.assertEqual(12, len(build_plan)) -- cgit From b13ca667bdd3303bbfcd4e58cc6d773cea09661d Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 7 Sep 2011 21:48:11 -0700 Subject: catch exceptions from novaclient when talking to child zones. store them and re-raise if no other child zones return any results. If no exceptions are raised but no results are returned, raise a NotFound exception. --- nova/scheduler/api.py | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 55cea5f8f..a5124678d 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -103,22 +103,6 @@ def update_service_capabilities(context, service_name, host, capabilities): return rpc.fanout_cast(context, 'scheduler', kwargs) -def _wrap_method(function, self): - """Wrap method to supply self.""" - def _wrap(*args, **kwargs): - return function(self, *args, **kwargs) - return _wrap - - -def _process(func, zone): - """Worker stub for green thread pool. Give the worker - an authenticated nova client and zone info.""" - nova = novaclient.Client(zone.username, zone.password, None, - zone.api_url) - nova.authenticate() - return func(nova, zone) - - def call_zone_method(context, method_name, errors_to_ignore=None, novaclient_collection_name='zones', zones=None, *args, **kwargs): @@ -166,6 +150,28 @@ def child_zone_helper(zone_list, func): For example, if you are calling server.pause(), the list will be whatever the response from server.pause() is. One entry per child zone called.""" + + def _wrap_method(function, arg1): + """Wrap method to supply an argument.""" + def _wrap(*args, **kwargs): + return function(arg1, *args, **kwargs) + return _wrap + + def _process(func, zone): + """Worker stub for green thread pool. Give the worker + an authenticated nova client and zone info.""" + try: + nova = novaclient.Client(zone.username, zone.password, None, + zone.api_url) + nova.authenticate() + except novaclient_exceptions.BadRequest, e: + url = zone.api_url + LOG.warn(_("Failed request to zone; URL=%(url)s: %(e)s") + % locals()) + return e + else: + return func(nova, zone) + green_pool = greenpool.GreenPool() return [result for result in green_pool.imap( _wrap_method(_process, func), zone_list)] @@ -260,6 +266,8 @@ class reroute_compute(object): if not FLAGS.enable_zone_routing: raise exception.InstanceNotFound(instance_id=item_uuid) + self.item_uuid = item_uuid + zones = db.zone_get_all(context) if not zones: raise exception.InstanceNotFound(instance_id=item_uuid) @@ -342,9 +350,13 @@ class reroute_compute(object): dict {'server':{k:v}}. Others may return a list of them, like {'servers':[{k,v}]}""" reduced_response = [] + found_exception = None for zone_response in zone_responses: if not zone_response: continue + if isinstance(zone_response, BaseException): + found_exception = zone_response + continue server = zone_response.__dict__ @@ -355,7 +367,9 @@ class reroute_compute(object): reduced_response.append(dict(server=server)) if reduced_response: return reduced_response[0] # first for now. - return {} + elif found_exception: + raise found_exception + exception.InstanceNotFound(instance_id=self.item_uuid) def redirect_handler(f): -- cgit From bc84c1306e9334d4082cadee4dcb5cd14a905afe Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 7 Sep 2011 22:57:36 -0700 Subject: pep8 fix for tests/api/openstack/test_servers.py which is an issue in trunk --- nova/tests/api/openstack/test_servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 2ef687709..d063a60c2 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3615,7 +3615,7 @@ class TestGetKernelRamdiskFromImage(test.TestCase): self.assertRaises(exception.NotFound, self._get_k_r, image_meta) def test_ami_no_ramdisk(self): - """If an ami is missing a ramdisk, return kernel ID and None for + """If an ami is missing a ramdisk, return kernel ID and None for ramdisk ID """ image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', -- cgit From be645283c85d69e2d3cf4f4eabdbb545aaf139bf Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 7 Sep 2011 23:24:07 -0700 Subject: create a new exception ZoneRequestError to use for returning errors when zone requests couldn't complete --- nova/exception.py | 7 +++++++ nova/scheduler/api.py | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/nova/exception.py b/nova/exception.py index 95d8229b5..aa6609461 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -806,3 +806,10 @@ class CannotResizeToSmallerSize(NovaException): class ImageTooLarge(NovaException): message = _("Image is larger than instance type allows") + + +class ZoneRequestError(Error): + def __init__(self, message=None): + if message is None: + message = _("1 or more Zones could not complete the request") + super(ZoneRequestError, self).__init__(message=message) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index a5124678d..05685fc15 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -168,7 +168,10 @@ def child_zone_helper(zone_list, func): url = zone.api_url LOG.warn(_("Failed request to zone; URL=%(url)s: %(e)s") % locals()) - return e + # This is being returned instead of raised, so that when results are + # processed in unmarshal_result() after the greenpool.imap completes, + # the exception can be raised there if no other zones had a response. + return exception.ZoneRequestError() else: return func(nova, zone) -- cgit From d1b1f301583fd67050c45f4c863733f251620a9c Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 7 Sep 2011 23:39:39 -0700 Subject: typo trying to raise InstanceNotFound when all zones returned nothing --- nova/scheduler/api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 05685fc15..6081c3f02 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -168,9 +168,10 @@ def child_zone_helper(zone_list, func): url = zone.api_url LOG.warn(_("Failed request to zone; URL=%(url)s: %(e)s") % locals()) - # This is being returned instead of raised, so that when results are - # processed in unmarshal_result() after the greenpool.imap completes, - # the exception can be raised there if no other zones had a response. + # This is being returned instead of raised, so that when + # results are # processed in unmarshal_result() after the + # greenpool.imap completes, # the exception can be raised + # there if no other zones had a response. return exception.ZoneRequestError() else: return func(nova, zone) @@ -372,7 +373,7 @@ class reroute_compute(object): return reduced_response[0] # first for now. elif found_exception: raise found_exception - exception.InstanceNotFound(instance_id=self.item_uuid) + raise exception.InstanceNotFound(instance_id=self.item_uuid) def redirect_handler(f): -- cgit From be7a081976d37b84d93028673d08ab78bc9d8a73 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 7 Sep 2011 23:45:11 -0700 Subject: comment fix --- nova/scheduler/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 6081c3f02..719437b73 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -169,8 +169,8 @@ def child_zone_helper(zone_list, func): LOG.warn(_("Failed request to zone; URL=%(url)s: %(e)s") % locals()) # This is being returned instead of raised, so that when - # results are # processed in unmarshal_result() after the - # greenpool.imap completes, # the exception can be raised + # results are processed in unmarshal_result() after the + # greenpool.imap completes, the exception can be raised # there if no other zones had a response. return exception.ZoneRequestError() else: -- cgit From 973870c82445d4c1ebbd46f2ba7c3817ae5e7f87 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 8 Sep 2011 01:09:22 -0700 Subject: added tests for failure cases talking with zones --- nova/tests/scheduler/test_scheduler.py | 102 +++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 5 deletions(-) diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index a52dd041a..890348192 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -963,9 +963,14 @@ class FakeZone(object): self.password = password +ZONE_API_URL1 = "http://1.example.com" +ZONE_API_URL2 = "http://2.example.com" + + def zone_get_all(context): return [ - FakeZone(1, 'http://example.com', 'bob', 'xxx'), + FakeZone(1, ZONE_API_URL1, 'bob', 'xxx'), + FakeZone(2, ZONE_API_URL2, 'bob', 'xxx'), ] @@ -1065,7 +1070,9 @@ class ZoneRedirectTest(test.TestCase): def test_unmarshal_single_server(self): decorator = api.reroute_compute("foo") - self.assertEquals(decorator.unmarshall_result([]), {}) + decorator.item_uuid = 'fake_uuid' + self.assertRaises(exception.InstanceNotFound, + decorator.unmarshall_result, []) self.assertEquals(decorator.unmarshall_result( [FakeResource(dict(a=1, b=2)), ]), dict(server=dict(a=1, b=2))) @@ -1079,6 +1086,90 @@ class ZoneRedirectTest(test.TestCase): [FakeResource(dict(_a=1, manager=2)), ]), dict(server={})) + def test_one_zone_down_no_instances(self): + + def _fake_issue_novaclient_command(nova, zone, *args, **kwargs): + return None + + class FakeNovaClientWithFailure(object): + def __init__(self, username, password, method, api_url): + self.api_url = api_url + + def authenticate(self): + if self.api_url == ZONE_API_URL2: + raise novaclient_exceptions.BadRequest('foo') + + self.stubs.Set(api, '_issue_novaclient_command', + _fake_issue_novaclient_command) + self.stubs.Set(api.novaclient, 'Client', FakeNovaClientWithFailure) + + @api.reroute_compute("get") + def do_get(self, context, uuid): + pass + + self.assertRaises(exception.ZoneRequestError, + do_get, None, {}, FAKE_UUID) + + def test_one_zone_down_got_instance(self): + + def _fake_issue_novaclient_command(nova, zone, *args, **kwargs): + class FakeServer(object): + def __init__(self): + self.id = FAKE_UUID + self.test = '1234' + return FakeServer() + + class FakeNovaClientWithFailure(object): + def __init__(self, username, password, method, api_url): + self.api_url = api_url + + def authenticate(self): + if self.api_url == ZONE_API_URL2: + raise novaclient_exceptions.BadRequest('foo') + + self.stubs.Set(api, '_issue_novaclient_command', + _fake_issue_novaclient_command) + self.stubs.Set(api.novaclient, 'Client', FakeNovaClientWithFailure) + + @api.reroute_compute("get") + def do_get(self, context, uuid): + pass + + try: + do_get(None, {}, FAKE_UUID) + except api.RedirectResult, e: + results = e.results + self.assertIn('server', results) + self.assertEqual(results['server']['id'], FAKE_UUID) + self.assertEqual(results['server']['test'], '1234') + except Exception, e: + self.fail(_("RedirectResult should have been raised")) + else: + self.fail(_("RedirectResult should have been raised")) + + def test_zones_up_no_instances(self): + + def _fake_issue_novaclient_command(nova, zone, *args, **kwargs): + return None + + class FakeNovaClientNoFailure(object): + def __init__(self, username, password, method, api_url): + pass + + def authenticate(self): + return + + self.stubs.Set(api, '_issue_novaclient_command', + _fake_issue_novaclient_command) + self.stubs.Set(api.novaclient, 'Client', FakeNovaClientNoFailure) + + @api.reroute_compute("get") + def do_get(self, context, uuid): + pass + + self.assertRaises(exception.InstanceNotFound, + do_get, None, {}, FAKE_UUID) + class FakeServerCollection(object): def get(self, instance_id): @@ -1097,7 +1188,7 @@ class FakeEmptyServerCollection(object): class FakeNovaClient(object): - def __init__(self, collection): + def __init__(self, collection, *args, **kwargs): self.servers = collection @@ -1162,8 +1253,9 @@ class CallZoneMethodTest(test.TestCase): context = {} method = 'do_something' results = api.call_zone_method(context, method) - expected = [(1, 42)] - self.assertEqual(expected, results) + self.assertEqual(len(results), 2) + self.assertIn((1, 42), results) + self.assertIn((2, 42), results) def test_call_zone_method_not_present(self): context = {} -- cgit From 6cbbdb909443a33c2af8ddd73b861cd41201fa0b Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 8 Sep 2011 11:32:11 -0400 Subject: adding can_read_deleted back to db api --- nova/db/sqlalchemy/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 523258841..1730b4ddb 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1277,6 +1277,10 @@ def instance_get_all_by_filters(context, filters): changes_since = filters['changes-since'] query_prefix = query_prefix.\ filter(models.Instance.updated_at > changes_since) + else: + # filter out deleted instances if no changes-since filter provided + query_prefix = query_prefix.\ + filter_by(deleted=can_read_deleted(context)) if not context.is_admin: # If we're not admin context, add appropriate filter.. -- cgit From 763bf3f1282e3d9723a356d4014a9599601637eb Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 8 Sep 2011 11:43:43 -0500 Subject: Do not attempt to mount the swap VDI for file injection. --- nova/tests/api/openstack/test_servers.py | 2 +- nova/virt/xenapi/vmops.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 2ef687709..d063a60c2 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3615,7 +3615,7 @@ class TestGetKernelRamdiskFromImage(test.TestCase): self.assertRaises(exception.NotFound, self._get_k_r, image_meta) def test_ami_no_ramdisk(self): - """If an ami is missing a ramdisk, return kernel ID and None for + """If an ami is missing a ramdisk, return kernel ID and None for ramdisk ID """ image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c5f105f40..bb53a52bc 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -188,9 +188,16 @@ class VMOps(object): ramdisk = VMHelper.fetch_image(context, self._session, instance, instance.ramdisk_id, instance.user_id, instance.project_id, ImageType.RAMDISK)[0] - # Create the VM ref and attach the first disk - first_vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', - vdis[0]['vdi_uuid']) + + # NOTE(jk0): Since vdi_type may contain either 'os' or 'swap', we + # need to ensure that the 'swap' VDI is not chosen as the mount + # point for file injection. + first_vdi_ref = None + for vdi in vdis: + if vdi['vdi_type'] != 'swap': + # Create the VM ref and attach the first disk + first_vdi_ref = self._session.call_xenapi( + 'VDI.get_by_uuid', vdi['vdi_uuid']) vm_mode = instance.vm_mode and instance.vm_mode.lower() if vm_mode == 'pv': -- cgit From 3f6738b9f07640b0950793975cfc55e62aa3e1ad Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 8 Sep 2011 12:11:39 -0500 Subject: Use .get instead. --- 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 bb53a52bc..9c138ee41 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -194,7 +194,7 @@ class VMOps(object): # point for file injection. first_vdi_ref = None for vdi in vdis: - if vdi['vdi_type'] != 'swap': + if vdi.get('vdi_type') != 'swap': # Create the VM ref and attach the first disk first_vdi_ref = self._session.call_xenapi( 'VDI.get_by_uuid', vdi['vdi_uuid']) -- cgit From c8a48eec1fb9f205af5cef2b882fc171bcca4d57 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Thu, 8 Sep 2011 19:08:46 +0000 Subject: Add a NOTE() --- nova/scheduler/abstract_scheduler.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py index be6267d3a..1ba10c3a9 100644 --- a/nova/scheduler/abstract_scheduler.py +++ b/nova/scheduler/abstract_scheduler.py @@ -123,6 +123,14 @@ class AbstractScheduler(driver.Scheduler): except novaclient_exceptions.BadRequest, e: raise exception.NotAuthorized(_("Bad credentials attempting " "to talk to zone at %(url)s.") % locals()) + # NOTE(Vek): Novaclient has two different calling conventions + # for this call, depending on whether you're using + # 1.0 or 1.1 API: in 1.0, there's an ipgroups + # argument after flavor_id which isn't present in + # 1.1. To work around this, all the extra + # arguments are passed as keyword arguments + # (there's a reasonable default for ipgroups in the + # novaclient call). nova.servers.create(name, image_ref, flavor_id, meta=meta, files=files, zone_blob=child_blob, reservation_id=reservation_id) -- cgit From 4bf6508a026c62a7aa2423b1910c871ddc3f0916 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 8 Sep 2011 15:26:44 -0400 Subject: converting fix to just address ec2; updating test --- nova/api/ec2/cloud.py | 4 +++- nova/db/sqlalchemy/api.py | 4 ---- nova/tests/test_cloud.py | 4 +--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 049ca6f93..4f7030a5a 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1200,8 +1200,10 @@ class CloudController(object): instances.append(instance) else: try: + # always filter out deleted instances + search_opts['deleted'] = False instances = self.compute_api.get_all(context, - search_opts=search_opts) + search_opts=search_opts) except exception.NotFound: instances = [] for instance in instances: diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 1730b4ddb..523258841 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1277,10 +1277,6 @@ def instance_get_all_by_filters(context, filters): changes_since = filters['changes-since'] query_prefix = query_prefix.\ filter(models.Instance.updated_at > changes_since) - else: - # filter out deleted instances if no changes-since filter provided - query_prefix = query_prefix.\ - filter_by(deleted=can_read_deleted(context)) if not context.is_admin: # If we're not admin context, add appropriate filter.. diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 3fe6a9b42..7fe353b3d 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -486,11 +486,9 @@ class CloudTestCase(test.TestCase): inst2 = db.instance_create(self.context, args2) db.instance_destroy(self.context, inst1.id) result = self.cloud.describe_instances(self.context) + self.assertEqual(len(result['reservationSet']), 1) result1 = result['reservationSet'][0]['instancesSet'] self.assertEqual(result1[0]['instanceId'], - ec2utils.id_to_ec2_id(inst1.id)) - result2 = result['reservationSet'][1]['instancesSet'] - self.assertEqual(result2[0]['instanceId'], ec2utils.id_to_ec2_id(inst2.id)) def _block_device_mapping_create(self, instance_id, mappings): -- cgit From fe355a10ad0a215eb5295e46e6c106221972e7ed Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 8 Sep 2011 13:18:08 -0700 Subject: make check for fixed_ip association more defensive --- nova/api/openstack/contrib/floating_ips.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 9ceb5858f..d078b26c6 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -107,7 +107,7 @@ class FloatingIPController(object): context = req.environ['nova.context'] floating_ip = self.network_api.get_floating_ip(context, id) - if floating_ip['fixed_ip']: + if floating_ip.get('fixed_ip'): self.network_api.disassociate_floating_ip(context, floating_ip['address']) @@ -161,7 +161,7 @@ class Floating_ips(extensions.ExtensionDescriptor): raise webob.exc.HTTPBadRequest(explanation=msg) floating_ip = self.network_api.get_floating_ip_by_ip(context, address) - if floating_ip['fixed_ip']: + if floating_ip.get('fixed_ip'): self.network_api.disassociate_floating_ip(context, address) return webob.Response(status_int=202) -- cgit From aec647b3b42c4cd56a9509c2d1ac25ff12b0664e Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 8 Sep 2011 16:10:03 -0500 Subject: First pass at adding reboot_type to reboot codepath. --- nova/api/openstack/servers.py | 5 ++--- nova/compute/api.py | 5 +++-- nova/compute/manager.py | 4 ++-- nova/tests/api/openstack/test_servers.py | 2 +- nova/tests/test_compute.py | 15 ++++++++++++--- nova/tests/test_virt_drivers.py | 3 ++- nova/tests/test_vmwareapi.py | 3 ++- nova/virt/driver.py | 3 ++- nova/virt/fake.py | 2 +- nova/virt/hyperv.py | 2 +- nova/virt/vmwareapi_conn.py | 2 +- nova/virt/xenapi/vmops.py | 9 +++++++-- nova/virt/xenapi_conn.py | 4 ++-- 13 files changed, 38 insertions(+), 21 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index d084ac360..f5447edc5 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -334,9 +334,8 @@ class Controller(object): LOG.exception(msg) raise exc.HTTPBadRequest(explanation=msg) try: - # TODO(gundlach): pass reboot_type, support soft reboot in - # virt driver - self.compute_api.reboot(req.environ['nova.context'], id) + self.compute_api.reboot(req.environ['nova.context'], id, + reboot_type) except Exception, e: LOG.exception(_("Error in reboot %s"), e) raise exc.HTTPUnprocessableEntity() diff --git a/nova/compute/api.py b/nova/compute/api.py index 4e2944bb7..b0ea044c5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1042,13 +1042,14 @@ class API(base.Base): return recv_meta @scheduler_api.reroute_compute("reboot") - def reboot(self, context, instance_id): + def reboot(self, context, instance_id, reboot_type): """Reboot the given instance.""" self.update(context, instance_id, vm_state=vm_states.ACTIVE, task_state=task_states.REBOOTING) - self._cast_compute_message('reboot_instance', context, instance_id) + self._cast_compute_message('reboot_instance', context, instance_id, + reboot_type) @scheduler_api.reroute_compute("rebuild") def rebuild(self, context, instance_id, image_href, admin_password, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0477db745..0be12297f 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -579,7 +579,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def reboot_instance(self, context, instance_id): + def reboot_instance(self, context, instance_id, reboot_type="SOFT"): """Reboot an instance on this host.""" LOG.audit(_("Rebooting instance %s"), instance_id, context=context) context = context.elevated() @@ -601,7 +601,7 @@ class ComputeManager(manager.SchedulerDependentManager): context=context) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.reboot(instance_ref, network_info) + self.driver.reboot(instance_ref, network_info, reboot_type) current_power_state = self._get_power_state(context, instance_ref) self._instance_update(context, diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 2ef687709..d063a60c2 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3615,7 +3615,7 @@ class TestGetKernelRamdiskFromImage(test.TestCase): self.assertRaises(exception.NotFound, self._get_k_r, image_meta) def test_ami_no_ramdisk(self): - """If an ami is missing a ramdisk, return kernel ID and None for + """If an ami is missing a ramdisk, return kernel ID and None for ramdisk ID """ image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 65fdffbd6..4d463572b 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -300,11 +300,20 @@ class ComputeTestCase(test.TestCase): self.compute.resume_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) - def test_reboot(self): - """Ensure instance can be rebooted""" + def test_soft_reboot(self): + """Ensure instance can be soft rebooted""" instance_id = self._create_instance() + reboot_type = "SOFT" self.compute.run_instance(self.context, instance_id) - self.compute.reboot_instance(self.context, instance_id) + self.compute.reboot_instance(self.context, instance_id, reboot_type) + self.compute.terminate_instance(self.context, instance_id) + + def test_hard_reboot(self): + """Ensure instance can be hard rebooted""" + instance_id = self._create_instance() + reboot_type = "HARD" + self.compute.run_instance(self.context, instance_id) + self.compute.reboot_instance(self.context, instance_id, reboot_type) self.compute.terminate_instance(self.context, instance_id) def test_set_admin_password(self): diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py index 480247c91..440d3401b 100644 --- a/nova/tests/test_virt_drivers.py +++ b/nova/tests/test_virt_drivers.py @@ -103,8 +103,9 @@ class _VirtDriverTestCase(test.TestCase): def test_reboot(self): instance_ref = test_utils.get_test_instance() network_info = test_utils.get_test_network_info() + reboot_type = "SOFT" self.connection.spawn(self.ctxt, instance_ref, network_info) - self.connection.reboot(instance_ref, network_info) + self.connection.reboot(instance_ref, network_info, reboot_type) @catch_notimplementederror def test_get_host_ip_addr(self): diff --git a/nova/tests/test_vmwareapi.py b/nova/tests/test_vmwareapi.py index 06daf46e8..e6da1690f 100644 --- a/nova/tests/test_vmwareapi.py +++ b/nova/tests/test_vmwareapi.py @@ -170,7 +170,8 @@ class VMWareAPIVMTestCase(test.TestCase): self._create_vm() info = self.conn.get_info(1) self._check_vm_info(info, power_state.RUNNING) - self.conn.reboot(self.instance, self.network_info) + reboot_type = "SOFT" + self.conn.reboot(self.instance, self.network_info, reboot_type) info = self.conn.get_info(1) self._check_vm_info(info, power_state.RUNNING) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index d05b51bd9..301346c6b 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -165,12 +165,13 @@ class ComputeDriver(object): # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() - def reboot(self, instance, network_info): + def reboot(self, instance, network_info, reboot_type): """Reboot the specified instance. :param instance: Instance object as returned by DB layer. :param network_info: :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + :param reboot_type: Either a HARD or SOFT reboot """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index d5e2bf31b..3596d8353 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -103,7 +103,7 @@ class FakeConnection(driver.ComputeDriver): if not instance['name'] in self.instances: raise exception.InstanceNotRunning() - def reboot(self, instance, network_info): + def reboot(self, instance, network_info, reboot_type): pass def get_host_ip_addr(self): diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 03a78db1f..76925b405 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -367,7 +367,7 @@ class HyperVConnection(driver.ComputeDriver): wmi_obj.Properties_.Item(prop).Value return newinst - def reboot(self, instance, network_info): + def reboot(self, instance, network_info, reboot_type): """Reboot the specified instance.""" vm = self._lookup(instance.name) if vm is None: diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index 243ee64f5..fa89a8f45 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -133,7 +133,7 @@ class VMWareESXConnection(driver.ComputeDriver): """Create snapshot from a running VM instance.""" self._vmops.snapshot(context, instance, name) - def reboot(self, instance, network_info): + def reboot(self, instance, network_info, reboot_type): """Reboot VM instance.""" self._vmops.reboot(instance, network_info) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c5f105f40..b7d6a40b4 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -617,10 +617,15 @@ class VMOps(object): str(new_disk_size)) LOG.debug(_("Resize instance %s complete") % (instance.name)) - def reboot(self, instance): + def reboot(self, instance, reboot_type): """Reboot VM instance.""" vm_ref = self._get_vm_opaque_ref(instance) - task = self._session.call_xenapi('Async.VM.clean_reboot', vm_ref) + + if reboot_type == "HARD": + task = self._session.call_xenapi('Async.VM.hard_reboot', vm_ref) + else: + task = self._session.call_xenapi('Async.VM.clean_reboot', vm_ref) + self._session.wait_for_task(task, instance.id) def get_agent_version(self, instance, timeout=None): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 0d23e7689..f6dbc19f8 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -203,9 +203,9 @@ class XenAPIConnection(driver.ComputeDriver): """ Create snapshot from a running VM instance """ self._vmops.snapshot(context, instance, image_id) - def reboot(self, instance, network_info): + def reboot(self, instance, network_info, reboot_type): """Reboot VM instance""" - self._vmops.reboot(instance) + self._vmops.reboot(instance, reboot_type) def set_admin_password(self, instance, new_pass): """Set the root/admin password on the VM instance""" -- cgit From 4ffe41dffaab64c96649bfc3236e5ba6bb9d8b37 Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Fri, 9 Sep 2011 09:27:26 +0200 Subject: Open Essex (switch version to 2012.1) --- nova/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/version.py b/nova/version.py index 1f8d08e8c..06810df46 100644 --- a/nova/version.py +++ b/nova/version.py @@ -22,7 +22,7 @@ except ImportError: 'revno': 0} -NOVA_VERSION = ['2011', '3'] +NOVA_VERSION = ['2012', '1'] YEAR, COUNT = NOVA_VERSION FINAL = False # This becomes true at Release Candidate time -- cgit From 7352e3e1eb7a4d29b556492a208e80439828f211 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 9 Sep 2011 09:48:38 -0400 Subject: removing key_name and config_drive from non-detailed server entity --- nova/api/openstack/views/servers.py | 7 ++----- nova/tests/api/openstack/test_servers.py | 4 ---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index ac09b5864..473dc9e7e 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -145,6 +145,8 @@ class ViewBuilderV11(ViewBuilder): response['server']['accessIPv4'] = inst.get('access_ip_v4') or "" response['server']['accessIPv6'] = inst.get('access_ip_v6') or "" + response['server']['key_name'] = inst.get('key_name', '') + response['server']['config_drive'] = inst.get('config_drive') return response @@ -185,8 +187,6 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) response['uuid'] = inst['uuid'] - response['key_name'] = inst.get('key_name', '') - self._build_config_drive(response, inst) def _build_links(self, response, inst): href = self.generate_href(inst["id"]) @@ -205,9 +205,6 @@ class ViewBuilderV11(ViewBuilder): response["links"] = links - def _build_config_drive(self, response, inst): - response['config_drive'] = inst.get('config_drive') - def generate_href(self, server_id): """Create an url that refers to a specific server id.""" return os.path.join(self.base_url, self.project_id, diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index d063a60c2..f0a1c5ce5 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3715,7 +3715,6 @@ class ServersViewBuilderV11Test(test.TestCase): "id": 1, "uuid": self.instance['uuid'], "name": "test_server", - "key_name": '', "links": [ { "rel": "self", @@ -3726,7 +3725,6 @@ class ServersViewBuilderV11Test(test.TestCase): "href": "http://localhost/servers/1", }, ], - "config_drive": None, } } @@ -3739,8 +3737,6 @@ class ServersViewBuilderV11Test(test.TestCase): "id": 1, "uuid": self.instance['uuid'], "name": "test_server", - "key_name": '', - "config_drive": None, "links": [ { "rel": "self", -- cgit From 9dd2d6c49a36c1834d0ef842c47d2ef400642ff2 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 9 Sep 2011 08:59:02 -0700 Subject: remove sudo from qemu-img commands --- nova/virt/libvirt/connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 363a20ed0..19cef5ad7 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1696,7 +1696,7 @@ class LibvirtConnection(driver.ComputeDriver): base = os.path.basename(info['path']) # Get image type and create empty disk image. instance_disk = os.path.join(instance_dir, base) - utils.execute('sudo', 'qemu-img', 'create', '-f', info['type'], + utils.execute('qemu-img', 'create', '-f', info['type'], instance_disk, info['local_gb']) # if image has kernel and ramdisk, just download @@ -1788,7 +1788,7 @@ class LibvirtConnection(driver.ComputeDriver): if disk_type == 'raw': size = int(os.path.getsize(path)) else: - out, err = utils.execute('sudo', 'qemu-img', 'info', path) + out, err = utils.execute('qemu-img', 'info', path) size = [i.split('(')[1].split()[0] for i in out.split('\n') if i.strip().find('virtual size') >= 0] size = int(size[0]) -- cgit From d05d4e77df0bdfd2b802186762391d7f91361701 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Fri, 9 Sep 2011 14:35:38 -0500 Subject: Add comment to document why random.shuffle() works --- nova/scheduler/abstract_scheduler.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py index b4c2bf4f1..e4f615b94 100644 --- a/nova/scheduler/abstract_scheduler.py +++ b/nova/scheduler/abstract_scheduler.py @@ -296,6 +296,13 @@ class AbstractScheduler(driver.Scheduler): "child_blob": weighting["blob"]} weighted_hosts.append(host_dict) if FLAGS.spread_first: + # NOTE(Vek): If all the weights are unique, then the sort + # below undoes this shuffle; however, if + # several responses from several zones have the + # same weight, then this shuffle serves to + # break up the monolithic blocks and cause the + # instances to be uniformly spread across the + # zones. random.shuffle(weighted_hosts) weighted_hosts.sort(key=operator.itemgetter('weight')) return weighted_hosts -- cgit From c0700ea7bbb4d860610b71e635b8dbde19157e85 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Fri, 9 Sep 2011 20:27:22 +0000 Subject: don't need random in abstract_scheduler.py anymore... --- nova/scheduler/abstract_scheduler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py index 76329bbc6..6e8c7d715 100644 --- a/nova/scheduler/abstract_scheduler.py +++ b/nova/scheduler/abstract_scheduler.py @@ -22,7 +22,6 @@ behavior is to simply select all hosts and weight them the same. import json import operator -import random import M2Crypto -- cgit