From 639717b1eadb769f1d77a4ddcdb6618da4defbea Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 30 Jun 2011 10:06:06 -0400 Subject: added FlavorRef exception handling on create instance --- nova/api/openstack/create_instance_helper.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 436e524c1..94ae3bb85 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -143,6 +143,10 @@ class CreateInstanceHelper(object): except exception.ImageNotFound as error: msg = _("Can not find requested image") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + except exception.FlavorNotFound as error: + msg = _("Invalid flavorRef provided.") + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + # Let the caller deal with unhandled exceptions. -- cgit From 01b9d211e606ee0be221b27edae8aab1d35096ff Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Jul 2011 11:51:46 -0700 Subject: First round of changes for ha-flatdhcp. * added 'host' column to fixed_ips to allow associating with a host * added 'multi_host' column to network for multi_host possibility * moved extra db access from linux_net to manager * added host parameter to network calls --- nova/db/api.py | 19 ++++-- nova/db/sqlalchemy/api.py | 29 ++++++--- .../migrate_repo/versions/032_ha_network.py | 37 +++++++++++ nova/db/sqlalchemy/models.py | 1 + nova/exception.py | 5 ++ nova/network/api.py | 13 +++- nova/network/linux_net.py | 15 ++--- nova/network/manager.py | 73 +++++++++++++++------- 8 files changed, 143 insertions(+), 49 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/032_ha_network.py (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index b7c5700e5..febe33374 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -332,13 +332,15 @@ def fixed_ip_associate(context, address, instance_id): return IMPL.fixed_ip_associate(context, address, instance_id) -def fixed_ip_associate_pool(context, network_id, instance_id): - """Find free ip in network and associate it to instance. +def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): + """Find free ip in network and associate it to instance or host. + If reserved is true, it will pull the ip from the reserved pool. Raises if one is not available. """ - return IMPL.fixed_ip_associate_pool(context, network_id, instance_id) + return IMPL.fixed_ip_associate_pool(context, network_id, + instance_id, host, reserved) def fixed_ip_create(context, values): @@ -361,9 +363,9 @@ def fixed_ip_get_all(context): return IMPL.fixed_ip_get_all(context) -def fixed_ip_get_all_by_host(context, host): - """Get all defined fixed ips used by a host.""" - return IMPL.fixed_ip_get_all_by_host(context, host) +def fixed_ip_get_all_by_instance_host(context, host): + """Get all allocated fixed ips filtered by instance host.""" + return IMPL.fixed_ip_get_all_instance_by_host(context, host) def fixed_ip_get_by_address(context, address): @@ -376,6 +378,11 @@ def fixed_ip_get_by_instance(context, instance_id): return IMPL.fixed_ip_get_by_instance(context, instance_id) +def fixed_ip_get_by_network_host(context, network_id, host): + """Get fixed ip for a host in a network.""" + return IMPL.fixed_ip_get_by_network_host(context, network_id, host) + + def fixed_ip_get_by_virtual_interface(context, vif_id): """Get fixed ips by virtual interface or raise if none exist.""" return IMPL.fixed_ip_get_by_virtual_interface(context, vif_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ffd009513..6db142276 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -18,7 +18,6 @@ """ Implementation of SQLAlchemy backend. """ -import traceback import warnings from nova import db @@ -33,7 +32,6 @@ from sqlalchemy import or_ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload_all -from sqlalchemy.sql import exists from sqlalchemy.sql import func from sqlalchemy.sql.expression import literal_column @@ -657,7 +655,7 @@ def fixed_ip_associate(context, address, instance_id): @require_admin_context -def fixed_ip_associate_pool(context, network_id, instance_id): +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, @@ -677,9 +675,12 @@ def fixed_ip_associate_pool(context, network_id, instance_id): fixed_ip_ref.network = network_get(context, network_id, session=session) - fixed_ip_ref.instance = instance_get(context, - instance_id, - session=session) + if instance_id: + fixed_ip_ref.instance = instance_get(context, + instance_id, + session=session) + if host: + fixed_ip_ref.host = host session.add(fixed_ip_ref) return fixed_ip_ref['address'] @@ -735,7 +736,7 @@ def fixed_ip_get_all(context, session=None): @require_admin_context -def fixed_ip_get_all_by_host(context, host=None): +def fixed_ip_get_all_by_instance_host(context, host=None): session = get_session() result = session.query(models.FixedIp).\ @@ -784,6 +785,20 @@ def fixed_ip_get_by_instance(context, instance_id): return rv +@require_context +def fixed_ip_get_by_network_host(context, network_id, host): + session = get_session() + rv = session.query(models.FixedIp).\ + filter_by(network_id=network_id).\ + filter_by(host=host).\ + filter_by(deleted=False).\ + all() + if not rv: + raise exception.FixedIpNotFoundForNetworkHost(network_id=network_id, + host=host) + return rv + + @require_context def fixed_ip_get_by_virtual_interface(context, vif_id): session = get_session() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/032_ha_network.py b/nova/db/sqlalchemy/migrate_repo/versions/032_ha_network.py new file mode 100644 index 000000000..316c36cbc --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/032_ha_network.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 MORITA Kazutaka. +# 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 Column, Table, MetaData, Boolean, String + +meta = MetaData() + +fixed_ips_host = Column('host', String(255)) + +networks_multi_host = Column('multi_host', Boolean, default=False) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + + # Add columns to existing tables + fixed_ips = Table('fixed_ips', meta, autoload=True) + fixed_ips.create_column(fixed_ips_host) + + networks = Table('networks', meta, autoload=True) + networks.create_column(networks_multi_host) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index d29d3d6f1..8c86870f0 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -543,6 +543,7 @@ class Network(BASE, NovaBase): injected = Column(Boolean, default=False) cidr = Column(String(255), unique=True) cidr_v6 = Column(String(255), unique=True) + multi_host = Column(Boolean, default=False) gateway_v6 = Column(String(255)) netmask_v6 = Column(String(255)) diff --git a/nova/exception.py b/nova/exception.py index a6776b64f..29a209c3e 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -377,6 +377,11 @@ class FixedIpNotFoundForInstance(FixedIpNotFound): message = _("Instance %(instance_id)s has zero fixed ips.") +class FixedIpNotFoundForNetworkHost(FixedIpNotFound): + message = _("Network host %(host)s has zero fixed ips " + "in network %(network_id)s.") + + class FixedIpNotFoundForVirtualInterface(FixedIpNotFound): message = _("Virtual interface %(vif_id)s has zero associated fixed ips.") diff --git a/nova/network/api.py b/nova/network/api.py index b2b96082b..c57394204 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -18,7 +18,6 @@ """Handles all requests relating to instances (guest vms).""" -from nova import db from nova import exception from nova import flags from nova import log as logging @@ -105,7 +104,10 @@ class API(base.Base): '(%(project)s)') % {'address': floating_ip['address'], 'project': context.project_id}) - host = fixed_ip['network']['host'] + if fixed_ip['network']['multi_gateway']: + host = fixed_ip['instance']['host'] + else: + host = fixed_ip['network']['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.network_topic, host), {'method': 'associate_floating_ip', @@ -120,7 +122,10 @@ class API(base.Base): return if not floating_ip.get('fixed_ip'): raise exception.ApiError('Address is not associated.') - host = floating_ip['fixed_ip']['network']['host'] + if floating_ip['fixed_ip']['network']['multi_gateway']: + host = floating_ip['fixed_ip']['instance']['host'] + else: + host = floating_ip['fixed_ip']['network']['host'] rpc.call(context, self.db.queue_get_for(context, FLAGS.network_topic, host), {'method': 'disassociate_floating_ip', @@ -134,7 +139,9 @@ class API(base.Base): args = kwargs args['instance_id'] = instance['id'] args['project_id'] = instance['project_id'] + args['host'] = instance['host'] args['instance_type_id'] = instance['instance_type_id'] + return rpc.call(context, FLAGS.network_topic, {'method': 'allocate_for_instance', 'args': args}) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 283a5aca1..8d2bc5c0b 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -573,18 +573,16 @@ def get_dhcp_hosts(context, network_id): # configuration options (like dchp-range, vlan, ...) # aren't reloaded. @utils.synchronized('dnsmasq_start') -def update_dhcp(context, network_id): +def update_dhcp(context, network_ref): """(Re)starts a dnsmasq server for a given network. If a dnsmasq instance is already running then send a HUP signal causing it to reload, otherwise spawn a new instance. """ - network_ref = db.network_get(context, network_id) - conffile = _dhcp_file(network_ref['bridge'], 'conf') with open(conffile, 'w') as f: - f.write(get_dhcp_hosts(context, network_id)) + f.write(get_dhcp_hosts(context, network_ref['id'])) # Make sure dnsmasq can actually read it (it setuid()s to "nobody") os.chmod(conffile, 0644) @@ -612,9 +610,7 @@ def update_dhcp(context, network_id): @utils.synchronized('radvd_start') -def update_ra(context, network_id): - network_ref = db.network_get(context, network_id) - +def update_ra(context, network_ref): conffile = _ra_file(network_ref['bridge'], 'conf') with open(conffile, 'w') as f: conf_str = """ @@ -650,9 +646,6 @@ interface %s LOG.debug(_('Pid %d is stale, relaunching radvd'), pid) command = _ra_cmd(network_ref) _execute(*command) - db.network_update(context, network_id, - {'gateway_v6': - utils.get_my_linklocal(network_ref['bridge'])}) def _host_lease(fixed_ip_ref): @@ -704,7 +697,7 @@ def _dnsmasq_cmd(net): '--conf-file=%s' % FLAGS.dnsmasq_config_file, '--domain=%s' % FLAGS.dhcp_domain, '--pid-file=%s' % _dhcp_file(net['bridge'], 'pid'), - '--listen-address=%s' % net['gateway'], + '--listen-address=%s' % net['dhcp_listen'], '--except-interface=lo', '--dhcp-range=%s,static,120s' % net['dhcp_start'], '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(net['cidr'])), diff --git a/nova/network/manager.py b/nova/network/manager.py index d42bc8c4e..f60763eba 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -124,15 +124,17 @@ class RPCAllocateFixedIP(object): used since they share code to RPC.call allocate_fixed_ip on the correct network host to configure dnsmasq """ - def _allocate_fixed_ips(self, context, instance_id, networks): + def _allocate_fixed_ips(self, context, instance_id, host, networks): """Calls allocate_fixed_ip once for each network.""" green_pool = greenpool.GreenPool() for network in networks: - if network['host'] != self.host: + # NOTE(vish): if we are multi_gateway pass to the specified host + if not network['multi_gateway']: + host = network['host'] + if host != self.host: # need to call allocate_fixed_ip to correct network host - topic = self.db.queue_get_for(context, FLAGS.network_topic, - network['host']) + topic = self.db.queue_get_for(context, FLAGS.network_topic, host) args = {} args['instance_id'] = instance_id args['network_id'] = network['id'] @@ -298,6 +300,21 @@ class NetworkManager(manager.SchedulerDependentManager): super(NetworkManager, self).__init__(service_name='network', *args, **kwargs) + def _update_dchp(self, context, network_ref): + """Sets the listen address before sending update to the driver.""" + network_ref['dhcp_listen'] = self._get_dhcp_ip() + return self.driver.update_dhcp(context, network_ref) + + def _get_dhcp_ip(self, context, network_ref): + """Get the proper dhcp address to listen on. + + If it is a multi_host network, get the ip assigned to this host, + otherwise, assume that dhcp is listening on the gateway.""" + if network_ref['multi_host']: + return self.db.network_get_host_ip(context, FLAGS.host) + else: + return network_ref['gateway'] + def init_host(self): """Do any initialization that needs to be run if this is a standalone service. @@ -360,6 +377,7 @@ class NetworkManager(manager.SchedulerDependentManager): rpc.called by network_api """ instance_id = kwargs.pop('instance_id') + host = kwargs.pop('host') project_id = kwargs.pop('project_id') type_id = kwargs.pop('instance_type_id') admin_context = context.elevated() @@ -368,7 +386,7 @@ class NetworkManager(manager.SchedulerDependentManager): networks = self._get_networks_for_instance(admin_context, instance_id, project_id) self._allocate_mac_addresses(context, instance_id, networks) - self._allocate_fixed_ips(admin_context, instance_id, networks) + self._allocate_fixed_ips(admin_context, instance_id, host, networks) return self.get_instance_nw_info(context, instance_id, type_id) def deallocate_for_instance(self, context, **kwargs): @@ -475,10 +493,10 @@ class NetworkManager(manager.SchedulerDependentManager): random.randint(0x00, 0xff)] return ':'.join(map(lambda x: "%02x" % x, mac)) - def add_fixed_ip_to_instance(self, context, instance_id, network_id): + def add_fixed_ip_to_instance(self, context, instance_id, host, network_id): """Adds a fixed ip to an instance from specified network.""" networks = [self.db.network_get(context, network_id)] - self._allocate_fixed_ips(context, instance_id, networks) + self._allocate_fixed_ips(context, instance_id, host, networks) def allocate_fixed_ip(self, context, instance_id, network, **kwargs): """Gets a fixed ip from the pool.""" @@ -540,8 +558,9 @@ class NetworkManager(manager.SchedulerDependentManager): # means there will stale entries in the conf file # the code below will update the file if necessary if FLAGS.update_dhcp_on_disassociate: - network = self.db.fixed_ip_get_network(context, address) - self.driver.update_dhcp(context, network['id']) + network_ref = self.db.fixed_ip_get_network(context, address) + network_ref['dhcp_listen'] = self._get_dhcp_ip(context, network_ref) + self._update_dhcp(context, network_ref) def create_networks(self, context, label, cidr, num_networks, network_size, cidr_v6, gateway_v6, bridge, @@ -637,7 +656,7 @@ class NetworkManager(manager.SchedulerDependentManager): 'address': address, 'reserved': reserved}) - def _allocate_fixed_ips(self, context, instance_id, networks): + def _allocate_fixed_ips(self, context, instance_id, host, networks): """Calls allocate_fixed_ip once for each network.""" raise NotImplementedError() @@ -684,7 +703,7 @@ class FlatManager(NetworkManager): timeout_fixed_ips = False - def _allocate_fixed_ips(self, context, instance_id, networks): + def _allocate_fixed_ips(self, context, instance_id, host, networks): """Calls allocate_fixed_ip once for each network.""" for network in networks: self.allocate_fixed_ip(context, instance_id, network) @@ -738,8 +757,9 @@ class FlatDHCPManager(FloatingIP, RPCAllocateFixedIP, NetworkManager): """ networks = db.network_get_all_by_instance(context, instance_id) for network in networks: - self.driver.ensure_bridge(network['bridge'], - network['bridge_interface']) + if not network['multi_host']: + self.driver.ensure_bridge(network['bridge'], + network['bridge_interface']) def allocate_fixed_ip(self, context, instance_id, network): """Allocate flat_network fixed_ip, then setup dhcp for this network.""" @@ -747,7 +767,7 @@ class FlatDHCPManager(FloatingIP, RPCAllocateFixedIP, NetworkManager): instance_id, network) if not FLAGS.fake_network: - self.driver.update_dhcp(context, network['id']) + self._update_dhcp(context, network) def _on_set_network_host(self, context, network_id): """Called when this host becomes the host for a project.""" @@ -759,9 +779,13 @@ class FlatDHCPManager(FloatingIP, RPCAllocateFixedIP, NetworkManager): network['bridge_interface'], network) if not FLAGS.fake_network: - self.driver.update_dhcp(context, network_id) + network_ref = self.db.network_get(context, network_id) + self._update_dhcp(context, network_ref) if(FLAGS.use_ipv6): - self.driver.update_ra(context, network_id) + self.driver.update_ra(context, network_ref) + gateway = utils.get_my_linklocal(network_ref['bridge']) + self.db.network_update(context, network_id, + {'gateway_v6': gateway}) class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): @@ -810,7 +834,7 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): 'virtual_interface_id': vif['id']} self.db.fixed_ip_update(context, address, values) if not FLAGS.fake_network: - self.driver.update_dhcp(context, network['id']) + self._update_dhcp(context, network) def add_network_to_project(self, context, project_id): """Force adds another network to a project.""" @@ -822,9 +846,10 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): """ networks = self.db.network_get_all_by_instance(context, instance_id) for network in networks: - self.driver.ensure_vlan_bridge(network['vlan'], - network['bridge'], - network['bridge_interface']) + if not network['multi_host']: + self.driver.ensure_vlan_bridge(network['vlan'], + network['bridge'], + network['bridge_interface']) def _get_networks_for_instance(self, context, instance_id, project_id): """Determine which networks an instance should connect to.""" @@ -874,9 +899,13 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): network['vpn_public_port'], network['vpn_private_address']) if not FLAGS.fake_network: - self.driver.update_dhcp(context, network_id) + network_ref = self.db.network_get(context, network_id) + self._update_dhcp(context, network_ref) if(FLAGS.use_ipv6): - self.driver.update_ra(context, network_id) + self.driver.update_ra(context, network_ref) + gateway = utils.get_my_linklocal(network_ref['bridge']) + self.db.network_update(context, network_id, + {'gateway_v6': gateway}) @property def _bottom_reserved_ips(self): -- cgit From 9a8254ef2751e1b7502107a9c6afe05ea1e2efd4 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Jul 2011 12:00:11 -0700 Subject: it is multi_host not multi_gateway --- nova/network/api.py | 6 ++++-- nova/network/manager.py | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/network/api.py b/nova/network/api.py index c57394204..c0f0fb8eb 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -104,7 +104,8 @@ class API(base.Base): '(%(project)s)') % {'address': floating_ip['address'], 'project': context.project_id}) - if fixed_ip['network']['multi_gateway']: + # NOTE(vish): if we are multi_host, send to the instances host + if fixed_ip['network']['multi_host']: host = fixed_ip['instance']['host'] else: host = fixed_ip['network']['host'] @@ -122,7 +123,8 @@ class API(base.Base): return if not floating_ip.get('fixed_ip'): raise exception.ApiError('Address is not associated.') - if floating_ip['fixed_ip']['network']['multi_gateway']: + # NOTE(vish): if we are multi_host, send to the instances host + if floating_ip['fixed_ip']['network']['multi_host']: host = floating_ip['fixed_ip']['instance']['host'] else: host = floating_ip['fixed_ip']['network']['host'] diff --git a/nova/network/manager.py b/nova/network/manager.py index f60763eba..84d2cbbcb 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -49,7 +49,6 @@ import datetime import math import netaddr import socket -import pickle from eventlet import greenpool from nova import context @@ -129,8 +128,8 @@ class RPCAllocateFixedIP(object): green_pool = greenpool.GreenPool() for network in networks: - # NOTE(vish): if we are multi_gateway pass to the specified host - if not network['multi_gateway']: + # NOTE(vish): if we are not multi_host pass to the network host + if not network['multi_host']: host = network['host'] if host != self.host: # need to call allocate_fixed_ip to correct network host -- cgit From 8d2f3f26e8089020616312334689f1c594a67b4f Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Jul 2011 12:16:46 -0700 Subject: make sure to filter out ips associated by host and add some sync for allocating ip to host --- nova/db/sqlalchemy/api.py | 1 + nova/network/manager.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 6db142276..7065de00f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -665,6 +665,7 @@ def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): filter_by(reserved=False).\ filter_by(deleted=False).\ filter_by(instance=None).\ + filter_by(host=None).\ with_lockmode('update').\ first() # NOTE(vish): if with_lockmode isn't supported, as in sqlite, diff --git a/nova/network/manager.py b/nova/network/manager.py index 84d2cbbcb..55f81da9e 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -310,7 +310,18 @@ class NetworkManager(manager.SchedulerDependentManager): If it is a multi_host network, get the ip assigned to this host, otherwise, assume that dhcp is listening on the gateway.""" if network_ref['multi_host']: - return self.db.network_get_host_ip(context, FLAGS.host) + + @utils.synchronized('get_dhcp') + def _sync_get_dhcp_ip(): + try: + return self.db.fixed_ip_get_by_network_host(context, + FLAGS.host) + except exception.FixedIpNotFoundForNetworkHost: + return self.db.fixed_ip_associate_pool(context.elevated(), + network_ref['id'], + host=FLAGS.host) + return _sync_get_dhcp_ip() + else: return network_ref['gateway'] -- cgit From 9b5adcbe92a4f7e0f9b1592be123c58f743def34 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Jul 2011 15:55:16 -0700 Subject: pass in dhcp server address, fix a bunch of bugs --- nova/compute/manager.py | 17 ++++++++------- nova/db/api.py | 2 +- nova/db/sqlalchemy/api.py | 3 ++- nova/db/sqlalchemy/models.py | 1 + nova/network/linux_net.py | 4 ++-- nova/network/manager.py | 47 ++++++++++++++++++++++++++++------------- nova/virt/libvirt/connection.py | 3 +-- 7 files changed, 48 insertions(+), 29 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index bbbddde0a..b198adc97 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -77,8 +77,6 @@ flags.DEFINE_integer('live_migration_retry_count', 30, flags.DEFINE_integer("rescue_timeout", 0, "Automatically unrescue an instance after N seconds." " Set to 0 to disable.") -flags.DEFINE_bool('auto_assign_floating_ip', False, - 'Autoassigning floating ip to VM') flags.DEFINE_integer('host_state_interval', 120, 'Interval in seconds for querying the host status') @@ -268,16 +266,19 @@ class ComputeManager(manager.SchedulerDependentManager): """Launch a new instance with specified options.""" context = context.elevated() instance = self.db.instance_get(context, instance_id) - instance.injected_files = kwargs.get('injected_files', []) - instance.admin_pass = kwargs.get('admin_password', None) if instance['name'] in self.driver.list_instances(): raise exception.Error(_("Instance has already been created")) LOG.audit(_("instance %s: starting..."), instance_id, context=context) - self.db.instance_update(context, - instance_id, - {'host': self.host, 'launched_on': self.host}) - + updates = {} + updates['host'] = self.host + updates['launched_on'] = self.host + # 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, diff --git a/nova/db/api.py b/nova/db/api.py index febe33374..ee4cbad57 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -340,7 +340,7 @@ def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): """ return IMPL.fixed_ip_associate_pool(context, network_id, - instance_id, host, reserved) + instance_id, host) def fixed_ip_create(context, values): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7065de00f..9c138d2fd 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -683,6 +683,7 @@ def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): if host: fixed_ip_ref.host = host session.add(fixed_ip_ref) + LOG.warn("gave address %s to %s, %s", fixed_ip_ref['address'], instance_id, host) return fixed_ip_ref['address'] @@ -793,7 +794,7 @@ def fixed_ip_get_by_network_host(context, network_id, host): filter_by(network_id=network_id).\ filter_by(host=host).\ filter_by(deleted=False).\ - all() + first() if not rv: raise exception.FixedIpNotFoundForNetworkHost(network_id=network_id, host=host) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 8c86870f0..639941dc8 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -602,6 +602,7 @@ class FixedIp(BASE, NovaBase): # leased means dhcp bridge has leased the ip leased = Column(Boolean, default=False) reserved = Column(Boolean, default=False) + host = Column(String(255)) class FloatingIp(BASE, NovaBase): diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 8d2bc5c0b..0a7a62510 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -497,7 +497,7 @@ def ensure_bridge(bridge, interface, net_attrs=None): suffix = net_attrs['cidr'].rpartition('/')[2] out, err = _execute('sudo', 'ip', 'addr', 'add', '%s/%s' % - (net_attrs['gateway'], suffix), + (net_attrs['dhcp_server'], suffix), 'brd', net_attrs['broadcast'], 'dev', @@ -697,7 +697,7 @@ def _dnsmasq_cmd(net): '--conf-file=%s' % FLAGS.dnsmasq_config_file, '--domain=%s' % FLAGS.dhcp_domain, '--pid-file=%s' % _dhcp_file(net['bridge'], 'pid'), - '--listen-address=%s' % net['dhcp_listen'], + '--listen-address=%s' % net['dhcp_server'], '--except-interface=lo', '--dhcp-range=%s,static,120s' % net['dhcp_start'], '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(net['cidr'])), diff --git a/nova/network/manager.py b/nova/network/manager.py index 55f81da9e..ce631ce75 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -103,7 +103,8 @@ flags.DEFINE_integer('fixed_ip_disassociate_timeout', 600, 'Seconds after which a deallocated ip is disassociated') flags.DEFINE_integer('create_unique_mac_address_attempts', 5, 'Number of attempts to create unique mac address') - +flags.DEFINE_bool('auto_assign_floating_ip', False, + 'Autoassigning floating ip to VM') flags.DEFINE_bool('use_ipv6', False, 'use the ipv6') flags.DEFINE_string('network_host', socket.gethostname(), @@ -192,7 +193,7 @@ class FloatingIP(object): # which is currently the NetworkManager version # do this first so fixed ip is already allocated ips = super(FloatingIP, self).allocate_for_instance(context, **kwargs) - if hasattr(FLAGS, 'auto_assign_floating_ip'): + if FLAGS.auto_assign_floating_ip: # allocate a floating ip (public_ip is just the address string) public_ip = self.allocate_floating_ip(context, project_id) # set auto_assigned column to true for the floating ip @@ -299,9 +300,9 @@ class NetworkManager(manager.SchedulerDependentManager): super(NetworkManager, self).__init__(service_name='network', *args, **kwargs) - def _update_dchp(self, context, network_ref): + def _update_dhcp(self, context, network_ref): """Sets the listen address before sending update to the driver.""" - network_ref['dhcp_listen'] = self._get_dhcp_ip() + network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) return self.driver.update_dhcp(context, network_ref) def _get_dhcp_ip(self, context, network_ref): @@ -313,13 +314,18 @@ class NetworkManager(manager.SchedulerDependentManager): @utils.synchronized('get_dhcp') def _sync_get_dhcp_ip(): + network_id = network_ref['id'] try: - return self.db.fixed_ip_get_by_network_host(context, - FLAGS.host) + fip = self.db.fixed_ip_get_by_network_host(context, + network_id, + self.host) + return fip['address'] except exception.FixedIpNotFoundForNetworkHost: - return self.db.fixed_ip_associate_pool(context.elevated(), - network_ref['id'], - host=FLAGS.host) + addr = self.db.fixed_ip_associate_pool(context.elevated(), + network_id, + host=self.host) + return addr + return _sync_get_dhcp_ip() else: @@ -334,6 +340,11 @@ class NetworkManager(manager.SchedulerDependentManager): ctxt = context.get_admin_context() for network in self.db.network_get_all_by_host(ctxt, self.host): self._on_set_network_host(ctxt, network['id']) + for network in self.db.network_get_all(ctxt): + # NOTE(vish): multi_host networks need to be set on all boxes + if network['multi_host']: + self._on_set_network_host(ctxt, network['id']) + def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval.""" @@ -349,7 +360,7 @@ class NetworkManager(manager.SchedulerDependentManager): LOG.debug(_('Dissassociated %s stale fixed ip(s)'), num) # setup any new networks which have been created - self.set_network_hosts(context) + #self.set_network_hosts(context) def set_network_host(self, context, network_id): """Safely sets the host of the network.""" @@ -378,8 +389,8 @@ class NetworkManager(manager.SchedulerDependentManager): networks = self.db.network_get_all(context) # return only networks which are not vlan networks and have host set - return [network for network in networks if - not network['vlan'] and network['host']] + return [network for network in networks if not network['vlan'] + and network['host'] or network['multi_host']] def allocate_for_instance(self, context, **kwargs): """Handles allocating the various network resources for an instance. @@ -395,6 +406,7 @@ class NetworkManager(manager.SchedulerDependentManager): context=context) networks = self._get_networks_for_instance(admin_context, instance_id, project_id) + LOG.warn(networks) self._allocate_mac_addresses(context, instance_id, networks) self._allocate_fixed_ips(admin_context, instance_id, host, networks) return self.get_instance_nw_info(context, instance_id, type_id) @@ -464,6 +476,7 @@ class NetworkManager(manager.SchedulerDependentManager): info = { 'label': network['label'], 'gateway': network['gateway'], + 'dhcp_server': self._get_dhcp_ip(context, network), 'broadcast': network['broadcast'], 'mac': vif['address'], 'rxtx_cap': flavor['rxtx_cap'], @@ -569,7 +582,7 @@ class NetworkManager(manager.SchedulerDependentManager): # the code below will update the file if necessary if FLAGS.update_dhcp_on_disassociate: network_ref = self.db.fixed_ip_get_network(context, address) - network_ref['dhcp_listen'] = self._get_dhcp_ip(context, network_ref) + network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) self._update_dhcp(context, network_ref) def create_networks(self, context, label, cidr, num_networks, @@ -628,6 +641,7 @@ class NetworkManager(manager.SchedulerDependentManager): # robust solution would be to make them uniq per ip net['vpn_public_port'] = kwargs['vpn_start'] + index + net['multi_host'] = True # None if network with cidr or cidr_v6 already exists network = self.db.network_create_safe(context, net) @@ -785,6 +799,7 @@ class FlatDHCPManager(FloatingIP, RPCAllocateFixedIP, NetworkManager): net['dhcp_start'] = FLAGS.flat_network_dhcp_start self.db.network_update(context, network_id, net) network = db.network_get(context, network_id) + network['dhcp_server'] = self._get_dhcp_ip(context, network) self.driver.ensure_bridge(network['bridge'], network['bridge_interface'], network) @@ -866,8 +881,9 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): # get networks associated with project networks = self.db.project_get_networks(context, project_id) - # return only networks which have host set - return [network for network in networks if network['host']] + # return only networks which have host set or are multi_host + return [network for network in networks + if network['host'] or network['multi_host']] def create_networks(self, context, **kwargs): """Create networks based on parameters.""" @@ -896,6 +912,7 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): db.network_update(context, network_id, net) else: address = network['vpn_public_address'] + network['dhcp_server'] = self._get_dhcp_ip(context, network) self.driver.ensure_vlan_bridge(network['vlan'], network['bridge'], network['bridge_interface'], diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 0c6eaab84..5e8fcdd2f 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -928,7 +928,6 @@ class LibvirtConnection(driver.ComputeDriver): def _get_nic_for_xml(self, network, mapping): # Assume that the gateway also acts as the dhcp server. - dhcp_server = mapping['gateway'] gateway6 = mapping.get('gateway6') mac_id = mapping['mac'].replace(':', '') @@ -951,7 +950,7 @@ class LibvirtConnection(driver.ComputeDriver): 'bridge_name': network['bridge'], 'mac_address': mapping['mac'], 'ip_address': mapping['ips'][0]['ip'], - 'dhcp_server': dhcp_server, + 'dhcp_server': mapping['dhcp_server'], 'extra_params': extra_params, } -- cgit From 8e1a74e5604e1569e314af67b72966122940330b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Jul 2011 16:06:20 -0700 Subject: filter the dhcp to only respond to requests from this host --- nova/network/linux_net.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 0a7a62510..848e40643 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -551,21 +551,27 @@ def ensure_bridge(bridge, interface, net_attrs=None): bridge) -def get_dhcp_leases(context, network_id): +def get_dhcp_leases(context, network_ref): """Return a network's hosts config in dnsmasq leasefile format.""" hosts = [] - for fixed_ip_ref in db.network_get_associated_fixed_ips(context, - network_id): - hosts.append(_host_lease(fixed_ip_ref)) + for fixed_ref in db.network_get_associated_fixed_ips(context, + network_ref['id']): + host = fixed_ref['instance']['host'] + if network_ref['multi_host'] and FLAGS.host != host: + continue + hosts.append(_host_lease(fixed_ref)) return '\n'.join(hosts) -def get_dhcp_hosts(context, network_id): +def get_dhcp_hosts(context, network_ref): """Get network's hosts config in dhcp-host format.""" hosts = [] - for fixed_ip_ref in db.network_get_associated_fixed_ips(context, - network_id): - hosts.append(_host_dhcp(fixed_ip_ref)) + for fixed_ref in db.network_get_associated_fixed_ips(context, + network_ref['id']): + host = fixed_ref['instance']['host'] + if network_ref['multi_host'] and FLAGS.host != host: + continue + hosts.append(_host_dhcp(fixed_ref)) return '\n'.join(hosts) @@ -582,7 +588,7 @@ def update_dhcp(context, network_ref): """ conffile = _dhcp_file(network_ref['bridge'], 'conf') with open(conffile, 'w') as f: - f.write(get_dhcp_hosts(context, network_ref['id'])) + f.write(get_dhcp_hosts(context, network_ref)) # Make sure dnsmasq can actually read it (it setuid()s to "nobody") os.chmod(conffile, 0644) -- cgit From 53213dc4cd0f6f940d707c5d5932f4af7e5f988a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Jul 2011 16:22:41 -0700 Subject: add ability to set multi_host in nova-manage and remove debugging issues --- nova/db/sqlalchemy/api.py | 1 - nova/network/manager.py | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9c138d2fd..472513329 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -683,7 +683,6 @@ def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): if host: fixed_ip_ref.host = host session.add(fixed_ip_ref) - LOG.warn("gave address %s to %s, %s", fixed_ip_ref['address'], instance_id, host) return fixed_ip_ref['address'] diff --git a/nova/network/manager.py b/nova/network/manager.py index ce631ce75..d799d5346 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -86,6 +86,8 @@ flags.DEFINE_integer('num_networks', 1, 'Number of networks to support') flags.DEFINE_string('vpn_ip', '$my_ip', 'Public IP for the cloudpipe VPN servers') flags.DEFINE_integer('vpn_start', 1000, 'First Vpn port for private networks') +flags.DEFINE_bool('multi_host', False, + 'Default value for multi_host in networks') flags.DEFINE_integer('network_size', 256, 'Number of addresses in each private subnet') flags.DEFINE_string('floating_range', '4.4.4.0/24', @@ -360,7 +362,7 @@ class NetworkManager(manager.SchedulerDependentManager): LOG.debug(_('Dissassociated %s stale fixed ip(s)'), num) # setup any new networks which have been created - #self.set_network_hosts(context) + self.set_network_hosts(context) def set_network_host(self, context, network_id): """Safely sets the host of the network.""" @@ -585,7 +587,7 @@ class NetworkManager(manager.SchedulerDependentManager): network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) self._update_dhcp(context, network_ref) - def create_networks(self, context, label, cidr, num_networks, + def create_networks(self, context, label, cidr, multi_host, num_networks, network_size, cidr_v6, gateway_v6, bridge, bridge_interface, **kwargs): """Create networks based on parameters.""" @@ -604,6 +606,7 @@ class NetworkManager(manager.SchedulerDependentManager): net['bridge_interface'] = bridge_interface net['dns'] = FLAGS.flat_network_dns net['cidr'] = cidr + net['multi_host'] = multi_host net['netmask'] = str(project_net.netmask) net['gateway'] = str(project_net[1]) net['broadcast'] = str(project_net.broadcast) @@ -641,7 +644,6 @@ class NetworkManager(manager.SchedulerDependentManager): # robust solution would be to make them uniq per ip net['vpn_public_port'] = kwargs['vpn_start'] + index - net['multi_host'] = True # None if network with cidr or cidr_v6 already exists network = self.db.network_create_safe(context, net) -- cgit From 11dfe937bcfa542c985a977e9ff855f717e80e69 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Jul 2011 16:24:43 -0700 Subject: don't set network host for multi_host networks --- nova/network/manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index d799d5346..83ab4dd72 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -378,8 +378,7 @@ class NetworkManager(manager.SchedulerDependentManager): """Set the network hosts for any networks which are unset.""" networks = self.db.network_get_all(context) for network in networks: - host = network['host'] - if not host: + if not network['multi_host'] and not network['host']: # return so worker will only grab 1 (to help scale flatter) return self.set_network_host(context, network['id']) -- cgit From 77b655cd16a265eb2b8fc369941d19890766e712 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Jul 2011 17:01:19 -0700 Subject: update tests --- nova/tests/__init__.py | 1 + nova/tests/test_network.py | 3 +++ 2 files changed, 4 insertions(+) (limited to 'nova') diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py index e4ed75d37..b885000e1 100644 --- a/nova/tests/__init__.py +++ b/nova/tests/__init__.py @@ -59,6 +59,7 @@ def setup(): network.create_networks(ctxt, label='test', cidr=FLAGS.fixed_range, + multi_host=FLAGS.multi_host, num_networks=FLAGS.num_networks, network_size=FLAGS.network_size, cidr_v6=FLAGS.fixed_range_v6, diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 6d5166019..390cdc003 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -44,6 +44,7 @@ class FakeModel(dict): networks = [{'id': 0, 'label': 'test0', 'injected': False, + 'multi_host': False, 'cidr': '192.168.0.0/24', 'cidr_v6': '2001:db8::/64', 'gateway_v6': '2001:db8::1', @@ -61,6 +62,7 @@ networks = [{'id': 0, {'id': 1, 'label': 'test1', 'injected': False, + 'multi_host': False, 'cidr': '192.168.1.0/24', 'cidr_v6': '2001:db9::/64', 'gateway_v6': '2001:db9::1', @@ -163,6 +165,7 @@ class FlatNetworkTestCase(test.TestCase): self.assertDictMatch(nw[0], 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, -- cgit From e24c1d998331444235480be241484b0408cdaf9e Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Jul 2011 17:07:44 -0700 Subject: fix libvirt test --- nova/tests/test_libvirt.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index f99e1713d..6e2ec7ed6 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -58,6 +58,7 @@ def _create_network_info(count=1, ipv6=None): 'cidr': fake_ip, 'cidr_v6': fake_ip} mapping = {'mac': fake, + 'dhcp_server': fake, 'gateway': fake, 'gateway6': fake, 'ips': [{'ip': fake_ip}, {'ip': fake_ip}]} -- cgit From 0af41b8c50631252f0dfe0b11268beb3d5e49508 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 6 Jul 2011 12:58:04 -0700 Subject: removed extra comment --- nova/db/api.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index ee4cbad57..c9d5bc72b 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -335,7 +335,6 @@ def fixed_ip_associate(context, address, instance_id): def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): """Find free ip in network and associate it to instance or host. - If reserved is true, it will pull the ip from the reserved pool. Raises if one is not available. """ -- cgit From 5ef4e6e318caa038f2af31a930f6b2b952176661 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 8 Jul 2011 18:28:25 -0700 Subject: cleaned up on set network host to _setup_network and made networks allocate ips dynamically --- nova/db/sqlalchemy/api.py | 2 + nova/network/api.py | 3 +- nova/network/linux_net.py | 1 + nova/network/manager.py | 184 ++++++++++++++++++---------------------------- 4 files changed, 75 insertions(+), 115 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 472513329..c22ece068 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1706,7 +1706,9 @@ def network_get_all_by_host(context, host): with session.begin(): return session.query(models.Network).\ filter_by(deleted=False).\ + join(models.Network.fixed_ips).\ filter_by(host=host).\ + filter_by(deleted=False).\ all() diff --git a/nova/network/api.py b/nova/network/api.py index c0f0fb8eb..38560b037 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -174,7 +174,8 @@ class API(base.Base): def get_instance_nw_info(self, context, instance): """Returns all network info related to an instance.""" args = {'instance_id': instance['id'], - 'instance_type_id': instance['instance_type_id']} + 'instance_type_id': instance['instance_type_id'], + 'host': instance['host']} return rpc.call(context, FLAGS.network_topic, {'method': 'get_instance_nw_info', 'args': args}) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 848e40643..a8ce1c16a 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -700,6 +700,7 @@ def _dnsmasq_cmd(net): cmd = ['sudo', '-E', 'dnsmasq', '--strict-order', '--bind-interfaces', + '--interface=%s' % net['bridge'], '--conf-file=%s' % FLAGS.dnsmasq_config_file, '--domain=%s' % FLAGS.dhcp_domain, '--pid-file=%s' % _dhcp_file(net['bridge'], 'pid'), diff --git a/nova/network/manager.py b/nova/network/manager.py index 83ab4dd72..8ef9c9a33 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -28,7 +28,6 @@ topologies. All of the network commands are issued to a subclass of :flat_network_bridge: Bridge device for simple network instances :flat_interface: FlatDhcp will bridge into this interface if set :flat_network_dns: Dns for simple network -:flat_network_dhcp_start: Dhcp start for FlatDhcp :vlan_start: First VLAN for private networks :vpn_ip: Public IP for the cloudpipe VPN servers :vpn_start: First Vpn port for private networks @@ -77,8 +76,6 @@ flags.DEFINE_bool('flat_injected', True, 'Whether to attempt to inject network setup into guest') flags.DEFINE_string('flat_interface', None, 'FlatDhcp will bridge into this interface if set') -flags.DEFINE_string('flat_network_dhcp_start', '10.0.0.2', - 'Dhcp start for FlatDhcp') flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks') flags.DEFINE_string('vlan_interface', None, 'vlans will bridge into this interface if set') @@ -134,6 +131,11 @@ class RPCAllocateFixedIP(object): # NOTE(vish): if we are not multi_host pass to the network host if not network['multi_host']: host = network['host'] + # NOTE(vish): if there is no network host, set one + if host == None: + host = rpc.call(context, FLAGS.network_topic, + {'method': 'set_network_host', + 'args': {'network_ref': network}}) if host != self.host: # need to call allocate_fixed_ip to correct network host topic = self.db.queue_get_for(context, FLAGS.network_topic, host) @@ -302,51 +304,34 @@ class NetworkManager(manager.SchedulerDependentManager): super(NetworkManager, self).__init__(service_name='network', *args, **kwargs) - def _update_dhcp(self, context, network_ref): - """Sets the listen address before sending update to the driver.""" - network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) - return self.driver.update_dhcp(context, network_ref) - - def _get_dhcp_ip(self, context, network_ref): - """Get the proper dhcp address to listen on. - - If it is a multi_host network, get the ip assigned to this host, - otherwise, assume that dhcp is listening on the gateway.""" - if network_ref['multi_host']: - - @utils.synchronized('get_dhcp') - def _sync_get_dhcp_ip(): - network_id = network_ref['id'] - try: - fip = self.db.fixed_ip_get_by_network_host(context, - network_id, - self.host) - return fip['address'] - except exception.FixedIpNotFoundForNetworkHost: - addr = self.db.fixed_ip_associate_pool(context.elevated(), - network_id, - host=self.host) - return addr - - return _sync_get_dhcp_ip() + @utils.synchronized('get_dhcp') + def _get_dhcp_ip(self, context, network_ref, host=None): + """Get the proper dhcp address to listen on.""" + if not host: + host = self.host + network_id = network_ref['id'] + try: + fip = self.db.fixed_ip_get_by_network_host(context, + network_id, + host) + return fip['address'] + except exception.FixedIpNotFoundForNetworkHost: + elevated = context.elevated() + addr = self.db.fixed_ip_associate_pool(elevated, + network_id, + host=host) + return addr - else: - return network_ref['gateway'] def init_host(self): """Do any initialization that needs to be run if this is a standalone service. """ - # Set up this host for networks in which it's already - # the designated network host. + # NOTE(vish): Set up networks for which this host already has + # an ip address. ctxt = context.get_admin_context() for network in self.db.network_get_all_by_host(ctxt, self.host): - self._on_set_network_host(ctxt, network['id']) - for network in self.db.network_get_all(ctxt): - # NOTE(vish): multi_host networks need to be set on all boxes - if network['multi_host']: - self._on_set_network_host(ctxt, network['id']) - + self._setup_network(ctxt, network) def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval.""" @@ -361,37 +346,21 @@ class NetworkManager(manager.SchedulerDependentManager): if num: LOG.debug(_('Dissassociated %s stale fixed ip(s)'), num) - # setup any new networks which have been created - self.set_network_hosts(context) - - def set_network_host(self, context, network_id): + def set_network_host(self, context, network_ref): """Safely sets the host of the network.""" LOG.debug(_('setting network host'), context=context) host = self.db.network_set_host(context, - network_id, + network_ref['id'], self.host) - if host == self.host: - self._on_set_network_host(context, network_id) return host - def set_network_hosts(self, context): - """Set the network hosts for any networks which are unset.""" - networks = self.db.network_get_all(context) - for network in networks: - if not network['multi_host'] and not network['host']: - # return so worker will only grab 1 (to help scale flatter) - return self.set_network_host(context, network['id']) - def _get_networks_for_instance(self, context, instance_id, project_id): """Determine & return which networks an instance should connect to.""" # TODO(tr3buchet) maybe this needs to be updated in the future if # there is a better way to determine which networks # a non-vlan instance should connect to - networks = self.db.network_get_all(context) - - # return only networks which are not vlan networks and have host set - return [network for network in networks if not network['vlan'] - and network['host'] or network['multi_host']] + return [network for network in self.db.network_get_all(context) + if not network['vlan']] def allocate_for_instance(self, context, **kwargs): """Handles allocating the various network resources for an instance. @@ -410,7 +379,7 @@ class NetworkManager(manager.SchedulerDependentManager): LOG.warn(networks) self._allocate_mac_addresses(context, instance_id, networks) self._allocate_fixed_ips(admin_context, instance_id, host, networks) - return self.get_instance_nw_info(context, instance_id, type_id) + return self.get_instance_nw_info(context, instance_id, type_id, host) def deallocate_for_instance(self, context, **kwargs): """Handles deallocating various network resources for an instance. @@ -430,7 +399,7 @@ class NetworkManager(manager.SchedulerDependentManager): # deallocate vifs (mac addresses) self.db.virtual_interface_delete_by_instance(context, instance_id) - def get_instance_nw_info(self, context, instance_id, instance_type_id): + def get_instance_nw_info(self, context, instance_id, instance_type_id, host): """Creates network info list for instance. called by allocate_for_instance and netowrk_api @@ -474,10 +443,16 @@ class NetworkManager(manager.SchedulerDependentManager): 'cidr': network['cidr'], 'cidr_v6': network['cidr_v6'], 'injected': network['injected']} + if network['multi_host']: + dhcp_server = self._get_dhcp_ip(context, network, host) + else: + dhcp_server = self._get_dhcp_ip(context, + network, + network['host']) info = { 'label': network['label'], 'gateway': network['gateway'], - 'dhcp_server': self._get_dhcp_ip(context, network), + 'dhcp_server': dhcp_server, 'broadcast': network['broadcast'], 'mac': vif['address'], 'rxtx_cap': flavor['rxtx_cap'], @@ -537,6 +512,7 @@ class NetworkManager(manager.SchedulerDependentManager): values = {'allocated': True, 'virtual_interface_id': vif['id']} self.db.fixed_ip_update(context, address, values) + self._setup_network(context, network) return address def deallocate_fixed_ip(self, context, address, **kwargs): @@ -583,8 +559,7 @@ class NetworkManager(manager.SchedulerDependentManager): # the code below will update the file if necessary if FLAGS.update_dhcp_on_disassociate: network_ref = self.db.fixed_ip_get_network(context, address) - network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) - self._update_dhcp(context, network_ref) + self._setup_network(context, network_ref) def create_networks(self, context, label, cidr, multi_host, num_networks, network_size, cidr_v6, gateway_v6, bridge, @@ -685,8 +660,8 @@ class NetworkManager(manager.SchedulerDependentManager): """Calls allocate_fixed_ip once for each network.""" raise NotImplementedError() - def _on_set_network_host(self, context, network_id): - """Called when this host becomes the host for a network.""" + def _setup_network(self, context, network_ref): + """Sets up network on this host.""" raise NotImplementedError() def setup_compute_network(self, context, instance_id): @@ -746,12 +721,12 @@ class FlatManager(NetworkManager): """ pass - def _on_set_network_host(self, context, network_id): - """Called when this host becomes the host for a network.""" + def _setup_network(self, context, network_ref): + """Setup Network on this host.""" net = {} net['injected'] = FLAGS.flat_injected net['dns'] = FLAGS.flat_network_dns - self.db.network_update(context, network_id, net) + self.db.network_update(context, network_ref['id'], net) class FlatDHCPManager(FloatingIP, RPCAllocateFixedIP, NetworkManager): @@ -786,31 +761,18 @@ class FlatDHCPManager(FloatingIP, RPCAllocateFixedIP, NetworkManager): self.driver.ensure_bridge(network['bridge'], network['bridge_interface']) - def allocate_fixed_ip(self, context, instance_id, network): - """Allocate flat_network fixed_ip, then setup dhcp for this network.""" - address = super(FlatDHCPManager, self).allocate_fixed_ip(context, - instance_id, - network) - if not FLAGS.fake_network: - self._update_dhcp(context, network) - - def _on_set_network_host(self, context, network_id): - """Called when this host becomes the host for a project.""" - net = {} - net['dhcp_start'] = FLAGS.flat_network_dhcp_start - self.db.network_update(context, network_id, net) - network = db.network_get(context, network_id) - network['dhcp_server'] = self._get_dhcp_ip(context, network) - self.driver.ensure_bridge(network['bridge'], - network['bridge_interface'], - network) + def _setup_network(self, context, network_ref): + """Sets up network on this host.""" + network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) + self.driver.ensure_bridge(network_ref['bridge'], + network_ref['bridge_interface'], + network_ref) if not FLAGS.fake_network: - network_ref = self.db.network_get(context, network_id) - self._update_dhcp(context, network_ref) + self.driver.update_dhcp(context, network_ref) if(FLAGS.use_ipv6): self.driver.update_ra(context, network_ref) gateway = utils.get_my_linklocal(network_ref['bridge']) - self.db.network_update(context, network_id, + self.db.network_update(context, network_ref['id'], {'gateway_v6': gateway}) @@ -859,8 +821,8 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): values = {'allocated': True, 'virtual_interface_id': vif['id']} self.db.fixed_ip_update(context, address, values) - if not FLAGS.fake_network: - self._update_dhcp(context, network) + self._setup_network(context, network) + return address def add_network_to_project(self, context, project_id): """Force adds another network to a project.""" @@ -880,11 +842,7 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): def _get_networks_for_instance(self, context, instance_id, project_id): """Determine which networks an instance should connect to.""" # get networks associated with project - networks = self.db.project_get_networks(context, project_id) - - # return only networks which have host set or are multi_host - return [network for network in networks - if network['host'] or network['multi_host']] + return self.db.project_get_networks(context, project_id) def create_networks(self, context, **kwargs): """Create networks based on parameters.""" @@ -903,36 +861,34 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): NetworkManager.create_networks(self, context, vpn=True, **kwargs) - def _on_set_network_host(self, context, network_id): - """Called when this host becomes the host for a network.""" - network = self.db.network_get(context, network_id) - if not network['vpn_public_address']: + def _setup_network(self, context, network_ref): + """Sets up network on this host.""" + network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) + if not network_ref['vpn_public_address']: net = {} address = FLAGS.vpn_ip net['vpn_public_address'] = address - db.network_update(context, network_id, net) + network_ref = db.network_update(context, network_ref, net) else: - address = network['vpn_public_address'] - network['dhcp_server'] = self._get_dhcp_ip(context, network) - self.driver.ensure_vlan_bridge(network['vlan'], - network['bridge'], - network['bridge_interface'], - network) + address = network_ref['vpn_public_address'] + self.driver.ensure_vlan_bridge(network_ref['vlan'], + network_ref['bridge'], + network_ref['bridge_interface'], + network_ref) # NOTE(vish): only ensure this forward if the address hasn't been set # manually. if address == FLAGS.vpn_ip and hasattr(self.driver, "ensure_vlan_forward"): self.driver.ensure_vlan_forward(FLAGS.vpn_ip, - network['vpn_public_port'], - network['vpn_private_address']) + network_ref['vpn_public_port'], + network_ref['vpn_private_address']) if not FLAGS.fake_network: - network_ref = self.db.network_get(context, network_id) - self._update_dhcp(context, network_ref) + self.driver.update_dhcp(context, network_ref) if(FLAGS.use_ipv6): self.driver.update_ra(context, network_ref) gateway = utils.get_my_linklocal(network_ref['bridge']) - self.db.network_update(context, network_id, + self.db.network_update(context, network_ref['id'], {'gateway_v6': gateway}) @property -- cgit From 71cbb30cfaf9b38786949fbf6314f0284e9a7efc Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 8 Jul 2011 18:45:13 -0700 Subject: make sure that old networks get the same dhcp ip so we don't break existing deployments --- nova/db/sqlalchemy/api.py | 7 ++++++- nova/network/manager.py | 8 +++++--- 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index c22ece068..8b7ab8cbd 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1704,10 +1704,15 @@ def network_get_all_by_instance(_context, instance_id): def network_get_all_by_host(context, host): session = get_session() with session.begin(): + # NOTE(vish): return networks that have host set + # or that have a fixed ip with host set + host_filter = or_(models.Network.host == host, + models.FixedIp.host == host) + return session.query(models.Network).\ filter_by(deleted=False).\ join(models.Network.fixed_ips).\ - filter_by(host=host).\ + filter(host_filter).\ filter_by(deleted=False).\ all() diff --git a/nova/network/manager.py b/nova/network/manager.py index 8ef9c9a33..dac42e7ec 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -307,6 +307,10 @@ class NetworkManager(manager.SchedulerDependentManager): @utils.synchronized('get_dhcp') def _get_dhcp_ip(self, context, network_ref, host=None): """Get the proper dhcp address to listen on.""" + # NOTE(vish): this is for compatibility + if not network_ref['multi_host']: + return network_ref['gateway'] + if not host: host = self.host network_id = network_ref['id'] @@ -317,11 +321,9 @@ class NetworkManager(manager.SchedulerDependentManager): return fip['address'] except exception.FixedIpNotFoundForNetworkHost: elevated = context.elevated() - addr = self.db.fixed_ip_associate_pool(elevated, + return self.db.fixed_ip_associate_pool(elevated, network_id, host=host) - return addr - def init_host(self): """Do any initialization that needs to be run if this is a -- cgit From 4858a276a4cb779de55d987f2b3c37db5bf24492 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 8 Jul 2011 18:58:24 -0700 Subject: fix tests --- nova/tests/__init__.py | 2 +- nova/tests/test_network.py | 16 +--------------- 2 files changed, 2 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py index b885000e1..7df6c06eb 100644 --- a/nova/tests/__init__.py +++ b/nova/tests/__init__.py @@ -69,7 +69,7 @@ def setup(): vpn_start=FLAGS.vpn_start, vlan_start=FLAGS.vlan_start) for net in db.network_get_all(ctxt): - network.set_network_host(ctxt, net['id']) + network.set_network_host(ctxt, net) cleandb = os.path.join(FLAGS.state_path, FLAGS.sqlite_clean_db) shutil.copyfile(testdb, cleandb) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 390cdc003..1740d4f54 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -123,20 +123,6 @@ class FlatNetworkTestCase(test.TestCase): self.network = network_manager.FlatManager(host=HOST) self.network.db = db - def test_set_network_hosts(self): - self.mox.StubOutWithMock(db, 'network_get_all') - self.mox.StubOutWithMock(db, 'network_set_host') - self.mox.StubOutWithMock(db, 'network_update') - - db.network_get_all(mox.IgnoreArg()).AndReturn([networks[0]]) - db.network_set_host(mox.IgnoreArg(), - networks[0]['id'], - mox.IgnoreArg()).AndReturn(HOST) - db.network_update(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) - self.mox.ReplayAll() - - self.network.set_network_hosts(None) - 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') @@ -150,7 +136,7 @@ class FlatNetworkTestCase(test.TestCase): mox.IgnoreArg()).AndReturn(flavor) self.mox.ReplayAll() - nw_info = self.network.get_instance_nw_info(None, 0, 0) + nw_info = self.network.get_instance_nw_info(None, 0, 0, None) self.assertTrue(nw_info) -- cgit From 934e8ab289cd906a94170f0d53cc49edf2db0d6f Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 13 Jul 2011 19:32:17 -0400 Subject: adding new query parameters --- nova/api/openstack/images.py | 13 ++- nova/tests/api/openstack/test_images.py | 142 +++++++++++++++++++++++--------- 2 files changed, 115 insertions(+), 40 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d0317583e..b0364b403 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -35,7 +35,13 @@ from nova.api.openstack import wsgi LOG = log.getLogger('nova.api.openstack.images') FLAGS = flags.FLAGS -SUPPORTED_FILTERS = ['name', 'status'] +SUPPORTED_FILTERS = { + 'name': 'name', + 'status': 'status', + 'changes-since': 'changes-since', + 'server': 'property-instance_ref', + 'type': 'property-image_type', +} class Controller(object): @@ -62,8 +68,9 @@ class Controller(object): filters = {} for param in req.str_params: if param in SUPPORTED_FILTERS or param.startswith('property-'): - filters[param] = req.str_params.get(param) - + # map filter name or carry through if property-* + _param = SUPPORTED_FILTERS.get(param, param) + filters[_param] = req.str_params.get(param) return filters def show(self, req, id): diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index c1bdd6906..8d73f6684 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -801,11 +801,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'name': 'testname'} - image_service.index( - context, filters=filters).AndReturn([]) + image_service.index(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images?name=testname') + request = webob.Request.blank('/v1.1/images?name=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) @@ -816,11 +814,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} - image_service.index( - context, filters=filters).AndReturn([]) + image_service.index(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images?status=ACTIVE') + request = webob.Request.blank('/v1.1/images?status=ACTIVE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) @@ -831,11 +827,51 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'property-test': '3'} - image_service.index( - context, filters=filters).AndReturn([]) + image_service.index(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images?property-test=3') + request = webob.Request.blank('/v1.1/images?property-test=3') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + + def test_image_filter_server(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + # 'server' should be converted to 'property-instance_ref' + filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images?server=' + 'http://localhost:8774/servers/12') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + + def test_image_filter_changes_since(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + filters = {'changes-since': '2011-01-24T17:08Z'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images?changes-since=' + '2011-01-24T17:08Z') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + + def test_image_filter_with_type(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + filters = {'property-image_type': 'BASE'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images?type=BASE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) @@ -846,14 +882,13 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} - image_service.index( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images?status=ACTIVE&UNSUPPORTEDFILTER=testname') + request = webob.Request.blank('/v1.1/images?status=ACTIVE&' + 'UNSUPPORTEDFILTER=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) - controller.index(request) + controller.detail(request) mocker.VerifyAll() def test_image_no_filters(self): @@ -876,11 +911,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'name': 'testname'} - image_service.detail( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images/detail?name=testname') + request = webob.Request.blank('/v1.1/images/detail?name=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -891,11 +924,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} - image_service.detail( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images/detail?status=ACTIVE') + request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -906,26 +937,65 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'property-test': '3'} - image_service.detail( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images/detail?property-test=3') + request = webob.Request.blank('/v1.1/images/detail?property-test=3') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) mocker.VerifyAll() + def test_image_detail_filter_server(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + # 'server' should be converted to 'property-instance_ref' + filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images/detail?server=' + 'http://localhost:8774/servers/12') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + + def test_image_detail_filter_changes_since(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + filters = {'changes-since': '2011-01-24T17:08Z'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images/detail?changes-since=' + '2011-01-24T17:08Z') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + + def test_image_detail_filter_with_type(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + filters = {'property-image_type': 'BASE'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images/detail?type=BASE') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + def test_image_detail_filter_not_supported(self): mocker = mox.Mox() image_service = mocker.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} - image_service.detail( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images/detail?status=ACTIVE&UNSUPPORTEDFILTER=testname') + request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE&' + 'UNSUPPORTEDFILTER=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -936,11 +1006,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {} - image_service.detail( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images/detail') + request = webob.Request.blank('/v1.1/images/detail') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) -- cgit From 10ef948271130db0dcc53b0ea94c3a47313244f4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 14 Jul 2011 09:29:27 -0400 Subject: removing mox object instantiation from each test; renaming _param to filter_name --- nova/api/openstack/images.py | 4 +- nova/tests/api/openstack/test_images.py | 112 ++++++++++++++------------------ 2 files changed, 50 insertions(+), 66 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index b0364b403..f82b98a38 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -69,8 +69,8 @@ class Controller(object): for param in req.str_params: if param in SUPPORTED_FILTERS or param.startswith('property-'): # map filter name or carry through if property-* - _param = SUPPORTED_FILTERS.get(param, param) - filters[_param] = req.str_params.get(param) + filter_name = SUPPORTED_FILTERS.get(param, param) + filters[filter_name] = req.str_params.get(param) return filters def show(self, req, id): diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 8d73f6684..2eb4896f0 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -797,222 +797,206 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertDictListMatch(expected, response_list) def test_image_filter_with_name(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'name': 'testname'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?name=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_with_status(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?status=ACTIVE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_with_property(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'property-test': '3'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?property-test=3') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_server(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() # 'server' should be converted to 'property-instance_ref' filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?server=' 'http://localhost:8774/servers/12') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_changes_since(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'changes-since': '2011-01-24T17:08Z'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?changes-since=' '2011-01-24T17:08Z') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_with_type(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'property-image_type': 'BASE'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?type=BASE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_not_supported(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?status=ACTIVE&' 'UNSUPPORTEDFILTER=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_no_filters(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {} image_service.index( context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank( '/v1.1/images') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_with_name(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'name': 'testname'} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?name=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_with_status(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_with_property(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'property-test': '3'} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?property-test=3') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_server(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() # 'server' should be converted to 'property-instance_ref' filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?server=' 'http://localhost:8774/servers/12') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_changes_since(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'changes-since': '2011-01-24T17:08Z'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?changes-since=' '2011-01-24T17:08Z') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_with_type(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'property-image_type': 'BASE'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?type=BASE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_not_supported(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE&' 'UNSUPPORTEDFILTER=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_no_filters(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_get_image_found(self): req = webob.Request.blank('/v1.0/images/123') -- cgit From 4c779a87651a37f9acf05f1101859a1ce4c288c1 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Thu, 14 Jul 2011 17:49:29 -0500 Subject: First pass --- nova/compute/api.py | 15 +++-- nova/compute/manager.py | 74 ++++++++++++---------- nova/db/sqlalchemy/api.py | 12 ++-- .../032_change_instance_id_in_migrations.py | 43 +++++++++++++ nova/db/sqlalchemy/models.py | 3 +- nova/tests/test_compute.py | 24 +++---- 6 files changed, 114 insertions(+), 57 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/032_change_instance_id_in_migrations.py (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 432658bbb..7857f06a5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -822,6 +822,7 @@ class API(base.Base): instance_id, params=rebuild_params) + @scheduler_api.reroute_compute("revert_resize") def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process.""" context = context.elevated() @@ -832,11 +833,12 @@ class API(base.Base): status='finished') params = {'migration_id': migration_ref['id']} - self._cast_compute_message('revert_resize', context, instance_id, + self._cast_compute_message('revert_resize', context, migration_ref['dest_compute'], params=params) self.db.migration_update(context, migration_ref['id'], {'status': 'reverted'}) + @scheduler_api.reroute_compute("confirm_resize") def confirm_resize(self, context, instance_id): """Confirms a migration/resize and deletes the 'old' instance.""" context = context.elevated() @@ -845,9 +847,8 @@ class API(base.Base): if not migration_ref: raise exception.MigrationNotFoundByStatus(instance_id=instance_id, status='finished') - instance_ref = self.db.instance_get(context, instance_id) params = {'migration_id': migration_ref['id']} - self._cast_compute_message('confirm_resize', context, instance_id, + self._cast_compute_message('confirm_resize', context, migration_ref['source_compute'], params=params) self.db.migration_update(context, migration_ref['id'], @@ -855,6 +856,7 @@ class API(base.Base): self.db.instance_update(context, instance_id, {'host': migration_ref['dest_compute'], }) + @scheduler_api.reroute_compute("resize") def resize(self, context, instance_id, flavor_id=None): """Resize (ie, migrate) a running instance. @@ -862,8 +864,8 @@ class API(base.Base): the original flavor_id. If flavor_id is not None, the instance should be migrated to a new host and resized to the new flavor_id. """ - instance = self.db.instance_get(context, instance_id) - current_instance_type = instance['instance_type'] + instance_ref = self._get_instance(context, instance_id, 'resize') + current_instance_type = instance_ref['instance_type'] # If flavor_id is not provided, only migrate the instance. if not flavor_id: @@ -891,10 +893,11 @@ class API(base.Base): raise exception.ApiError(_("Invalid flavor: cannot use" "the same flavor. ")) + instance_ref = self._get_instance(context, instance_id, 'resize') self._cast_scheduler_message(context, {"method": "prep_resize", "args": {"topic": FLAGS.compute_topic, - "instance_id": instance_id, + "instance_uuid": instance_ref['uuid'], "flavor_id": new_instance_type['id']}}) @scheduler_api.reroute_compute("add_fixed_ip") diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c627d2985..e00e3d0ab 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -664,10 +664,12 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def confirm_resize(self, context, instance_id, migration_id): + def confirm_resize(self, context, migration_id): """Destroys the source instance.""" - context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) + migration_ref = self.db.migration_get(context, migration_id) + instance_ref = self.db.instance_get(context, + migration_ref.instance_uuid) + self.driver.destroy(instance_ref) usage_info = utils.usage_from_instance(instance_ref) notifier_api.notify('compute.%s' % self.host, @@ -677,43 +679,44 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def revert_resize(self, context, instance_id, migration_id): + def revert_resize(self, context, migration_id): """Destroys the new instance on the destination machine. Reverts the model changes, and powers on the old instance on the source machine. """ - instance_ref = self.db.instance_get(context, instance_id) migration_ref = self.db.migration_get(context, migration_id) + instance_ref = self.db.instance_get(context, + migration_ref.instance_uuid) self.driver.destroy(instance_ref) topic = self.db.queue_get_for(context, FLAGS.compute_topic, instance_ref['host']) rpc.cast(context, topic, {'method': 'finish_revert_resize', - 'args': { - 'migration_id': migration_ref['id'], - 'instance_id': instance_id, }, + 'args': {'migration_id': migration_ref['id']}, }) @exception.wrap_exception @checks_instance_lock - def finish_revert_resize(self, context, instance_id, migration_id): + def finish_revert_resize(self, context, migration_id): """Finishes the second half of reverting a resize. Power back on the source instance and revert the resized attributes in the database. """ - instance_ref = self.db.instance_get(context, instance_id) migration_ref = self.db.migration_get(context, migration_id) + instance_ref = self.db.instance_get(context, + migration_ref.instance_uuid) + instance_type = self.db.instance_type_get_by_flavor_id(context, migration_ref['old_flavor_id']) # 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_id, + self.db.instance_update(context, instance_ref.instance_uuid, dict(memory_mb=instance_type['memory_mb'], vcpus=instance_type['vcpus'], local_gb=instance_type['local_gb'])) @@ -729,14 +732,14 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def prep_resize(self, context, instance_id, flavor_id): + def prep_resize(self, context, instance_uuid, flavor_id): """Initiates the process of moving a running instance to another host. Possibly changes the RAM and disk size in the process. """ context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) + instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) if instance_ref['host'] == FLAGS.host: raise exception.Error(_( 'Migration error: destination same as source!')) @@ -744,7 +747,7 @@ class ComputeManager(manager.SchedulerDependentManager): instance_type = self.db.instance_type_get_by_flavor_id(context, flavor_id) migration_ref = self.db.migration_create(context, - {'instance_id': instance_id, + {'instance_uuid': instance_uuid, 'source_compute': instance_ref['host'], 'dest_compute': FLAGS.host, 'dest_host': self.driver.get_host_ip_addr(), @@ -752,15 +755,13 @@ class ComputeManager(manager.SchedulerDependentManager): 'new_flavor_id': flavor_id, 'status': 'pre-migrating'}) - LOG.audit(_('instance %s: migrating to '), instance_id, + LOG.audit(_('instance %s: migrating to '), instance_uuid, context=context) topic = self.db.queue_get_for(context, FLAGS.compute_topic, instance_ref['host']) rpc.cast(context, topic, {'method': 'resize_instance', - 'args': { - 'migration_id': migration_ref['id'], - 'instance_id': instance_id, }, + 'args': {'migration_id': migration_ref['id']}, }) usage_info = utils.usage_from_instance(instance_ref, new_instance_type=instance_type['name'], @@ -772,10 +773,12 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def resize_instance(self, context, instance_id, migration_id): + def resize_instance(self, context, migration_id): """Starts the migration of a running instance to another host.""" migration_ref = self.db.migration_get(context, migration_id) - instance_ref = self.db.instance_get(context, instance_id) + instance_ref = self.db.instance_get_by_uuid(context, + migration_ref.instance_uuid) + self.db.migration_update(context, migration_id, {'status': 'migrating'}) @@ -791,14 +794,14 @@ class ComputeManager(manager.SchedulerDependentManager): topic = self.db.queue_get_for(context, FLAGS.compute_topic, migration_ref['dest_compute']) + params = {'migration_id': migration_id, + 'disk_info': disk_info} rpc.cast(context, topic, {'method': 'finish_resize', - 'args': {'migration_id': migration_id, - 'instance_id': instance_id, - 'disk_info': disk_info}}) + 'args': params}) @exception.wrap_exception @checks_instance_lock - def finish_resize(self, context, instance_id, migration_id, disk_info): + def finish_resize(self, context, migration_id, disk_info): """Completes the migration process. Sets up the newly transferred disk and turns on the instance at its @@ -806,24 +809,21 @@ class ComputeManager(manager.SchedulerDependentManager): """ migration_ref = self.db.migration_get(context, migration_id) - instance_ref = self.db.instance_get(context, - migration_ref['instance_id']) - # TODO(mdietz): apply the rest of the instance_type attributes going - # after they're supported + instance_ref = self.db.instance_get_by_uuid(context, + migration_ref.instance_uuid) instance_type = self.db.instance_type_get_by_flavor_id(context, migration_ref['new_flavor_id']) - self.db.instance_update(context, instance_id, + self.db.instance_update(context, instance_ref.uuid, dict(instance_type_id=instance_type['id'], memory_mb=instance_type['memory_mb'], vcpus=instance_type['vcpus'], local_gb=instance_type['local_gb'])) - # reload the updated instance ref - # FIXME(mdietz): is there reload functionality? - instance = self.db.instance_get(context, instance_id) + instance_ref = self.db.instance_get_by_uuid(context, + instance_ref.uuid) network_info = self.network_api.get_instance_nw_info(context, - instance) - self.driver.finish_resize(instance, disk_info, network_info) + instance_ref) + self.driver.finish_resize(instance_ref, disk_info, network_info) self.db.migration_update(context, migration_id, {'status': 'finished', }) @@ -955,7 +955,11 @@ class ComputeManager(manager.SchedulerDependentManager): context = context.elevated() LOG.debug(_('instance %s: getting locked state'), instance_id, context=context) - instance_ref = self.db.instance_get(context, instance_id) + if utils.is_uuid_like(instance_id): + uuid = instance_id + instance_ref = self.db.instance_get_by_uuid(context, uuid) + else: + instance_ref = self.db.instance_get(context, instance_id) return instance_ref['locked'] @checks_instance_lock diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ffd009513..c440e700e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1317,7 +1317,11 @@ def instance_update(context, instance_id, values): instance_metadata_update_or_create(context, instance_id, values.pop('metadata')) with session.begin(): - instance_ref = instance_get(context, instance_id, session=session) + if utils.is_uuid_like(instance_id): + instance_ref = instance_get_by_uuid(context, instance_id, + session=session) + else: + instance_ref = instance_get(context, instance_id, session=session) instance_ref.update(values) instance_ref.save(session=session) return instance_ref @@ -2765,13 +2769,13 @@ def migration_get(context, id, session=None): @require_admin_context -def migration_get_by_instance_and_status(context, instance_id, status): +def migration_get_by_instance_and_status(context, instance_uuid, status): session = get_session() result = session.query(models.Migration).\ - filter_by(instance_id=instance_id).\ + filter_by(instance_uuid=instance_uuid).\ filter_by(status=status).first() if not result: - raise exception.MigrationNotFoundByStatus(instance_id=instance_id, + raise exception.MigrationNotFoundByStatus(instance_id=instance_uuid, status=status) return result diff --git a/nova/db/sqlalchemy/migrate_repo/versions/032_change_instance_id_in_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/032_change_instance_id_in_migrations.py new file mode 100644 index 000000000..b002ba064 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/032_change_instance_id_in_migrations.py @@ -0,0 +1,43 @@ +# 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.from sqlalchemy import * + +from sqlalchemy import Column, Integer, String, MetaData, Table + +meta = MetaData() + + +# +# Tables to alter +# +# + +instance_id = Column('instance_id', Integer()) +instance_uuid = Column('instance_uuid', String(255)) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + migrations = Table('migrations', meta, autoload=True) + migrations.create_column(instance_uuid) + migrations.c.instance_id.drop() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + migrations = Table('migrations', meta, autoload=True) + migrations.c.instance_uuid.drop() + migrations.create_column(instance_id) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index d29d3d6f1..1e8e2e758 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -526,7 +526,8 @@ class Migration(BASE, NovaBase): dest_host = Column(String(255)) old_flavor_id = Column(Integer()) new_flavor_id = Column(Integer()) - instance_id = Column(Integer, ForeignKey('instances.id'), nullable=True) + instance_uuid = Column(String(255), ForeignKey('instances.uuid'), + nullable=True) #TODO(_cerberus_): enum status = Column(String(255)) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 04bb194d5..0586cc40d 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -424,12 +424,12 @@ class ComputeTestCase(test.TestCase): self.stubs.Set(self.compute.network_api, 'get_instance_nw_info', fake) context = self.context.elevated() instance_id = self._create_instance() - self.compute.prep_resize(context, instance_id, 1) + instance_ref = db.instance_get(context, instance_id) + self.compute.prep_resize(context, instance_ref['uuid'], 1) migration_ref = db.migration_get_by_instance_and_status(context, - instance_id, 'pre-migrating') + instance_ref['uuid'], 'pre-migrating') try: - self.compute.finish_resize(context, instance_id, - int(migration_ref['id']), {}) + self.compute.finish_resize(context, int(migration_ref['id']), {}) except KeyError, e: # Only catch key errors. We want other reasons for the test to # fail to actually error out so we don't obscure anything @@ -441,14 +441,15 @@ class ComputeTestCase(test.TestCase): """Ensure notifications on instance migrate/resize""" instance_id = self._create_instance() context = self.context.elevated() + inst_ref = db.instance_get(context, instance_id) self.compute.run_instance(self.context, instance_id) test_notifier.NOTIFICATIONS = [] db.instance_update(self.context, instance_id, {'host': 'foo'}) - self.compute.prep_resize(context, instance_id, 1) + self.compute.prep_resize(context, inst_ref['uuid'], 1) migration_ref = db.migration_get_by_instance_and_status(context, - instance_id, 'pre-migrating') + inst_ref['uuid'], 'pre-migrating') self.assertEquals(len(test_notifier.NOTIFICATIONS), 1) msg = test_notifier.NOTIFICATIONS[0] @@ -471,14 +472,15 @@ class ComputeTestCase(test.TestCase): """Ensure instance can be migrated/resized""" instance_id = self._create_instance() context = self.context.elevated() + inst_ref = db.instance_get(context, instance_id) self.compute.run_instance(self.context, instance_id) - db.instance_update(self.context, instance_id, {'host': 'foo'}) - self.compute.prep_resize(context, instance_id, 1) + db.instance_update(self.context, inst_ref['uuid'], + {'host': 'foo'}) + self.compute.prep_resize(context, inst_ref['uuid'], 1) migration_ref = db.migration_get_by_instance_and_status(context, - instance_id, 'pre-migrating') - self.compute.resize_instance(context, instance_id, - migration_ref['id']) + inst_ref['uuid'], 'pre-migrating') + self.compute.resize_instance(context, migration_ref['id']) self.compute.terminate_instance(context, instance_id) def test_resize_invalid_flavor_fails(self): -- cgit From c8a35349be912bb862789059abc95ccf9f7b8ef5 Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Fri, 15 Jul 2011 19:07:58 +0000 Subject: Updated with some changes from manual testing --- nova/compute/api.py | 14 +++++++++----- nova/compute/manager.py | 21 +++++++++++++++------ nova/exception.py | 2 +- 3 files changed, 25 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 7857f06a5..2233b9225 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -826,15 +826,16 @@ class API(base.Base): def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process.""" context = context.elevated() + instance_ref = self._get_instance(context, instance_id, 'confirm_resize') migration_ref = self.db.migration_get_by_instance_and_status(context, - instance_id, 'finished') + instance_ref['uuid'], 'finished') if not migration_ref: raise exception.MigrationNotFoundByStatus(instance_id=instance_id, status='finished') params = {'migration_id': migration_ref['id']} self._cast_compute_message('revert_resize', context, - migration_ref['dest_compute'], params=params) + instance_ref['uuid'], params=params) self.db.migration_update(context, migration_ref['id'], {'status': 'reverted'}) @@ -842,14 +843,17 @@ class API(base.Base): def confirm_resize(self, context, instance_id): """Confirms a migration/resize and deletes the 'old' instance.""" context = context.elevated() + + instance_ref = self._get_instance(context, instance_id, 'confirm_resize') migration_ref = self.db.migration_get_by_instance_and_status(context, - instance_id, 'finished') + instance_ref['uuid'], 'finished') if not migration_ref: raise exception.MigrationNotFoundByStatus(instance_id=instance_id, status='finished') params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, - migration_ref['source_compute'], params=params) + instance_ref['uuid'], + params=params) self.db.migration_update(context, migration_ref['id'], {'status': 'confirmed'}) @@ -897,7 +901,7 @@ class API(base.Base): self._cast_scheduler_message(context, {"method": "prep_resize", "args": {"topic": FLAGS.compute_topic, - "instance_uuid": instance_ref['uuid'], + "instance_id": instance_ref['uuid'], "flavor_id": new_instance_type['id']}}) @scheduler_api.reroute_compute("add_fixed_ip") diff --git a/nova/compute/manager.py b/nova/compute/manager.py index e00e3d0ab..55bc1cdcd 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -89,6 +89,10 @@ def checks_instance_lock(function): """Decorator to prevent action against locked instances for non-admins.""" @functools.wraps(function) def decorated_function(self, context, instance_id, *args, **kwargs): + #TODO(anyone): this being called instance_id is forcing a slightly + # confusing convention of pushing instance_uuids + # through an "instance_id" key in the queue args dict when + # casting through the compute API LOG.info(_("check_instance_lock: decorating: |%s|"), function, context=context) LOG.info(_("check_instance_lock: arguments: |%(self)s| |%(context)s|" @@ -664,10 +668,10 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def confirm_resize(self, context, migration_id): + def confirm_resize(self, context, instance_id, migration_id): """Destroys the source instance.""" migration_ref = self.db.migration_get(context, migration_id) - instance_ref = self.db.instance_get(context, + instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) self.driver.destroy(instance_ref) @@ -679,7 +683,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def revert_resize(self, context, migration_id): + def revert_resize(self, context, instance_id, migration_id): """Destroys the new instance on the destination machine. Reverts the model changes, and powers on the old instance on the @@ -687,7 +691,7 @@ class ComputeManager(manager.SchedulerDependentManager): """ migration_ref = self.db.migration_get(context, migration_id) - instance_ref = self.db.instance_get(context, + instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) self.driver.destroy(instance_ref) @@ -732,14 +736,19 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def prep_resize(self, context, instance_uuid, flavor_id): + def prep_resize(self, context, instance_id, flavor_id): """Initiates the process of moving a running instance to another host. Possibly changes the RAM and disk size in the process. """ context = context.elevated() - instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) + + # Because of checks_instance_lock, this must currently be called + # instance_id. However, the compute API is always passing the UUID + # of the instance down + 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!')) diff --git a/nova/exception.py b/nova/exception.py index 988940d6a..a98b0a36b 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -77,8 +77,8 @@ def wrap_db_error(f): except Exception, e: LOG.exception(_('DB exception wrapped.')) raise DBError(e) - return _wrap _wrap.func_name = f.func_name + return _wrap def wrap_exception(f): -- cgit From 435c205ddc04c5d5c33d23a326ade38dab8ed943 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Fri, 15 Jul 2011 14:20:44 -0500 Subject: One last nit --- nova/compute/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 9788f6147..0e41e2a19 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -712,7 +712,7 @@ class ComputeManager(manager.SchedulerDependentManager): """ migration_ref = self.db.migration_get(context, migration_id) - instance_ref = self.db.instance_get(context, + instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) instance_type = self.db.instance_type_get_by_flavor_id(context, @@ -720,7 +720,7 @@ 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.instance_uuid, + 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'])) -- cgit From 67e5492d6723a00b0ad5d7e8c44f5762a9b0a206 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 18 Jul 2011 12:16:10 -0700 Subject: fix issues that were breaking vlan mode --- nova/db/sqlalchemy/api.py | 4 +--- nova/network/manager.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 8b7ab8cbd..a90b61e39 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1480,8 +1480,6 @@ def network_associate(context, project_id, force=False): called by project_get_networks under certain conditions and network manager add_network_to_project() - only associates projects with networks that have configured hosts - only associate if the project doesn't already have a network or if force is True @@ -1497,7 +1495,6 @@ def network_associate(context, project_id, force=False): def network_query(project_filter): return session.query(models.Network).\ filter_by(deleted=False).\ - filter(models.Network.host != None).\ filter_by(project_id=project_filter).\ with_lockmode('update').\ first() @@ -1745,6 +1742,7 @@ def network_update(context, network_id, values): network_ref = network_get(context, network_id, session=session) network_ref.update(values) network_ref.save(session=session) + return network_ref ################### diff --git a/nova/network/manager.py b/nova/network/manager.py index dac42e7ec..4568d0fa7 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -870,7 +870,7 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): net = {} address = FLAGS.vpn_ip net['vpn_public_address'] = address - network_ref = db.network_update(context, network_ref, net) + network_ref = db.network_update(context, network_ref['id'], net) else: address = network_ref['vpn_public_address'] self.driver.ensure_vlan_bridge(network_ref['vlan'], -- cgit From 55de6a262f40024c2c9c8f7c6e84c56eaa14d206 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 18 Jul 2011 17:34:36 -0400 Subject: adding fixed_ipv6 property to VirtualInterface model; exposing ipv6 in api --- nova/api/openstack/views/addresses.py | 22 ++++++++++++++++++---- nova/db/sqlalchemy/api.py | 2 +- nova/db/sqlalchemy/models.py | 13 +++++++++++++ nova/tests/api/openstack/test_servers.py | 6 ++++++ 4 files changed, 38 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index a242efa45..77310e4b1 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -50,22 +50,36 @@ class ViewBuilderV11(ViewBuilder): if network_label not in networks: networks[network_label] = [] - networks[network_label].extend(self._extract_ipv4(interface)) + ip_addresses = list(self._extract_ipv4_addresses(interface)) + + ipv6_address = self._extract_ipv6_address(interface) + if ipv6_address is not None: + ip_addresses.append(ipv6_address) + + networks[network_label].extend(ip_addresses) return networks def build_network(self, interfaces, network_label): for interface in interfaces: if interface['network']['label'] == network_label: - ips = self._extract_ipv4(interface) - return {network_label: list(ips)} + ips = list(self._extract_ipv4_addresses(interface)) + ipv6 = self._extract_ipv6_address(interface) + if ipv6 is not None: + ips.append(ipv6) + return {network_label: ips} return None - def _extract_ipv4(self, interface): + def _extract_ipv4_addresses(self, interface): for fixed_ip in interface['fixed_ips']: yield self._build_ip_entity(fixed_ip['address'], 4) for floating_ip in fixed_ip.get('floating_ips', []): yield self._build_ip_entity(floating_ip['address'], 4) + def _extract_ipv6_address(self, interface): + fixed_ipv6 = interface.get('fixed_ipv6') + if fixed_ipv6 is not None: + return self._build_ip_entity(fixed_ipv6, 6) + def _build_ip_entity(self, address, version): return {'addr': address, 'version': version} diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index a831516a8..8a4bd11b7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -949,8 +949,8 @@ def virtual_interface_get_by_instance(context, instance_id): options(joinedload('instance')).\ options(joinedload('fixed_ips')).\ all() - return vif_refs + return vif_refs @require_context def virtual_interface_get_by_instance_and_network(context, instance_id, diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index d29d3d6f1..def33d056 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -31,6 +31,7 @@ from nova.db.sqlalchemy.session import get_session from nova import auth from nova import exception from nova import flags +from nova import ipv6 from nova import utils @@ -575,6 +576,18 @@ class VirtualInterface(BASE, NovaBase): instance_id = Column(Integer, ForeignKey('instances.id'), nullable=False) instance = relationship(Instance, backref=backref('virtual_interfaces')) + @property + def fixed_ipv6(self): + cidr_v6 = self.network.cidr_v6 + if cidr_v6 is None: + ipv6_address = None + else: + project_id = self.instance.project_id + mac = self.address + ipv6_address = ipv6.to_global(cidr_v6, mac, project_id) + + return ipv6_address + # TODO(vish): can these both come from the same baseclass? class FixedIp(BASE, NovaBase): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 1577c922b..467aa907c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -447,6 +447,7 @@ class ServersTest(test.TestCase): {'address': '172.19.0.1'}, {'address': '172.19.0.2'}, ], + 'fixed_ipv6': '2001:4860::12', }, ] new_return_server = return_server_with_interfaces(interfaces) @@ -467,6 +468,7 @@ class ServersTest(test.TestCase): 'network_2': [ {'addr': '172.19.0.1', 'version': 4}, {'addr': '172.19.0.2', 'version': 4}, + {'addr': '2001:4860::12', 'version': 6}, ], } @@ -492,6 +494,7 @@ class ServersTest(test.TestCase): }, {'address': '172.19.0.2'}, ], + 'fixed_ipv6': '2001:4860::12', }, ] @@ -514,6 +517,7 @@ class ServersTest(test.TestCase): {'version': 4, 'addr': '172.19.0.1'}, {'version': 4, 'addr': '1.2.3.4'}, {'version': 4, 'addr': '172.19.0.2'}, + {'version': 6, 'addr': '2001:4860::12'}, ], }, } @@ -540,6 +544,7 @@ class ServersTest(test.TestCase): }, {'address': '172.19.0.2'}, ], + 'fixed_ipv6': '2001:4860::12', }, ] _return_vifs = return_virtual_interface_by_instance(interfaces) @@ -556,6 +561,7 @@ class ServersTest(test.TestCase): {'version': 4, 'addr': '172.19.0.1'}, {'version': 4, 'addr': '1.2.3.4'}, {'version': 4, 'addr': '172.19.0.2'}, + {'version': 6, 'addr': '2001:4860::12'}, ], } self.assertEqual(res_dict, expected) -- cgit From acf3e24c4b6d782543c1b8cdc4d25c9d129ecb95 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 18 Jul 2011 17:45:39 -0400 Subject: pep8 cleanup --- nova/db/sqlalchemy/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 8a4bd11b7..a831516a8 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -949,9 +949,9 @@ def virtual_interface_get_by_instance(context, instance_id): options(joinedload('instance')).\ options(joinedload('fixed_ips')).\ all() - return vif_refs + @require_context def virtual_interface_get_by_instance_and_network(context, instance_id, network_id): -- cgit From 8bc0dcc54a7218bd9f567c5719718f540f6ac549 Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Mon, 18 Jul 2011 22:16:38 +0000 Subject: Some missed instance_id casts --- nova/compute/manager.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0e41e2a19..1feb96faa 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -770,7 +770,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref['host']) rpc.cast(context, topic, {'method': 'resize_instance', - 'args': {'migration_id': migration_ref['id']}, + 'args': { 'instance_id': instance_ref['uuid'], + 'migration_id': migration_ref['id']}, }) usage_info = utils.usage_from_instance(instance_ref, new_instance_type=instance_type['name'], @@ -782,7 +783,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def resize_instance(self, context, migration_id): + def resize_instance(self, context, instance_id, migration_id): """Starts the migration of a running instance to another host.""" migration_ref = self.db.migration_get(context, migration_id) instance_ref = self.db.instance_get_by_uuid(context, @@ -804,13 +805,14 @@ class ComputeManager(manager.SchedulerDependentManager): FLAGS.compute_topic, migration_ref['dest_compute']) params = {'migration_id': migration_id, - 'disk_info': disk_info} + 'disk_info': disk_info, + 'instance_id': instance_ref['uuid']} rpc.cast(context, topic, {'method': 'finish_resize', 'args': params}) @exception.wrap_exception @checks_instance_lock - def finish_resize(self, context, migration_id, disk_info): + def finish_resize(self, context, instance_id, migration_id, disk_info): """Completes the migration process. Sets up the newly transferred disk and turns on the instance at its -- cgit From 115727a7cd41e703b35b6b061b64d097b9bbbf1d Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Mon, 18 Jul 2011 17:58:33 -0500 Subject: Fixed the broken tests again --- nova/compute/manager.py | 5 ++--- nova/tests/test_compute.py | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 1feb96faa..595e2e334 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -770,9 +770,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref['host']) rpc.cast(context, topic, {'method': 'resize_instance', - 'args': { 'instance_id': instance_ref['uuid'], - 'migration_id': migration_ref['id']}, - }) + 'args': {'instance_id': instance_ref['uuid'], + 'migration_id': migration_ref['id']}}) usage_info = utils.usage_from_instance(instance_ref, new_instance_type=instance_type['name'], new_instance_type_id=instance_type['id']) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 0586cc40d..a4adaee17 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -429,7 +429,8 @@ class ComputeTestCase(test.TestCase): migration_ref = db.migration_get_by_instance_and_status(context, instance_ref['uuid'], 'pre-migrating') try: - self.compute.finish_resize(context, int(migration_ref['id']), {}) + self.compute.finish_resize(context, instance_ref['uuid'], + int(migration_ref['id']), {}) except KeyError, e: # Only catch key errors. We want other reasons for the test to # fail to actually error out so we don't obscure anything @@ -480,7 +481,8 @@ class ComputeTestCase(test.TestCase): self.compute.prep_resize(context, inst_ref['uuid'], 1) migration_ref = db.migration_get_by_instance_and_status(context, inst_ref['uuid'], 'pre-migrating') - self.compute.resize_instance(context, migration_ref['id']) + self.compute.resize_instance(context, inst_ref['uuid'], + migration_ref['id']) self.compute.terminate_instance(context, instance_id) def test_resize_invalid_flavor_fails(self): -- cgit From 87434352e7dc165d269b5831af8ff690fcdbdb5b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 18 Jul 2011 17:17:30 -0700 Subject: change migration number --- .../migrate_repo/versions/032_ha_network.py | 37 ---------------------- .../migrate_repo/versions/033_ha_network.py | 37 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 37 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/032_ha_network.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/033_ha_network.py (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/032_ha_network.py b/nova/db/sqlalchemy/migrate_repo/versions/032_ha_network.py deleted file mode 100644 index 316c36cbc..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/032_ha_network.py +++ /dev/null @@ -1,37 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 MORITA Kazutaka. -# 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 Column, Table, MetaData, Boolean, String - -meta = MetaData() - -fixed_ips_host = Column('host', String(255)) - -networks_multi_host = Column('multi_host', Boolean, default=False) - - -def upgrade(migrate_engine): - # Upgrade operations go here. Don't create your own engine; - # bind migrate_engine to your metadata - meta.bind = migrate_engine - - # Add columns to existing tables - fixed_ips = Table('fixed_ips', meta, autoload=True) - fixed_ips.create_column(fixed_ips_host) - - networks = Table('networks', meta, autoload=True) - networks.create_column(networks_multi_host) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/033_ha_network.py b/nova/db/sqlalchemy/migrate_repo/versions/033_ha_network.py new file mode 100644 index 000000000..316c36cbc --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/033_ha_network.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 MORITA Kazutaka. +# 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 Column, Table, MetaData, Boolean, String + +meta = MetaData() + +fixed_ips_host = Column('host', String(255)) + +networks_multi_host = Column('multi_host', Boolean, default=False) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + + # Add columns to existing tables + fixed_ips = Table('fixed_ips', meta, autoload=True) + fixed_ips.create_column(fixed_ips_host) + + networks = Table('networks', meta, autoload=True) + networks.create_column(networks_multi_host) -- cgit From 89ead61d521c8f585732d50b8d3d31361d7c1d82 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 18 Jul 2011 17:26:38 -0700 Subject: fix bad merge --- nova/network/manager.py | 8 -------- 1 file changed, 8 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index a411ad9ff..360e08ceb 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -776,14 +776,6 @@ class FlatDHCPManager(FloatingIP, RPCAllocateFixedIP, NetworkManager): self.driver.metadata_forward() - def allocate_fixed_ip(self, context, instance_id, network, **kwargs): - """Allocate flat_network fixed_ip, then setup dhcp for this network.""" - address = super(FlatDHCPManager, self).allocate_fixed_ip(context, - instance_id, - network) - if not FLAGS.fake_network: - self.driver.update_dhcp(context, network['id']) - def setup_compute_network(self, context, instance_id): """Sets up matching networks for compute hosts. -- cgit From 39701e13188275fc2f798a790e2edc4488ecb3e4 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 18 Jul 2011 17:35:14 -0700 Subject: missed the vpn kwarg in rpc --- nova/network/manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 360e08ceb..7a7520f5d 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -156,12 +156,13 @@ class RPCAllocateFixedIP(object): # wait for all of the allocates (if any) to finish green_pool.waitall() - def _rpc_allocate_fixed_ip(self, context, instance_id, network_id): + def _rpc_allocate_fixed_ip(self, context, instance_id, network_id, + **kwargs): """Sits in between _allocate_fixed_ips and allocate_fixed_ip to perform network lookup on the far side of rpc. """ network = self.db.network_get(context, network_id) - self.allocate_fixed_ip(context, instance_id, network) + self.allocate_fixed_ip(context, instance_id, network, **kwargs) class FloatingIP(object): -- cgit From 82e2eeb5a097f1c3c6cb56fc3dfa862575f5da9a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Jul 2011 09:36:37 -0400 Subject: respecting use_ipv6 flag if set to False --- nova/api/openstack/views/addresses.py | 10 ++++--- nova/tests/api/openstack/test_servers.py | 45 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index 77310e4b1..ddbf7a144 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -15,9 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. +from nova import flags from nova import utils from nova.api.openstack import common +FLAGS = flags.FLAGS + class ViewBuilder(object): """Models a server addresses response as a python dictionary.""" @@ -52,9 +55,10 @@ class ViewBuilderV11(ViewBuilder): ip_addresses = list(self._extract_ipv4_addresses(interface)) - ipv6_address = self._extract_ipv6_address(interface) - if ipv6_address is not None: - ip_addresses.append(ipv6_address) + if FLAGS.use_ipv6: + ipv6_address = self._extract_ipv6_address(interface) + if ipv6_address is not None: + ip_addresses.append(ipv6_address) networks[network_label].extend(ip_addresses) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 467aa907c..3fc38b73c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -433,6 +433,7 @@ class ServersTest(test.TestCase): self.assertEquals(ip.getAttribute('addr'), private) def test_get_server_by_id_with_addresses_v1_1(self): + FLAGS.use_ipv6 = True interfaces = [ { 'network': {'label': 'network_1'}, @@ -474,7 +475,50 @@ class ServersTest(test.TestCase): self.assertEqual(addresses, expected) + def test_get_server_by_id_with_addresses_v1_1_ipv6_disabled(self): + FLAGS.use_ipv6 = False + interfaces = [ + { + 'network': {'label': 'network_1'}, + 'fixed_ips': [ + {'address': '192.168.0.3'}, + {'address': '192.168.0.4'}, + ], + }, + { + 'network': {'label': 'network_2'}, + 'fixed_ips': [ + {'address': '172.19.0.1'}, + {'address': '172.19.0.2'}, + ], + 'fixed_ipv6': '2001:4860::12', + }, + ] + new_return_server = return_server_with_interfaces(interfaces) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + + req = webob.Request.blank('/v1.1/servers/1') + res = req.get_response(fakes.wsgi_app()) + + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['id'], 1) + self.assertEqual(res_dict['server']['name'], 'server1') + addresses = res_dict['server']['addresses'] + expected = { + 'network_1': [ + {'addr': '192.168.0.3', 'version': 4}, + {'addr': '192.168.0.4', 'version': 4}, + ], + 'network_2': [ + {'addr': '172.19.0.1', 'version': 4}, + {'addr': '172.19.0.2', 'version': 4}, + ], + } + + self.assertEqual(addresses, expected) + def test_get_server_addresses_v1_1(self): + FLAGS.use_ipv6 = True interfaces = [ { 'network': {'label': 'network_1'}, @@ -525,6 +569,7 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict, expected) def test_get_server_addresses_single_network_v1_1(self): + FLAGS.use_ipv6 = True interfaces = [ { 'network': {'label': 'network_1'}, -- cgit From 7e204b9df840c597a137b605a0d640222a5b97b6 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Tue, 19 Jul 2011 11:25:33 -0500 Subject: Beginnings of the patch --- nova/compute/manager.py | 4 +++- nova/tests/test_compute.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 960dfea54..0c82b155b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -743,6 +743,8 @@ class ComputeManager(manager.SchedulerDependentManager): raise exception.Error(_( 'Migration error: destination same as source!')) + old_instance_type = self.db.instance_type_get_by_flavor_id(context, + instance_ref['instance_type_id']) instance_type = self.db.instance_type_get_by_flavor_id(context, flavor_id) migration_ref = self.db.migration_create(context, @@ -750,7 +752,7 @@ class ComputeManager(manager.SchedulerDependentManager): 'source_compute': instance_ref['host'], 'dest_compute': FLAGS.host, 'dest_host': self.driver.get_host_ip_addr(), - 'old_flavor_id': instance_type['flavorid'], + 'old_flavor_id': old_instance_type['flavorid'], 'new_flavor_id': flavor_id, 'status': 'pre-migrating'}) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 04bb194d5..6abdd173c 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -519,6 +519,17 @@ class ComputeTestCase(test.TestCase): self.compute.terminate_instance(context, instance_id) + def test_finish_revert_resize(self): + """Ensure that the flavor is reverted to the original on revert""" + context = self.context.elevated() + instance_id = self._create_instance() + + self.compute.run_instance(self.context, instance_id) + + self.compute_api.finish_revert_resize(context, instance_id, 1) + + self.compute.terminate_instance(context, instance_id) + def test_get_by_flavor_id(self): type = instance_types.get_instance_type_by_flavor_id(1) self.assertEqual(type['name'], 'm1.tiny') -- cgit From 9febf1eec69708d8c54b6348e882da384d8b42a9 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 19 Jul 2011 12:26:41 -0400 Subject: correct broken logic for lxc and uml to avoid adding vnc arguments (LP: #812553) This fixes the logic, so that lxc and uml will not get vnc arguments added to their libvirt xml. To also seems more readable to me. --- nova/virt/libvirt/connection.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 977bb7dfe..56aaa6c50 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1014,8 +1014,7 @@ class LibvirtConnection(driver.ComputeDriver): 'ebs_root': ebs_root, 'volumes': block_device_mapping} - if FLAGS.vnc_enabled: - if FLAGS.libvirt_type != 'lxc' or FLAGS.libvirt_type != 'uml': + if FLAGS.vnc_enabled and FLAGS.libvirt_type not in ('lxc', 'uml'): xml_info['vncserver_host'] = FLAGS.vncserver_host xml_info['vnc_keymap'] = FLAGS.vnc_keymap if not rescue: -- cgit From cfa755c6f3bbcc3dd376d66b3722021ad4b4ec62 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Jul 2011 10:00:00 -0700 Subject: add downgrade --- .../db/sqlalchemy/migrate_repo/versions/033_ha_network.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/033_ha_network.py b/nova/db/sqlalchemy/migrate_repo/versions/033_ha_network.py index 316c36cbc..3a5f7eba8 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/033_ha_network.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/033_ha_network.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2011 MORITA Kazutaka. +# Copyright (c) 2011 OpenStack, LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -25,13 +25,20 @@ networks_multi_host = Column('multi_host', Boolean, default=False) def upgrade(migrate_engine): - # Upgrade operations go here. Don't create your own engine; - # bind migrate_engine to your metadata meta.bind = migrate_engine - # Add columns to existing tables fixed_ips = Table('fixed_ips', meta, autoload=True) fixed_ips.create_column(fixed_ips_host) networks = Table('networks', meta, autoload=True) networks.create_column(networks_multi_host) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + fixed_ips = Table('fixed_ips', meta, autoload=True) + fixed_ips.drop_column(fixed_ips_host) + + networks = Table('networks', meta, autoload=True) + networks.drop_column(networks_multi_host) -- cgit From 36f8bd801bc8a7696e7850613747a5f7ae8c487a Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Tue, 19 Jul 2011 10:19:10 -0700 Subject: Fix unchecked key reference to mappings['gateway6']. Fixes LP #807764. --- nova/virt/libvirt/connection.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 977bb7dfe..572be38d8 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -881,9 +881,12 @@ class LibvirtConnection(driver.ComputeDriver): address = mapping['ips'][0]['ip'] netmask = mapping['ips'][0]['netmask'] address_v6 = None + gateway_v6 = None + netmask_v6 = None if FLAGS.use_ipv6: address_v6 = mapping['ip6s'][0]['ip'] netmask_v6 = mapping['ip6s'][0]['netmask'] + gateway_v6 = mapping['gateway6'] net_info = {'name': 'eth%d' % ifc_num, 'address': address, 'netmask': netmask, @@ -891,7 +894,7 @@ class LibvirtConnection(driver.ComputeDriver): 'broadcast': mapping['broadcast'], 'dns': mapping['dns'], 'address_v6': address_v6, - 'gateway6': mapping['gateway6'], + 'gateway6': gateway_v6, 'netmask_v6': netmask_v6} nets.append(net_info) -- cgit From d8132fd792d3316420033435a43604f7e09cfcdb Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Tue, 19 Jul 2011 14:29:44 -0500 Subject: better handling of periodic network host setup --- nova/network/manager.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 24736f53d..33cf86e37 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -334,21 +334,21 @@ class NetworkManager(manager.SchedulerDependentManager): self.host) if host == self.host: self._on_set_network_host(context, network_id) - return host def set_network_hosts(self, context): """Set the network hosts for any networks which are unset.""" try: networks = self.db.network_get_all(context) except exception.NoNetworksFound: - # we don't care if no networks are found - pass + # no networks found, nothing to do + return for network in networks: host = network['host'] if not host: - # return so worker will only grab 1 (to help scale flatter) - return self.set_network_host(context, network['id']) + # break so worker will only grab 1 (to help scale flatter) + self.set_network_host(context, network['id']) + break def _get_networks_for_instance(self, context, instance_id, project_id): """Determine & return which networks an instance should connect to.""" -- cgit From b2637c282fba3d542c4e157e3e5e22046d28bb29 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Tue, 19 Jul 2011 15:21:39 -0500 Subject: Functionality fixed and new test passing --- nova/compute/manager.py | 12 ++++++++---- nova/tests/test_compute.py | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index b91e6b9af..eb3996d29 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -720,7 +720,8 @@ class ComputeManager(manager.SchedulerDependentManager): self.db.instance_update(context, instance_id, dict(memory_mb=instance_type['memory_mb'], vcpus=instance_type['vcpus'], - local_gb=instance_type['local_gb'])) + local_gb=instance_type['local_gb'], + instance_type_id=instance_type['id'])) self.driver.revert_resize(instance_ref) self.db.migration_update(context, migration_id, @@ -741,14 +742,14 @@ class ComputeManager(manager.SchedulerDependentManager): """ context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) + if instance_ref['host'] == FLAGS.host: raise exception.Error(_( 'Migration error: destination same as source!')) - old_instance_type = self.db.instance_type_get_by_flavor_id(context, + old_instance_type = self.db.instance_type_get_by_id(context, instance_ref['instance_type_id']) - instance_type = self.db.instance_type_get_by_flavor_id(context, - flavor_id) + migration_ref = self.db.migration_create(context, {'instance_id': instance_id, 'source_compute': instance_ref['host'], @@ -768,6 +769,9 @@ class ComputeManager(manager.SchedulerDependentManager): 'migration_id': migration_ref['id'], 'instance_id': instance_id, }, }) + + instance_type = self.db.instance_type_get_by_flavor_id(context, + flavor_id) usage_info = utils.usage_from_instance(instance_ref, new_instance_type=instance_type['name'], new_instance_type_id=instance_type['id']) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index d5de99956..dc3f0596d 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -524,9 +524,49 @@ class ComputeTestCase(test.TestCase): context = self.context.elevated() instance_id = self._create_instance() + def fake(*args, **kwargs): + pass + + self.stubs.Set(self.compute.driver, 'finish_resize', fake) + self.stubs.Set(self.compute.driver, 'revert_resize', fake) + self.stubs.Set(self.compute.network_api, 'get_instance_nw_info', fake) + self.compute.run_instance(self.context, instance_id) - self.compute_api.finish_revert_resize(context, instance_id, 1) + # Confirm the instance size before the resize starts + inst_ref = db.instance_get(context, instance_id) + instance_type_ref = db.instance_type_get_by_id(context, + inst_ref['instance_type_id']) + self.assertEqual(instance_type_ref['flavorid'], 1) + + db.instance_update(self.context, instance_id, {'host': 'foo'}) + + self.compute.prep_resize(context, instance_id, 3) + + migration_ref = db.migration_get_by_instance_and_status(context, + instance_id, 'pre-migrating') + + self.compute.resize_instance(context, instance_id, + migration_ref['id']) + self.compute.finish_resize(context, instance_id, + int(migration_ref['id']), {}) + + # Prove that the instance size is now the new size + inst_ref = db.instance_get(context, instance_id) + instance_type_ref = db.instance_type_get_by_id(context, + inst_ref['instance_type_id']) + self.assertEqual(instance_type_ref['flavorid'], 3) + + # Finally, revert and confirm the old flavor has been applied + self.compute.revert_resize(context, instance_id, + migration_ref['id']) + self.compute.finish_revert_resize(context, instance_id, + migration_ref['id']) + + inst_ref = db.instance_get(context, instance_id) + instance_type_ref = db.instance_type_get_by_id(context, + inst_ref['instance_type_id']) + self.assertEqual(instance_type_ref['flavorid'], 1) self.compute.terminate_instance(context, instance_id) -- cgit From ecb68bf77565cb01ec0ea4d28c7f1315e10b21c4 Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 19 Jul 2011 13:49:05 -0700 Subject: network api release_floating_ip method checks if an instance associated to the floating prior to releasing. added test --- nova/network/api.py | 3 +++ nova/tests/test_cloud.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) (limited to 'nova') diff --git a/nova/network/api.py b/nova/network/api.py index 70b1099f0..f03081be4 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -61,6 +61,9 @@ class API(base.Base): affect_auto_assigned=False): """Removes floating ip with address from a project.""" floating_ip = self.db.floating_ip_get_by_address(context, address) + if floating_ip['fixed_ip']: + raise exception.ApiError(_('Floating ip is in use. ' + 'Disassociate it before releasing.')) if not affect_auto_assigned and floating_ip.get('auto_assigned'): return # NOTE(vish): We don't know which network host should get the ip diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index d71a03aff..557e3f89b 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -15,6 +15,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 mox from base64 import b64decode from M2Crypto import BIO @@ -131,6 +132,34 @@ class CloudTestCase(test.TestCase): allocate, self.context) + def test_release_address(self): + address = "10.10.10.10" + allocate = self.cloud.allocate_address + db.floating_ip_create(self.context, + {'address': address, + 'host': self.network.host}) + result = self.cloud.release_address(self.context, address) + self.assertEqual(result['releaseResponse'], ['Address released.']) + + def test_release_address_still_associated(self): + address = "10.10.10.10" + fixed_ip = {'instance': {'id': 1}} + floating_ip = {'id': 0, + 'address': address, + 'fixed_ip_id': 0, + 'fixed_ip': fixed_ip, + 'project_id': None, + 'auto_assigned': False} + from nova import network + network_api = network.api.API() + self.mox.StubOutWithMock(network_api.db, 'floating_ip_get_by_address') + network_api.db.floating_ip_get_by_address(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(floating_ip) + self.mox.ReplayAll() + release = self.cloud.release_address + # ApiError: Floating ip is in use. Disassociate it before releasing. + self.assertRaises(exception.ApiError, release, self.context, address) + @test.skip_test("Skipping this pending future merge") def test_associate_disassociate_address(self): """Verifies associate runs cleanly without raising an exception""" -- cgit From 1d4a789ed370fe0cc00c292f89b96b0ffaf115ff Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 19 Jul 2011 14:16:14 -0700 Subject: move import network to the top --- nova/tests/test_cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 46e0ec68c..8cdc73a66 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -30,6 +30,7 @@ from nova import db from nova import exception from nova import flags from nova import log as logging +from nova import network from nova import rpc from nova import test from nova import utils @@ -151,7 +152,6 @@ class CloudTestCase(test.TestCase): 'fixed_ip': fixed_ip, 'project_id': None, 'auto_assigned': False} - from nova import network network_api = network.api.API() self.mox.StubOutWithMock(network_api.db, 'floating_ip_get_by_address') network_api.db.floating_ip_get_by_address(mox.IgnoreArg(), -- cgit From 45bdf5cc27c9389255d32ad189a561b967b8f89e Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Tue, 19 Jul 2011 16:25:16 -0500 Subject: Fixes lp813006 - inconsistent DB API naming --- nova/compute/instance_types.py | 2 +- nova/db/api.py | 4 ++-- nova/db/sqlalchemy/api.py | 2 +- nova/network/manager.py | 3 +-- nova/tests/db/fakes.py | 8 ++++---- nova/tests/test_instance_types_extra_specs.py | 8 ++++---- nova/tests/test_network.py | 4 ++-- nova/virt/libvirt/netutils.py | 2 +- 8 files changed, 16 insertions(+), 17 deletions(-) (limited to 'nova') diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 1d246e445..c13a629a9 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -112,7 +112,7 @@ def get_instance_type(id): return get_default_instance_type() try: ctxt = context.get_admin_context() - return db.instance_type_get_by_id(ctxt, id) + return db.instance_type_get(ctxt, id) except exception.DBError: raise exception.ApiError(_("Unknown instance type: %s") % id) diff --git a/nova/db/api.py b/nova/db/api.py index cb4da169c..ca7064333 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1305,9 +1305,9 @@ def instance_type_get_all(context, inactive=False): return IMPL.instance_type_get_all(context, inactive) -def instance_type_get_by_id(context, id): +def instance_type_get(context, id): """Get instance type by id.""" - return IMPL.instance_type_get_by_id(context, id) + return IMPL.instance_type_get(context, id) def instance_type_get_by_name(context, name): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 189be0714..04b16ba1a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2985,7 +2985,7 @@ def instance_type_get_all(context, inactive=False): @require_context -def instance_type_get_by_id(context, id): +def instance_type_get(context, id): """Returns a dict describing specific instance_type""" session = get_session() inst_type = session.query(models.InstanceTypes).\ diff --git a/nova/network/manager.py b/nova/network/manager.py index 24736f53d..963a3cbea 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -413,8 +413,7 @@ class NetworkManager(manager.SchedulerDependentManager): # TODO(tr3buchet) should handle floating IPs as well? fixed_ips = self.db.fixed_ip_get_by_instance(context, instance_id) vifs = self.db.virtual_interface_get_by_instance(context, instance_id) - flavor = self.db.instance_type_get_by_id(context, - instance_type_id) + flavor = self.db.instance_type_get(context, instance_type_id) network_info = [] # a vif has an address, instance_id, and network_id # it is also joined to the instance and network given by those IDs diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 7762df41c..19028a451 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -230,7 +230,7 @@ def stub_out_db_network_api(stubs): continue fixed_ip_fields['virtual_interface'] = FakeModel(vif[0]) - def fake_instance_type_get_by_id(context, id): + def fake_instance_type_get(context, id): if flavor_fields['id'] == id: return FakeModel(flavor_fields) @@ -323,7 +323,7 @@ def stub_out_db_network_api(stubs): fake_fixed_ip_get_by_address, fake_fixed_ip_get_network, fake_fixed_ip_update, - fake_instance_type_get_by_id, + fake_instance_type_get, fake_virtual_interface_create, fake_virtual_interface_delete_by_instance, fake_virtual_interface_get_by_instance, @@ -415,7 +415,7 @@ def stub_out_db_instance_api(stubs, injected=True): def fake_instance_type_get_by_name(context, name): return INSTANCE_TYPES[name] - def fake_instance_type_get_by_id(context, id): + def fake_instance_type_get(context, id): for name, inst_type in INSTANCE_TYPES.iteritems(): if str(inst_type['id']) == str(id): return inst_type @@ -448,7 +448,7 @@ def stub_out_db_instance_api(stubs, injected=True): fake_network_get_all_by_instance, fake_instance_type_get_all, fake_instance_type_get_by_name, - fake_instance_type_get_by_id, + fake_instance_type_get, fake_instance_get_fixed_addresses, fake_instance_get_fixed_addresses_v6, fake_network_get_all_by_instance, diff --git a/nova/tests/test_instance_types_extra_specs.py b/nova/tests/test_instance_types_extra_specs.py index c26cf82ff..393ed1e36 100644 --- a/nova/tests/test_instance_types_extra_specs.py +++ b/nova/tests/test_instance_types_extra_specs.py @@ -105,8 +105,8 @@ class InstanceTypeExtraSpecsTestCase(test.TestCase): self.instance_type_id) self.assertEquals(expected_specs, actual_specs) - def test_instance_type_get_by_id_with_extra_specs(self): - instance_type = db.api.instance_type_get_by_id( + def test_instance_type_get_with_extra_specs(self): + instance_type = db.api.instance_type_get( context.get_admin_context(), self.instance_type_id) self.assertEquals(instance_type['extra_specs'], @@ -115,7 +115,7 @@ class InstanceTypeExtraSpecsTestCase(test.TestCase): xpu_arch="fermi", xpus="2", xpu_model="Tesla 2050")) - instance_type = db.api.instance_type_get_by_id( + instance_type = db.api.instance_type_get( context.get_admin_context(), 5) self.assertEquals(instance_type['extra_specs'], {}) @@ -136,7 +136,7 @@ class InstanceTypeExtraSpecsTestCase(test.TestCase): "m1.small") self.assertEquals(instance_type['extra_specs'], {}) - def test_instance_type_get_by_id_with_extra_specs(self): + def test_instance_type_get_with_extra_specs(self): instance_type = db.api.instance_type_get_by_flavor_id( context.get_admin_context(), 105) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index b09021e13..dd12d15ca 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -139,13 +139,13 @@ class FlatNetworkTestCase(test.TestCase): 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_by_id') + 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_by_id(mox.IgnoreArg(), + db.instance_type_get(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(flavor) self.mox.ReplayAll() diff --git a/nova/virt/libvirt/netutils.py b/nova/virt/libvirt/netutils.py index e5aaf7cec..8d36c0a05 100644 --- a/nova/virt/libvirt/netutils.py +++ b/nova/virt/libvirt/netutils.py @@ -59,7 +59,7 @@ def get_network_info(instance): vifs = db.virtual_interface_get_by_instance(admin_context, instance['id']) networks = db.network_get_all_by_instance(admin_context, instance['id']) - flavor = db.instance_type_get_by_id(admin_context, + flavor = db.instance_type_get(admin_context, instance['instance_type_id']) network_info = [] -- cgit From bc2747faf2164833b3e5412dd226f9ee431b1bbf Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 19 Jul 2011 17:51:43 -0400 Subject: updated links to use proper atom:link per spec --- nova/api/openstack/versions.py | 36 ++++++++++++++++++++++++++++++- nova/tests/api/openstack/test_versions.py | 8 ++----- 2 files changed, 37 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index a634c3267..450b42765 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -17,6 +17,7 @@ import webob import webob.dec +from xml.dom import minidom import nova.api.openstack.views.versions from nova.api.openstack import wsgi @@ -32,7 +33,7 @@ class Versions(wsgi.Resource): } body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), + 'application/xml': VersionsXMLSerializer(metadata=metadata), } serializer = wsgi.ResponseSerializer(body_serializers) @@ -54,3 +55,36 @@ class Versions(wsgi.Resource): builder = nova.api.openstack.views.versions.get_view_builder(request) versions = [builder.build(version) for version in version_objs] return dict(versions=versions) + +class VersionsXMLSerializer(wsgi.XMLDictSerializer): + def __init__(self, metadata=None, xmlns=None): + super(VersionsXMLSerializer, self).__init__(metadata, xmlns) + + def _versions_to_xml(self, versions): + root = self.xml_doc.createElement('versions') + + for version in versions: + root.appendChild(self._create_version_node(version)) + + return root + + def _create_version_node(self, version): + version_node = self.xml_doc.createElement('version') + version_node.setAttribute('id', version['id']) + version_node.setAttribute('status', version['status']) + #TODO(wwolf) need 'updated' attribute too + + for link in version['links']: + link_node = self.xml_doc.createElement('atom:link') + link_node.setAttribute('rel', link['rel']) + link_node.setAttribute('href', link['href']) + version_node.appendChild(link_node) + + return version_node + + + def default(self, data): + self.xml_doc = minidom.Document() + node = self._versions_to_xml(data['versions']) + + return self.to_xml_string(node) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index fd8d50904..ff6298640 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -70,14 +70,10 @@ class VersionsTest(test.TestCase): expected = """ - - - + - - - + """.replace(" ", "").replace("\n", "") -- cgit From e22b4f1b80f60e7235ef6a1af7d6a7c2df88effe Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Jul 2011 15:12:24 -0700 Subject: tell glance to not filter out private images --- nova/image/glance.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index 55d948a32..4b7e7573f 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -89,6 +89,9 @@ class GlanceImageService(service.BaseImageService): # `get_images` here because we need `is_public` and `properties` # included so we can filter by user filtered = [] + filters = filters or {} + if 'is_public' not in filters: + filters['is_public'] = None image_metas = self.client.get_images_detailed(filters=filters, marker=marker, limit=limit) @@ -101,6 +104,9 @@ class GlanceImageService(service.BaseImageService): def detail(self, context, filters=None, marker=None, limit=None): """Calls out to Glance for a list of detailed image information.""" filtered = [] + filters = filters or {} + if 'is_public' not in filters: + filters['is_public'] = None image_metas = self.client.get_images_detailed(filters=filters, marker=marker, limit=limit) -- cgit From c5cb902d19c890dd20e5120017973ea0172dd428 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Jul 2011 15:29:12 -0700 Subject: have to use string 'none' and add a note --- nova/image/glance.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index 4b7e7573f..5c2dc957b 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -91,7 +91,8 @@ class GlanceImageService(service.BaseImageService): filtered = [] filters = filters or {} if 'is_public' not in filters: - filters['is_public'] = None + # NOTE(vish): don't filter out private images + filters['is_public'] = 'none' image_metas = self.client.get_images_detailed(filters=filters, marker=marker, limit=limit) @@ -106,7 +107,8 @@ class GlanceImageService(service.BaseImageService): filtered = [] filters = filters or {} if 'is_public' not in filters: - filters['is_public'] = None + # NOTE(vish): don't filter out private images + filters['is_public'] = 'none' image_metas = self.client.get_images_detailed(filters=filters, marker=marker, limit=limit) -- cgit From 27f326c712e485322003ccdc13acfd04a6fdb119 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 09:59:56 -0400 Subject: added 'update' field to versions --- nova/api/openstack/versions.py | 6 +++++- nova/api/openstack/views/versions.py | 1 + nova/tests/api/openstack/test_versions.py | 9 +++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 450b42765..1b1fc41ab 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -45,10 +45,14 @@ class Versions(wsgi.Resource): { "id": "v1.1", "status": "CURRENT", + #TODO(wwolf) get correct value for these + "updated": "2011-7-18T11:30:00Z", }, { "id": "v1.0", "status": "DEPRECATED", + #TODO(wwolf) get correct value for these + "updated": "2010-10-09T11:30:00Z", }, ] @@ -72,7 +76,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): version_node = self.xml_doc.createElement('version') version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) - #TODO(wwolf) need 'updated' attribute too + version_node.setAttribute('updated', version['updated']) for link in version['links']: link_node = self.xml_doc.createElement('atom:link') diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index d0145c94a..e815974f1 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -36,6 +36,7 @@ class ViewBuilder(object): version = { "id": version_data["id"], "status": version_data["status"], + "updated": version_data["updated"], "links": self._build_links(version_data), } diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index ff6298640..32c57057a 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -43,6 +43,7 @@ class VersionsTest(test.TestCase): { "id": "v1.1", "status": "CURRENT", + "updated": "2011-7-18T11:30:00Z", "links": [ { "rel": "self", @@ -52,6 +53,7 @@ class VersionsTest(test.TestCase): { "id": "v1.0", "status": "DEPRECATED", + "updated": "2010-10-09T11:30:00Z", "links": [ { "rel": "self", @@ -69,10 +71,11 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/xml") expected = """ - + - + """.replace(" ", "").replace("\n", "") @@ -87,11 +90,13 @@ class VersionsTest(test.TestCase): version_data = { "id": "3.2.1", "status": "CURRENT", + "updated": "2011-7-18T11:30:00Z" } expected = { "id": "3.2.1", "status": "CURRENT", + "updated": "2011-7-18T11:30:00Z", "links": [ { "rel": "self", -- cgit From e65a9fe5827e0d5961e618a3163382c2ad02274c Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 20 Jul 2011 12:17:44 -0400 Subject: correct indentation --- nova/virt/libvirt/connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 56aaa6c50..4905d931a 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1015,8 +1015,8 @@ class LibvirtConnection(driver.ComputeDriver): 'volumes': block_device_mapping} if FLAGS.vnc_enabled and FLAGS.libvirt_type not in ('lxc', 'uml'): - xml_info['vncserver_host'] = FLAGS.vncserver_host - xml_info['vnc_keymap'] = FLAGS.vnc_keymap + xml_info['vncserver_host'] = FLAGS.vncserver_host + xml_info['vnc_keymap'] = FLAGS.vnc_keymap if not rescue: if instance['kernel_id']: xml_info['kernel'] = xml_info['basepath'] + "/kernel" -- cgit From a35a70fbaef0ef6634213308d5a68ee60bd714f2 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 13:12:18 -0400 Subject: initial changes for application/atom+xml for versions --- nova/api/openstack/versions.py | 98 ++++++++++++++++++++++++++++--- nova/api/openstack/wsgi.py | 19 ++++-- nova/tests/api/openstack/test_versions.py | 42 +++++++++++++ 3 files changed, 145 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 1b1fc41ab..a83472e15 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -23,7 +23,11 @@ import nova.api.openstack.views.versions from nova.api.openstack import wsgi +ATOM_XMLNS = "http://www.w3.org/2005/Atom" + + class Versions(wsgi.Resource): + def __init__(self): metadata = { "attributes": { @@ -33,11 +37,19 @@ class Versions(wsgi.Resource): } body_serializers = { + 'application/atom+xml': VersionsAtomSerializer(metadata=metadata), 'application/xml': VersionsXMLSerializer(metadata=metadata), } serializer = wsgi.ResponseSerializer(body_serializers) - wsgi.Resource.__init__(self, None, serializer=serializer) + supported_content_types = ('application/json', + 'application/xml', + 'application/atom+xml') + deserializer = wsgi.RequestDeserializer( + supported_content_types=supported_content_types) + + wsgi.Resource.__init__(self, None, serializer=serializer, + deserializer=deserializer) def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" @@ -61,11 +73,8 @@ class Versions(wsgi.Resource): return dict(versions=versions) class VersionsXMLSerializer(wsgi.XMLDictSerializer): - def __init__(self, metadata=None, xmlns=None): - super(VersionsXMLSerializer, self).__init__(metadata, xmlns) - def _versions_to_xml(self, versions): - root = self.xml_doc.createElement('versions') + root = self._xml_doc.createElement('versions') for version in versions: root.appendChild(self._create_version_node(version)) @@ -73,13 +82,13 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return root def _create_version_node(self, version): - version_node = self.xml_doc.createElement('version') + version_node = self._xml_doc.createElement('version') version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) version_node.setAttribute('updated', version['updated']) for link in version['links']: - link_node = self.xml_doc.createElement('atom:link') + link_node = self._xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) version_node.appendChild(link_node) @@ -88,7 +97,80 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def default(self, data): - self.xml_doc = minidom.Document() + self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) return self.to_xml_string(node) + +class VersionsAtomSerializer(wsgi.XMLDictSerializer): + def __init__(self, metadata=None, xmlns=None): + if not xmlns: + self.xmlns = ATOM_XMLNS + else: + self.xmlns = xmlns + + def _create_text_elem(self, name, text, type=None): + elem = self._xml_doc.createElement(name) + if type: + elem.setAttribute('type', type) + elem_text = self._xml_doc.createTextNode(text) + elem.appendChild(elem_text) + return elem + + def _create_meta(self, root): + title = self._create_text_elem('title', 'Available API Versions', + type='text') + #TODO(wwolf): what should updated be? + updated = self._create_text_elem('updated', '2010-12-12T18:30:02.25Z') + #TODO(wwolf): get URI + id = self._create_text_elem('id', '') + #TODO(wwolf): get link info + link = self._xml_doc.createElement('link') + link.setAttribute('rel', 'rel') + link.setAttribute('href', 'href') + + author = self._xml_doc.createElement('author') + author_name = self._create_text_elem('name', 'Rackspace') + author_uri = self._create_text_elem('uri', 'http://www.rackspace.com') + author.appendChild(author_name) + author.appendChild(author_uri) + + root.appendChild(title) + root.appendChild(updated) + root.appendChild(id) + root.appendChild(link) + root.appendChild(author) + + def _create_version_entries(self, root, versions): + for version in versions: + entry = self._xml_doc.createElement('entry') + #TODO(wwolf) GET URI + id = self._create_text_elem('id', 'URI') + title = self._create_text_elem('title', + 'Version %s' % version['id'], + type='text') + updated = self._create_text_elem('updated', version['updated']) + #TODO(wwolf): get link info + link = self._xml_doc.createElement('link') + link.setAttribute('rel', 'rel') + link.setAttribute('href', 'href') + content = self._create_text_elem('content', + 'Version %s %s (%s)' % + (version['id'], + version['status'], + version['updated'])) + + entry.appendChild(id) + entry.appendChild(title) + entry.appendChild(updated) + entry.appendChild(link) + entry.appendChild(content) + root.appendChild(entry) + + def default(self, data): + self._xml_doc = minidom.Document() + node = self._xml_doc.createElementNS(self.xmlns, 'feed') + self._create_meta(node) + self._create_version_entries(node, data['versions']) + + return self.to_xml_string(node) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c3f841aa5..9df6fd058 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -20,21 +20,22 @@ LOG = logging.getLogger('nova.api.openstack.wsgi') class Request(webob.Request): """Add some Openstack API-specific logic to the base webob.Request.""" - def best_match_content_type(self): + def best_match_content_type(self, supported_content_types=None): """Determine the requested response content-type. Based on the query extension then the Accept header. """ - supported = ('application/json', 'application/xml') + supported_content_types = supported_content_types or \ + ('application/json', 'application/xml') parts = self.path.rsplit('.', 1) if len(parts) > 1: ctype = 'application/{0}'.format(parts[1]) - if ctype in supported: + if ctype in supported_content_types: return ctype - bm = self.accept.best_match(supported) + bm = self.accept.best_match(supported_content_types) # default to application/json if we don't find a preference return bm or 'application/json' @@ -151,7 +152,12 @@ class RequestHeadersDeserializer(ActionDispatcher): class RequestDeserializer(object): """Break up a Request object into more useful pieces.""" - def __init__(self, body_deserializers=None, headers_deserializer=None): + def __init__(self, body_deserializers=None, headers_deserializer=None, + supported_content_types=None): + + self.supported_content_types = supported_content_types or \ + ('application/json', 'application/xml') + self.body_deserializers = { 'application/xml': XMLDeserializer(), 'application/json': JSONDeserializer(), @@ -213,7 +219,7 @@ class RequestDeserializer(object): raise exception.InvalidContentType(content_type=content_type) def get_expected_content_type(self, request): - return request.best_match_content_type() + return request.best_match_content_type(self.supported_content_types) def get_action_args(self, request_environment): """Parse dictionary created by routes library.""" @@ -412,6 +418,7 @@ class Resource(wsgi.Application): serialized by requested content type. """ + def __init__(self, controller, deserializer=None, serializer=None): """ :param controller: object that implement methods created by routes lib diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 32c57057a..275cfa9ef 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -84,6 +84,48 @@ class VersionsTest(test.TestCase): self.assertEqual(expected, actual) + def test_get_version_list_atom(self): + req = webob.Request.blank('/') + req.accept = "application/atom+xml" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + self.assertEqual(res.content_type, "application/atom+xml") + + expected = """ + + Available API Versions + 2010-12-12T18:30:02.25Z + http://servers.api.openstack.org/ + + Rackspace + http://www.rackspace.com/ + + + + http://servers.api.openstack.org/v1.1/ + Version v1.1 + 2010-12-12T18:30:02.25Z + + + Version v1.1 CURRENT (2010-12-12T18:30:02.25Z) + + + + http://servers.api.openstack.org/v1.0/ + Version v1.0 + 2009-10-09T11:30:00Z + + + Version v1.0 DEPRECATED (2009-10-09T11:30:00Z) + + + + """.replace(" ", "").replace("\n", "") + + actual = res.body.replace(" ", "").replace("\n", "") + + self.assertEqual(expected, actual) + def test_view_builder(self): base_url = "http://example.org/" -- cgit From a497d2e8f6beb667c73f66bef756fd955b9d6d69 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 20 Jul 2011 12:26:50 -0500 Subject: Host fix --- nova/compute/api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 207a3d27b..9cc517345 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -836,7 +836,10 @@ class API(base.Base): params = {'migration_id': migration_ref['id']} self._cast_compute_message('revert_resize', context, - instance_ref['uuid'], params=params) + instance_ref['uuid'], + migration_ref['source_compute'], + params=params) + self.db.migration_update(context, migration_ref['id'], {'status': 'reverted'}) @@ -853,6 +856,7 @@ class API(base.Base): status='finished') params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, + migration_ref['dest_compute'], instance_ref['uuid'], params=params) -- cgit From 9fc4cfaafceb3b96c51bc49aec020f89068544a0 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 20 Jul 2011 12:39:37 -0500 Subject: Stupid merge and fixed broken test --- nova/compute/manager.py | 2 +- nova/tests/test_compute.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 85a60e413..bfc7694e7 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -708,7 +708,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def finish_revert_resize(self, context, migration_id): + def finish_revert_resize(self, context, instance_id, migration_id): """Finishes the second half of reverting a resize. Power back on the source instance and revert the resized attributes diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index ce7ec46af..4bda38d85 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -545,14 +545,14 @@ class ComputeTestCase(test.TestCase): db.instance_update(self.context, instance_id, {'host': 'foo'}) - self.compute.prep_resize(context, instance_id, 3) + self.compute.prep_resize(context, inst_ref['uuid'], 3) migration_ref = db.migration_get_by_instance_and_status(context, - instance_id, 'pre-migrating') + inst_ref['uuid'], 'pre-migrating') - self.compute.resize_instance(context, instance_id, + self.compute.resize_instance(context, inst_ref['uuid'], migration_ref['id']) - self.compute.finish_resize(context, instance_id, + self.compute.finish_resize(context, inst_ref['uuid'], int(migration_ref['id']), {}) # Prove that the instance size is now the new size @@ -562,9 +562,9 @@ class ComputeTestCase(test.TestCase): self.assertEqual(instance_type_ref['flavorid'], 3) # Finally, revert and confirm the old flavor has been applied - self.compute.revert_resize(context, instance_id, + self.compute.revert_resize(context, inst_ref['uuid'], migration_ref['id']) - self.compute.finish_revert_resize(context, instance_id, + self.compute.finish_revert_resize(context, inst_ref['uuid'], migration_ref['id']) inst_ref = db.instance_get(context, instance_id) -- cgit From 775ea7f40d769645520d4ea7c7d9c0bfdb36c2dc Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 20 Jul 2011 17:45:55 +0000 Subject: default image to private on register --- nova/image/s3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/image/s3.py b/nova/image/s3.py index 4a3df98ba..c313c7a13 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -168,7 +168,7 @@ class S3ImageService(service.BaseImageService): metadata.update({'disk_format': image_format, 'container_format': image_format, 'status': 'queued', - 'is_public': True, + 'is_public': False, 'properties': properties}) metadata['properties']['image_state'] = 'pending' image = self.service.create(context, metadata) -- cgit From 1b423b464e0dd849d1a6dafde08b3c4420217151 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 20 Jul 2011 13:52:22 -0400 Subject: Set the status_int on fault wrapped exceptions. Fixes WSGI logging issues when faults are returned. Updated so that webob exceptions aren't used for the happy path (HTTP 200 responses). We now return a proper webob object response in these cases. This fixes issues where HTML/XML would get returned with the old style happy path exceptions. --- nova/api/openstack/consoles.py | 3 +- nova/api/openstack/contrib/multinic.py | 5 ++-- nova/api/openstack/contrib/volumes.py | 5 ++-- nova/api/openstack/faults.py | 1 + nova/api/openstack/servers.py | 49 ++++++++++++++++----------------- nova/tests/api/openstack/test_faults.py | 5 ++++ 6 files changed, 37 insertions(+), 31 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 7a43fba96..9c7b37f0d 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -16,6 +16,7 @@ # under the License. from webob import exc +import webob from nova import console from nova import exception @@ -86,7 +87,7 @@ class Controller(object): int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def create_resource(): diff --git a/nova/api/openstack/contrib/multinic.py b/nova/api/openstack/contrib/multinic.py index 841061721..da8dcee5d 100644 --- a/nova/api/openstack/contrib/multinic.py +++ b/nova/api/openstack/contrib/multinic.py @@ -16,6 +16,7 @@ """The multinic extension.""" from webob import exc +import webob from nova import compute from nova import log as logging @@ -103,7 +104,7 @@ class Multinic(extensions.ExtensionDescriptor): except Exception, e: LOG.exception(_("Error in addFixedIp %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _remove_fixed_ip(self, input_dict, req, id): """Removes an IP from an instance.""" @@ -122,4 +123,4 @@ class Multinic(extensions.ExtensionDescriptor): except Exception, e: LOG.exception(_("Error in removeFixedIp %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index e5e2c5b50..827e36097 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -16,6 +16,7 @@ """The volumes extension.""" from webob import exc +import webob from nova import compute from nova import exception @@ -104,7 +105,7 @@ class VolumeController(object): self.volume_api.delete(context, volume_id=id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def index(self, req): """Returns a summary list of volumes.""" @@ -279,7 +280,7 @@ class VolumeAttachmentController(object): self.compute_api.detach_volume(context, volume_id=volume_id) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _items(self, req, server_id, entity_maker): """Returns a list of attachments, transformed through entity_maker.""" diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index b9a23c126..24cde69e4 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -40,6 +40,7 @@ class Fault(webob.exc.HTTPException): def __init__(self, exception): """Create a Fault for the given webob.exc.exception.""" self.wrapped_exc = exception + self.status_int = exception.status_int @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 93f8e832c..3b3d0685d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,6 +17,7 @@ import base64 import traceback from webob import exc +import webob from nova import compute from nova import db @@ -189,7 +190,7 @@ class Controller(object): except Exception, e: LOG.exception(_("Error in revert-resize %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_resize(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -207,7 +208,7 @@ class Controller(object): except Exception, e: LOG.exception(_("Error in reboot %s"), e) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_migrate(self, input_dict, req, id): try: @@ -215,7 +216,7 @@ class Controller(object): except Exception, e: LOG.exception(_("Error in migrate %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def lock(self, req, id): @@ -231,7 +232,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::lock %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def unlock(self, req, id): @@ -247,7 +248,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::unlock %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def get_lock(self, req, id): @@ -262,7 +263,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::get_lock %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def reset_network(self, req, id, body): @@ -277,7 +278,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::reset_network %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def inject_network_info(self, req, id, body): @@ -292,7 +293,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::inject_network_info %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def pause(self, req, id, body): @@ -304,7 +305,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::pause %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def unpause(self, req, id, body): @@ -316,7 +317,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::unpause %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def suspend(self, req, id, body): @@ -328,7 +329,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::suspend %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def resume(self, req, id, body): @@ -340,7 +341,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::resume %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def rescue(self, req, id): @@ -352,7 +353,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::rescue %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def unrescue(self, req, id): @@ -364,7 +365,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::unrescue %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def get_ajax_console(self, req, id): @@ -374,7 +375,7 @@ class Controller(object): int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def get_vnc_console(self, req, id): @@ -384,7 +385,7 @@ class Controller(object): int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def diagnostics(self, req, id): @@ -416,7 +417,7 @@ class ControllerV10(Controller): self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _image_ref_from_req_data(self, data): return data['server']['imageId'] @@ -450,7 +451,7 @@ class ControllerV10(Controller): except Exception, e: LOG.exception(_("Error in resize %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] @@ -470,9 +471,7 @@ class ControllerV10(Controller): LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - response = exc.HTTPAccepted() - response.empty_body = True - return response + return webob.Response(status_int=202) def _get_server_admin_password(self, server): """ Determine the admin password for a server on creation """ @@ -519,7 +518,7 @@ class ControllerV11(Controller): msg = _("Invalid adminPass") return exc.HTTPBadRequest(explanation=msg) self.compute_api.set_admin_password(context, id, password) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _limit_items(self, items, req): return common.limited_by_marker(items, req) @@ -565,7 +564,7 @@ class ControllerV11(Controller): except Exception, e: LOG.exception(_("Error in resize %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] @@ -594,9 +593,7 @@ class ControllerV11(Controller): LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - response = exc.HTTPAccepted() - response.empty_body = True - return response + return webob.Response(status_int=202) def get_default_xmlns(self, req): return common.XML_NS_V11 diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py index 4d86ffb26..927009e77 100644 --- a/nova/tests/api/openstack/test_faults.py +++ b/nova/tests/api/openstack/test_faults.py @@ -139,3 +139,8 @@ class TestFaults(test.TestCase): self.assertEqual(resp.content_type, "application/xml") self.assertEqual(resp.status_int, 404) self.assertTrue('whut?' in resp.body) + + def test_fault_has_status_int(self): + """Ensure the status_int is set correctly on faults""" + fault = faults.Fault(webob.exc.HTTPBadRequest(explanation='what?')) + self.assertEqual(fault.status_int, 400) -- cgit From a01afcd449d7f1f6ffd74d9ca314edcdbb9b155d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 20 Jul 2011 18:27:15 +0000 Subject: pep8 --- nova/network/manager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 7a7520f5d..34001657b 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -140,7 +140,9 @@ class RPCAllocateFixedIP(object): 'args': {'network_ref': network}}) if host != self.host: # need to call allocate_fixed_ip to correct network host - topic = self.db.queue_get_for(context, FLAGS.network_topic, host) + topic = self.db.queue_get_for(context, + FLAGS.network_topic, + host) args = {} args['instance_id'] = instance_id args['network_id'] = network['id'] @@ -414,7 +416,8 @@ class NetworkManager(manager.SchedulerDependentManager): # deallocate vifs (mac addresses) self.db.virtual_interface_delete_by_instance(context, instance_id) - def get_instance_nw_info(self, context, instance_id, instance_type_id, host): + def get_instance_nw_info(self, context, instance_id, + instance_type_id, host): """Creates network info list for instance. called by allocate_for_instance and netowrk_api -- cgit From 58a76cede01b6bb141d060636c21318db1712527 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 20 Jul 2011 18:46:29 +0000 Subject: Make sure reset_network() call happens after we've determined the agent is running. --- nova/virt/xenapi/vmops.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index bade23889..419685a6f 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -311,6 +311,7 @@ class VMOps(object): _check_agent_version() _inject_files() _set_admin_password() + self.reset_network(instance, vm_ref) return True except Exception, exc: LOG.warn(exc) @@ -320,9 +321,6 @@ class VMOps(object): timer.f = _wait_for_boot - # call to reset network to configure network from xenstore - self.reset_network(instance, vm_ref) - return timer.start(interval=0.5, now=True) def _get_vm_opaque_ref(self, instance_or_vm): -- cgit From e2eff68dbe78d8639d4cb212ab48240ebf045f73 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 20 Jul 2011 14:25:23 -0500 Subject: Whoops --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index e25a9eeb3..67aa3c20f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -902,8 +902,8 @@ class API(base.Base): status='finished') params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, - migration_ref['dest_compute'], instance_ref['uuid'], + migration_ref['dest_compute'], params=params) self.db.migration_update(context, migration_ref['id'], -- cgit From 148b4095a5f18f192ae243b02b4070af384c1152 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 16:47:17 -0400 Subject: added versions list atom test and it passes --- nova/api/openstack/versions.py | 68 ++++++++++++++++++++----------- nova/api/openstack/views/versions.py | 2 +- nova/tests/api/openstack/test_versions.py | 36 ++++++++-------- 3 files changed, 64 insertions(+), 42 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index a83472e15..49633df88 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +from datetime import datetime import webob import webob.dec from xml.dom import minidom @@ -58,7 +59,7 @@ class Versions(wsgi.Resource): "id": "v1.1", "status": "CURRENT", #TODO(wwolf) get correct value for these - "updated": "2011-7-18T11:30:00Z", + "updated": "2011-07-18T11:30:00Z", }, { "id": "v1.0", @@ -117,60 +118,81 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): elem.appendChild(elem_text) return elem - def _create_meta(self, root): + def _get_most_recent_update(self, versions): + recent = None + for version in versions: + updated = datetime.strptime(version['updated'],'%Y-%m-%dT%H:%M:%SZ') + if not recent: + recent = updated + elif updated > recent: + recent = updated + + return recent.strftime('%Y-%m-%dT%H:%M:%SZ') + + def _get_base_url(self, link_href): + # Make sure no trailing / + link_href = link_href.rstrip('/') + return link_href.rsplit('/',1)[0] + '/' + + def _create_meta(self, root, versions): title = self._create_text_elem('title', 'Available API Versions', type='text') - #TODO(wwolf): what should updated be? - updated = self._create_text_elem('updated', '2010-12-12T18:30:02.25Z') - #TODO(wwolf): get URI - id = self._create_text_elem('id', '') - #TODO(wwolf): get link info + # Set this updated to the most recently updated version + recent = self._get_most_recent_update(versions) + updated = self._create_text_elem('updated', recent) + + base_url = self._get_base_url(versions[0]['links'][0]['href']) + id = self._create_text_elem('id', base_url) link = self._xml_doc.createElement('link') - link.setAttribute('rel', 'rel') - link.setAttribute('href', 'href') + link.setAttribute('rel', 'self') + link.setAttribute('href', base_url) author = self._xml_doc.createElement('author') author_name = self._create_text_elem('name', 'Rackspace') - author_uri = self._create_text_elem('uri', 'http://www.rackspace.com') + author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/') author.appendChild(author_name) author.appendChild(author_uri) root.appendChild(title) root.appendChild(updated) root.appendChild(id) - root.appendChild(link) root.appendChild(author) + root.appendChild(link) def _create_version_entries(self, root, versions): for version in versions: entry = self._xml_doc.createElement('entry') - #TODO(wwolf) GET URI - id = self._create_text_elem('id', 'URI') + + id = self._create_text_elem('id', version['links'][0]['href']) title = self._create_text_elem('title', 'Version %s' % version['id'], type='text') updated = self._create_text_elem('updated', version['updated']) - #TODO(wwolf): get link info - link = self._xml_doc.createElement('link') - link.setAttribute('rel', 'rel') - link.setAttribute('href', 'href') + + entry.appendChild(id) + entry.appendChild(title) + entry.appendChild(updated) + + for link in version['links']: + link_node = self._xml_doc.createElement('link') + link_node.setAttribute('rel', link['rel']) + link_node.setAttribute('href', link['href']) + entry.appendChild(link_node) + content = self._create_text_elem('content', 'Version %s %s (%s)' % (version['id'], version['status'], - version['updated'])) + version['updated']), + type='text') - entry.appendChild(id) - entry.appendChild(title) - entry.appendChild(updated) - entry.appendChild(link) entry.appendChild(content) root.appendChild(entry) def default(self, data): self._xml_doc = minidom.Document() node = self._xml_doc.createElementNS(self.xmlns, 'feed') - self._create_meta(node) + self._create_meta(node, data['versions']) self._create_version_entries(node, data['versions']) return self.to_xml_string(node) diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index e815974f1..9fa8f49dc 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -57,4 +57,4 @@ class ViewBuilder(object): def generate_href(self, version_number): """Create an url that refers to a specific version_number.""" - return os.path.join(self.base_url, version_number) + return os.path.join(self.base_url, version_number) + '/' diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 275cfa9ef..471afec94 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -43,7 +43,7 @@ class VersionsTest(test.TestCase): { "id": "v1.1", "status": "CURRENT", - "updated": "2011-7-18T11:30:00Z", + "updated": "2011-07-18T11:30:00Z", "links": [ { "rel": "self", @@ -71,7 +71,7 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/xml") expected = """ - + Available API Versions - 2010-12-12T18:30:02.25Z - http://servers.api.openstack.org/ + 2011-07-18T11:30:00Z + http://localhost/ Rackspace http://www.rackspace.com/ - + - http://servers.api.openstack.org/v1.1/ + http://localhost/v1.1/ Version v1.1 - 2010-12-12T18:30:02.25Z - + 2011-07-18T11:30:00Z + - Version v1.1 CURRENT (2010-12-12T18:30:02.25Z) + Version v1.1 CURRENT (2011-07-18T11:30:00Z) - http://servers.api.openstack.org/v1.0/ + http://localhost/v1.0/ Version v1.0 - 2009-10-09T11:30:00Z - + 2010-10-09T11:30:00Z + - Version v1.0 DEPRECATED (2009-10-09T11:30:00Z) + Version v1.0 DEPRECATED (2010-10-09T11:30:00Z) - - """.replace(" ", "").replace("\n", "") + + """.replace(" ", "").replace("\n", "").replace("\t", "") - actual = res.body.replace(" ", "").replace("\n", "") + actual = res.body.replace(" ", "").replace("\n", "").replace("\t","") self.assertEqual(expected, actual) @@ -132,13 +132,13 @@ class VersionsTest(test.TestCase): version_data = { "id": "3.2.1", "status": "CURRENT", - "updated": "2011-7-18T11:30:00Z" + "updated": "2011-07-18T11:30:00Z" } expected = { "id": "3.2.1", "status": "CURRENT", - "updated": "2011-7-18T11:30:00Z", + "updated": "2011-07-18T11:30:00Z", "links": [ { "rel": "self", -- cgit From 38fb47ab2a12be0b169e3f51f4da413efc041f9c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 20 Jul 2011 17:06:36 -0400 Subject: adding flavors xml serialization --- nova/api/openstack/flavors.py | 55 ++++- nova/tests/api/openstack/test_flavors.py | 374 +++++++++++++++++++++++++------ 2 files changed, 351 insertions(+), 78 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 6fab13147..b4bda68d4 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -16,6 +16,7 @@ # under the License. import webob +import xml.dom.minidom as minidom from nova import db from nova import exception @@ -74,19 +75,65 @@ class ControllerV11(Controller): return views.flavors.ViewBuilderV11(base_url) +class FlavorXMLSerializer(wsgi.XMLDictSerializer): + + def __init__(self): + super(FlavorXMLSerializer, self).__init__(xmlns=wsgi.XMLNS_V11) + + def _flavor_to_xml(self, xml_doc, flavor, detailed): + flavor_node = xml_doc.createElement('flavor') + flavor_node.setAttribute('id', str(flavor['id'])) + flavor_node.setAttribute('name', flavor['name']) + + if detailed: + flavor_node.setAttribute('ram', str(flavor['ram'])) + flavor_node.setAttribute('disk', str(flavor['disk'])) + + link_nodes = self._create_link_nodes(xml_doc, flavor['links']) + for link_node in link_nodes: + flavor_node.appendChild(link_node) + return flavor_node + + def _flavors_list_to_xml(self, xml_doc, flavors, detailed): + container_node = xml_doc.createElement('flavors') + + for flavor in flavors: + item_node = self._flavor_to_xml(xml_doc, flavor, detailed) + container_node.appendChild(item_node) + return container_node + + def show(self, flavor_container): + xml_doc = minidom.Document() + flavor = flavor_container['flavor'] + node = self._flavor_to_xml(xml_doc, flavor, True) + return self.to_xml_string(node, True) + + def detail(self, flavors_container): + xml_doc = minidom.Document() + flavors = flavors_container['flavors'] + node = self._flavors_list_to_xml(xml_doc, flavors, True) + return self.to_xml_string(node, True) + + def index(self, flavors_container): + xml_doc = minidom.Document() + flavors = flavors_container['flavors'] + node = self._flavors_list_to_xml(xml_doc, flavors, False) + return self.to_xml_string(node, True) + + def create_resource(version='1.0'): controller = { '1.0': ControllerV10, '1.1': ControllerV11, }[version]() - xmlns = { - '1.0': wsgi.XMLNS_V10, - '1.1': wsgi.XMLNS_V11, + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10), + '1.1': FlavorXMLSerializer(), }[version] body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns), + 'application/xml': xml_serializer, } serializer = wsgi.ResponseSerializer(body_serializers) diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index 689647cc6..4ac35b26b 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -18,12 +18,14 @@ import json import stubout import webob +import xml.dom.minidom as minidom +from nova.api.openstack import flavors import nova.db.api -from nova import context from nova import exception from nova import test from nova.tests.api.openstack import fakes +from nova import wsgi def stub_flavor(flavorid, name, memory_mb="256", local_gb="10"): @@ -64,7 +66,6 @@ class FlavorsTest(test.TestCase): return_instance_types) self.stubs.Set(nova.db.api, "instance_type_get_by_flavor_id", return_instance_type_by_flavor_id) - self.context = context.get_admin_context() def tearDown(self): self.stubs.UnsetAll() @@ -146,61 +147,65 @@ class FlavorsTest(test.TestCase): req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) - flavor = json.loads(res.body)["flavor"] + flavor = json.loads(res.body) expected = { - "id": "12", - "name": "flavor 12", - "ram": "256", - "disk": "10", - "links": [ - { - "rel": "self", - "href": "http://localhost/v1.1/flavors/12", - }, - { - "rel": "bookmark", - "href": "http://localhost/flavors/12", - }, - ], - } - self.assertEqual(flavor, expected) - - def test_get_flavor_list_v1_1(self): - req = webob.Request.blank('/v1.1/flavors') - req.environ['api.version'] = '1.1' - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - flavor = json.loads(res.body)["flavors"] - expected = [ - { - "id": "1", - "name": "flavor 1", - "links": [ - { - "rel": "self", - "href": "http://localhost/v1.1/flavors/1", - }, - { - "rel": "bookmark", - "href": "http://localhost/flavors/1", - }, - ], - }, - { - "id": "2", - "name": "flavor 2", + "flavor": { + "id": "12", + "name": "flavor 12", + "ram": "256", + "disk": "10", "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/2", + "href": "http://localhost/v1.1/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/2", + "href": "http://localhost/flavors/12", }, ], }, - ] + } + self.assertEqual(flavor, expected) + + def test_get_flavor_list_v1_1(self): + req = webob.Request.blank('/v1.1/flavors') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + flavor = json.loads(res.body) + expected = { + "flavors": [ + { + "id": "1", + "name": "flavor 1", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/1", + }, + ], + }, + { + "id": "2", + "name": "flavor 2", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/2", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/2", + }, + ], + }, + ], + } self.assertEqual(flavor, expected) def test_get_flavor_list_detail_v1_1(self): @@ -208,52 +213,273 @@ class FlavorsTest(test.TestCase): req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) - flavor = json.loads(res.body)["flavors"] - expected = [ - { - "id": "1", - "name": "flavor 1", + flavor = json.loads(res.body) + expected = { + "flavors": [ + { + "id": "1", + "name": "flavor 1", + "ram": "256", + "disk": "10", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/1", + }, + ], + }, + { + "id": "2", + "name": "flavor 2", + "ram": "256", + "disk": "10", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/2", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/2", + }, + ], + }, + ], + } + self.assertEqual(flavor, expected) + + def test_get_empty_flavor_list_v1_1(self): + def _return_empty(self): + return {} + self.stubs.Set(nova.db.api, "instance_type_get_all", _return_empty) + + req = webob.Request.blank('/v1.1/flavors') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + flavors = json.loads(res.body)["flavors"] + expected = [] + self.assertEqual(flavors, expected) + + +class FlavorsXMLSerializationTest(test.TestCase): + + def test_show(self): + serializer = flavors.FlavorXMLSerializer() + + input = { + "flavor": { + "id": "12", + "name": "asdf", "ram": "256", "disk": "10", "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/1", + "href": "http://localhost/v1.1/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/1", + "href": "http://localhost/flavors/12", }, ], }, - { - "id": "2", - "name": "flavor 2", - "ram": "256", - "disk": "10", + } + + output = serializer.serialize(input, 'show') + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + + + + """.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_show_handles_integers(self): + serializer = flavors.FlavorXMLSerializer() + + input = { + "flavor": { + "id": 12, + "name": "asdf", + "ram": 256, + "disk": 10, "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/2", + "href": "http://localhost/v1.1/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/2", + "href": "http://localhost/flavors/12", }, ], }, - ] - self.assertEqual(flavor, expected) + } - def test_get_empty_flavor_list_v1_1(self): - def _return_empty(self): - return {} - self.stubs.Set(nova.db.api, "instance_type_get_all", - _return_empty) + output = serializer.serialize(input, 'show') + actual = minidom.parseString(output.replace(" ", "")) - req = webob.Request.blank('/v1.1/flavors') - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - flavors = json.loads(res.body)["flavors"] - expected = [] - self.assertEqual(flavors, expected) + expected = minidom.parseString(""" + + + + + """.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_detail(self): + serializer = flavors.FlavorXMLSerializer() + + input = { + "flavors": [ + { + "id": "23", + "name": "flavor 23", + "ram": "512", + "disk": "20", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/23", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/23", + }, + ], + }, { + "id": "13", + "name": "flavor 13", + "ram": "256", + "disk": "10", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/13", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/13", + }, + ], + }, + ], + } + + output = serializer.serialize(input, 'detail') + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + + + + + + + + + + """.replace(" ", "") % locals()) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_index(self): + serializer = flavors.FlavorXMLSerializer() + + input = { + "flavors": [ + { + "id": "23", + "name": "flavor 23", + "ram": "512", + "disk": "20", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/23", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/23", + }, + ], + }, { + "id": "13", + "name": "flavor 13", + "ram": "256", + "disk": "10", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/13", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/13", + }, + ], + }, + ], + } + + output = serializer.serialize(input, 'index') + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + + + + + + + + + + """.replace(" ", "") % locals()) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_index_empty(self): + serializer = flavors.FlavorXMLSerializer() + + input = { + "flavors": [], + } + + output = serializer.serialize(input, 'index') + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + """.replace(" ", "") % locals()) + + self.assertEqual(expected.toxml(), actual.toxml()) -- cgit From b2f7ae2ad7762531a9200799de4be9849cd3b42c Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 09:43:25 -0400 Subject: Added LimitsXMLSerializer Added LimitsViewBuidlerV11Test test case --- nova/api/openstack/limits.py | 61 +++++++++- nova/tests/api/openstack/test_limits.py | 206 ++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index bc76547d8..86afa3b62 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -25,6 +25,7 @@ import re import time import urllib import webob.exc +from xml.dom import minidom from collections import defaultdict @@ -76,6 +77,58 @@ class LimitsControllerV11(LimitsController): return limits_views.ViewBuilderV11() +class LimitsXMLSerializer(wsgi.XMLDictSerializer): + + xmlns = wsgi.XMLNS_V11 + + def __init__(self): + pass + + def _create_rates_node(self, xml_doc, rates): + rates_node = xml_doc.createElement('rates') + for rate in rates: + rate_node = xml_doc.createElement('rate') + rate_node.setAttribute('uri', rate['uri']) + rate_node.setAttribute('regex', rate['regex']) + + for limit in rate['limit']: + limit_node = xml_doc.createElement('limit') + limit_node.setAttribute('value', str(limit['value'])) + limit_node.setAttribute('verb', limit['verb']) + limit_node.setAttribute('remaining', str(limit['remaining'])) + limit_node.setAttribute('unit', limit['unit']) + limit_node.setAttribute('next-available', + str(limit['next-available'])) + rate_node.appendChild(limit_node) + + rates_node.appendChild(rate_node) + return rates_node + + def _create_absolute_node(self, xml_doc, absolutes): + absolute_node = xml_doc.createElement('absolute') + for key, value in absolutes.iteritems(): + limit_node = xml_doc.createElement('limit') + limit_node.setAttribute('name', key) + limit_node.setAttribute('value', str(value)) + absolute_node.appendChild(limit_node) + return absolute_node + + def _limits_to_xml(self, xml_doc, limits): + limits_node = xml_doc.createElement('limits') + rates_node = self._create_rates_node(xml_doc, limits['rate']) + limits_node.appendChild(rates_node) + + absolute_node = self._create_absolute_node(xml_doc, limits['absolute']) + limits_node.appendChild(absolute_node) + + return limits_node + + def index(self, limits_dict): + xml_doc = minidom.Document() + node = self._limits_to_xml(xml_doc, limits_dict['limits']) + return self.to_xml_string(node, False) + + def create_resource(version='1.0'): controller = { '1.0': LimitsControllerV10, @@ -97,9 +150,13 @@ def create_resource(version='1.0'): }, } + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(xmlns=xmlns, metadata=metadata), + '1.1': LimitsXMLSerializer(), + }[version] + body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, - metadata=metadata), + 'application/xml': xml_serializer, } serializer = wsgi.ResponseSerializer(body_serializers) diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 76363450d..9859c17f8 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -27,6 +27,7 @@ import webob from xml.dom.minidom import parseString +from nova import test import nova.context from nova.api.openstack import limits @@ -904,3 +905,208 @@ class WsgiLimiterProxyTest(BaseLimitTestSuite): "made to /delayed every minute.") self.assertEqual((delay, error), expected) + + +class LimitsViewBuilderV11Test(test.TestCase): + + def setUp(self): + self.view_builder = self._get_view_builder() + + def tearDown(self): + pass + + def _get_view_builder(self): + views = nova.api.openstack.views + view_builder = views.limits.ViewBuilderV11() + return view_builder + + def _get_rate_limits(self): + rate_limits = [ + { + "URI": "*", + "regex": ".*", + "value": 10, + "verb": "POST", + "remaining": 2, + "unit": "MINUTE", + "resetTime": "2011-12-15T22:42:45Z" + }, + { + "URI": "*/servers", + "regex": "^/servers", + "value": 50, + "verb": "POST", + "remaining": 10, + "unit": "DAY", + "resetTime": "2011-12-15T22:42:45Z" + }, + ] + + return rate_limits + + def _get_absolute_limits(self): + absolute_limits = { + "metadata_items": 1, + "injected_files": 5, + "injected_file_content_bytes": 5, + } + + return absolute_limits + + def test_build_limits(self): + expected_limits = { + "limits": { + "rate": [ + { + "uri": "*", + "regex": ".*", + "limit": [ + { + "value": 10, + "verb": "POST", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + ] + }, + { + "uri": "*/servers", + "regex": "^/servers", + "limit": [ + { + "value": 50, + "verb": "POST", + "remaining": 10, + "unit": "DAY", + "next-available": "2011-12-15T22:42:45Z" + }, + ] + }, + ], + "absolute": { + "maxServerMeta": 1, + "maxImageMeta": 1, + "maxPersonality": 5, + "maxPersonalitySize": 5 + } + } + } + + output = self.view_builder.build(self._get_rate_limits(), + self._get_absolute_limits()) + self.assertDictMatch(output, expected_limits) + + def test_build_limits_empty_limits(self): + expected_limits = { + "limits": { + "rate": [], + "absolute": {} + } + } + + abs_limits = {} + rate_limits = [] + output = self.view_builder.build(rate_limits, abs_limits) + self.assertDictMatch(output, expected_limits) + + +class LimitsXMLSerializationTest(test.TestCase): + + def setUp(self): + self.maxDiff = None + + def tearDown(self): + pass + + def test_index(self): + serializer = limits.LimitsXMLSerializer() + + fixture = { + "limits": { + "rate": [ + { + "uri": "*", + "regex": ".*", + "limit": [ + { + "value": 10, + "verb": "POST", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + ] + }, + { + "uri": "*/servers", + "regex": "^/servers", + "limit": [ + { + "value": 50, + "verb": "POST", + "remaining": 10, + "unit": "DAY", + "next-available": "2011-12-15T22:42:45Z" + }, + ] + }, + ], + "absolute": { + "maxServerMeta": 1, + "maxImageMeta": 1, + "maxPersonality": 5, + "maxPersonalitySize": 10240 + } + } + } + + output = serializer.serialize(fixture, 'index') + actual = parseString(output.replace(" ", "")) + + expected = parseString(""" + + + + + + + + + + + + + + + + + """.replace(" ", "") % (locals())) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_index_no_limits(self): + serializer = limits.LimitsXMLSerializer() + + fixture = { + "limits": { + "rate": [], + "absolute": {} + } + } + + output = serializer.serialize(fixture, 'index') + actual = parseString(output.replace(" ", "")) + + expected = parseString(""" + + + + + """.replace(" ", "") % (locals())) + + self.assertEqual(expected.toxml(), actual.toxml()) -- cgit From 126714f297fc29c0a0c930e7e1f351bf114b3795 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 21 Jul 2011 10:02:16 -0400 Subject: Perform fault wrapping in the openstack WSGI controller. This allows us to just raise webob Exceptions in OS API controllers with the appropriate explanations set. This resolves some inconsistencies with exception raising and returning that could cause HTML output to occur when an exception was raised. --- nova/api/openstack/accounts.py | 6 +- nova/api/openstack/backup_schedules.py | 9 ++- nova/api/openstack/common.py | 19 +++--- nova/api/openstack/consoles.py | 7 +-- nova/api/openstack/create_instance_helper.py | 7 +-- nova/api/openstack/image_metadata.py | 5 +- nova/api/openstack/images.py | 3 +- nova/api/openstack/ips.py | 11 ++-- nova/api/openstack/server_metadata.py | 1 - nova/api/openstack/servers.py | 92 +++++++++++++--------------- nova/api/openstack/shared_ip_groups.py | 13 ++-- nova/api/openstack/users.py | 3 +- nova/api/openstack/wsgi.py | 6 +- nova/api/openstack/zones.py | 8 +-- nova/tests/api/openstack/test_servers.py | 2 +- 15 files changed, 90 insertions(+), 102 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index e3201b14f..a13a758ab 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -47,10 +47,10 @@ class Controller(object): raise exception.AdminRequired() def index(self, req): - raise faults.Fault(webob.exc.HTTPNotImplemented()) + raise webob.exc.HTTPNotImplemented() def detail(self, req): - raise faults.Fault(webob.exc.HTTPNotImplemented()) + raise webob.exc.HTTPNotImplemented() def show(self, req, id): """Return data about the given account id""" @@ -65,7 +65,7 @@ class Controller(object): def create(self, req, body): """We use update with create-or-update semantics because the id comes from an external source""" - raise faults.Fault(webob.exc.HTTPNotImplemented()) + raise webob.exc.HTTPNotImplemented() def update(self, req, id, body): """This is really create or update.""" diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 3e95aedf3..7ff0d999e 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -19,7 +19,6 @@ import time from webob import exc -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -36,20 +35,20 @@ class Controller(object): def index(self, req, server_id, **kwargs): """ Returns the list of backup schedules for a given instance """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def show(self, req, server_id, id, **kwargs): """ Returns a single backup schedule for a given instance """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create(self, req, server_id, **kwargs): """ No actual update method required, since the existing API allows both create and update through a POST """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id, **kwargs): """ Deletes an existing backup schedule """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create_resource(): diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 8e12ce0c0..f41d25537 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -53,10 +53,10 @@ def get_pagination_params(request): params[param] = int(request.GET[param]) except ValueError: msg = _('%s param must be an integer') % param - raise webob.exc.HTTPBadRequest(msg) + raise webob.exc.HTTPBadRequest(explanation=msg) if params[param] < 0: msg = _('%s param must be positive') % param - raise webob.exc.HTTPBadRequest(msg) + raise webob.exc.HTTPBadRequest(explanation=msg) return params @@ -77,18 +77,22 @@ def limited(items, request, max_limit=FLAGS.osapi_max_limit): try: offset = int(request.GET.get('offset', 0)) except ValueError: - raise webob.exc.HTTPBadRequest(_('offset param must be an integer')) + msg = _('offset param must be an integer') + raise webob.exc.HTTPBadRequest(explanation=msg) try: limit = int(request.GET.get('limit', max_limit)) except ValueError: - raise webob.exc.HTTPBadRequest(_('limit param must be an integer')) + msg = _('limit param must be an integer') + raise webob.exc.HTTPBadRequest(explanation=msg) if limit < 0: - raise webob.exc.HTTPBadRequest(_('limit param must be positive')) + msg = _('limit param must be positive') + raise webob.exc.HTTPBadRequest(explanation=msg) if offset < 0: - raise webob.exc.HTTPBadRequest(_('offset param must be positive')) + msg = _('offset param must be positive') + raise webob.exc.HTTPBadRequest(explanation=msg) limit = min(max_limit, limit or max_limit) range_end = offset + limit @@ -111,7 +115,8 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit): start_index = i + 1 break if start_index < 0: - raise webob.exc.HTTPBadRequest(_('marker [%s] not found' % marker)) + msg = _('marker [%s] not found' % marker) + raise webob.exc.HTTPBadRequest(explanation=msg) range_end = start_index + limit return items[start_index:range_end] diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 9c7b37f0d..d2655acfa 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -20,7 +20,6 @@ import webob from nova import console from nova import exception -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -72,12 +71,12 @@ class Controller(object): int(server_id), int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return _translate_detail_keys(console) def update(self, req, server_id, id): """You can't update a console""" - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id): """Deletes a console""" @@ -86,7 +85,7 @@ class Controller(object): int(server_id), int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2654e3c40..7784a3e81 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -28,7 +28,6 @@ from nova import quota from nova import utils from nova.compute import instance_types -from nova.api.openstack import faults from nova.api.openstack import wsgi from nova.auth import manager as auth_manager @@ -70,7 +69,7 @@ class CreateInstanceHelper(object): return type from this method is left to the caller. """ if not body: - raise faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() context = req.environ['nova.context'] @@ -94,7 +93,7 @@ class CreateInstanceHelper(object): except Exception, e: msg = _("Cannot find requested image %(image_href)s: %(e)s" % locals()) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) personality = body['server'].get('personality') @@ -153,7 +152,7 @@ class CreateInstanceHelper(object): self._handle_quota_error(error) except exception.ImageNotFound as error: msg = _("Can not find requested image") - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) # Let the caller deal with unhandled exceptions. diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 4f33844fa..c0fc8c09b 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -22,7 +22,6 @@ from nova import flags from nova import image from nova import quota from nova import utils -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -62,7 +61,7 @@ class Controller(object): if id in metadata: return {'meta': {id: metadata[id]}} else: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def create(self, req, image_id, body): context = req.environ['nova.context'] @@ -105,7 +104,7 @@ class Controller(object): img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id) if not id in metadata: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() metadata.pop(id) img['properties'] = metadata self.image_service.update(context, image_id, img, None) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d0317583e..96c5cd3fa 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -25,7 +25,6 @@ from nova import flags import nova.image from nova import log from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import image_metadata from nova.api.openstack import servers from nova.api.openstack.views import images as images_view @@ -78,7 +77,7 @@ class Controller(object): image = self._image_service.show(context, id) except (exception.NotFound, exception.InvalidImageRef): explanation = _("Image not found.") - raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation)) + raise webob.exc.HTTPNotFound(explanation=explanation) return dict(image=self.get_builder(req).build(image, detail=True)) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 1ebfdb831..2996b032d 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -20,7 +20,6 @@ import time from webob import exc import nova -from nova.api.openstack import faults import nova.api.openstack.views.addresses from nova.api.openstack import wsgi from nova import db @@ -37,14 +36,14 @@ class Controller(object): instance = self.compute_api.get( req.environ['nova.context'], server_id) except nova.exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return instance def create(self, req, server_id, body): - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id): - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() class ControllerV10(Controller): @@ -63,7 +62,7 @@ class ControllerV10(Controller): view = builder.build_public_parts(instance) else: msg = _("Only private and public networks available") - return faults.Fault(exc.HTTPNotFound(explanation=msg)) + raise exc.HTTPNotFound(explanation=msg) return {id: view} @@ -86,7 +85,7 @@ class ControllerV11(Controller): if network is None: msg = _("Instance is not a member of specified network") - return faults.Fault(exc.HTTPNotFound(explanation=msg)) + raise exc.HTTPNotFound(explanation=msg) return network diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 3b9169f81..d4f42bbf5 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -18,7 +18,6 @@ from webob import exc from nova import compute -from nova.api.openstack import faults from nova.api.openstack import wsgi from nova import exception from nova import quota diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3b3d0685d..7bef1d9b2 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -27,7 +27,6 @@ from nova import log as logging from nova import utils from nova.api.openstack import common from nova.api.openstack import create_instance_helper as helper -from nova.api.openstack import faults import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors import nova.api.openstack.views.images @@ -102,17 +101,14 @@ class Controller(object): req.environ['nova.context'], id) return self._build_view(req, instance, is_detail=True) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def create(self, req, body): """ Creates a new server for a given user """ extra_values = None result = None - try: - extra_values, instances = self.helper.create_instance( - req, body, self.compute_api.create) - except faults.Fault, f: - return f + extra_values, instances = self.helper.create_instance( + req, body, self.compute_api.create) # We can only return 1 instance via the API, if we happen to # build more than one... instances is a list, so we'll just @@ -132,7 +128,7 @@ class Controller(object): raise exc.HTTPUnprocessableEntity() if not body: - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() ctxt = req.environ['nova.context'] update_dict = {} @@ -147,7 +143,7 @@ class Controller(object): try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return exc.HTTPNoContent() @@ -171,7 +167,7 @@ class Controller(object): for key in actions.keys(): if key in body: return actions[key](body, req, id) - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def _action_change_password(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -181,7 +177,7 @@ class Controller(object): self.compute_api.confirm_resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in confirm-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return exc.HTTPNoContent() def _action_revert_resize(self, input_dict, req, id): @@ -189,7 +185,7 @@ class Controller(object): self.compute_api.revert_resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in revert-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) def _action_resize(self, input_dict, req, id): @@ -200,14 +196,14 @@ class Controller(object): reboot_type = input_dict['reboot']['type'] else: LOG.exception(_("Missing argument 'type' for reboot")) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver self.compute_api.reboot(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in reboot %s"), e) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) def _action_migrate(self, input_dict, req, id): @@ -215,7 +211,7 @@ class Controller(object): self.compute_api.resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in migrate %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -231,7 +227,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::lock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -247,7 +243,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::unlock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -262,7 +258,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::get_lock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -277,7 +273,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::reset_network %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -292,7 +288,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::inject_network_info %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -304,7 +300,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::pause %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -316,7 +312,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::unpause %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -328,7 +324,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::suspend %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -340,7 +336,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::resume %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -352,7 +348,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::rescue %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -364,7 +360,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::unrescue %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -374,7 +370,7 @@ class Controller(object): self.compute_api.get_ajax_console(req.environ['nova.context'], int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -384,7 +380,7 @@ class Controller(object): self.compute_api.get_vnc_console(req.environ['nova.context'], int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -416,7 +412,7 @@ class ControllerV10(Controller): try: self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) def _image_ref_from_req_data(self, data): @@ -440,17 +436,13 @@ class ControllerV10(Controller): def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ - try: - if 'resize' in input_dict and 'flavorId' in input_dict['resize']: - flavor_id = input_dict['resize']['flavorId'] - self.compute_api.resize(req.environ['nova.context'], id, - flavor_id) - else: - LOG.exception(_("Missing 'flavorId' argument for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) - except Exception, e: - LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + if 'resize' in input_dict and 'flavorId' in input_dict['resize']: + flavor_id = input_dict['resize']['flavorId'] + self.compute_api.resize(req.environ['nova.context'], id, + flavor_id) + else: + LOG.exception(_("Missing 'flavorId' argument for resize")) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): @@ -462,14 +454,14 @@ class ControllerV10(Controller): except (KeyError, TypeError): msg = _("Could not parse imageId from request.") LOG.debug(msg) - return faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) try: self.compute_api.rebuild(context, instance_id, image_id) except exception.BuildInProgress: msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) - return faults.Fault(exc.HTTPConflict(explanation=msg)) + raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) @@ -486,7 +478,7 @@ class ControllerV11(Controller): try: self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def _image_ref_from_req_data(self, data): return data['server']['imageRef'] @@ -530,7 +522,7 @@ class ControllerV11(Controller): except AttributeError as ex: msg = _("Unable to parse metadata key/value pairs.") LOG.debug(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) def _decode_personalities(self, personalities): """Decode the Base64-encoded personalities.""" @@ -541,14 +533,14 @@ class ControllerV11(Controller): except (KeyError, TypeError): msg = _("Unable to parse personality path/contents.") LOG.info(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) try: personality["contents"] = base64.b64decode(contents) except TypeError: msg = _("Personality content could not be Base64 decoded.") LOG.info(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ @@ -560,10 +552,10 @@ class ControllerV11(Controller): flavor_id) else: LOG.exception(_("Missing 'flavorRef' argument for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() except Exception, e: LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): @@ -575,7 +567,7 @@ class ControllerV11(Controller): except (KeyError, TypeError): msg = _("Could not parse imageRef from request.") LOG.debug(msg) - return faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) personalities = info["rebuild"].get("personality", []) metadata = info["rebuild"].get("metadata") @@ -591,7 +583,7 @@ class ControllerV11(Controller): except exception.BuildInProgress: msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) - return faults.Fault(exc.HTTPConflict(explanation=msg)) + raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index cf2ddbabb..54d0a8334 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -17,7 +17,6 @@ from webob import exc -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -26,27 +25,27 @@ class Controller(object): def index(self, req, **kwargs): """ Returns a list of Shared IP Groups for the user """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def show(self, req, id, **kwargs): """ Shows in-depth information on a specific Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def update(self, req, id, **kwargs): """ You can't update a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, id, **kwargs): """ Deletes a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def detail(self, req, **kwargs): """ Returns a complete list of Shared IP Groups """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create(self, req, **kwargs): """ Creates a new Shared IP group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create_resource(): diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index 6ae1eaf2a..8dd72d559 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -19,7 +19,6 @@ from nova import exception from nova import flags from nova import log as logging from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import wsgi from nova.auth import manager @@ -69,7 +68,7 @@ class Controller(object): user = None if user is None: - raise faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return dict(user=_translate_keys(user)) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c3f841aa5..05767da19 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -441,7 +441,11 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) - action_result = self.dispatch(request, action, args) + try: + action_result = self.dispatch(request, action, args) + except webob.exc.HTTPException as ex: + LOG.info(_("HTTP exception thrown: %s"), unicode(ex)) + action_result = faults.Fault(ex) #TODO(bcwaldon): find a more elegant way to pass through non-dict types if type(action_result) is dict or action_result is None: diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 2e02ec380..f7fd87bcd 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -27,7 +27,6 @@ from nova.scheduler import api from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -127,11 +126,8 @@ class Controller(object): Returns a reservation ID (a UUID). """ result = None - try: - extra_values, result = self.helper.create_instance(req, body, - self.compute_api.create_all_at_once) - except faults.Fault, f: - return f + extra_values, result = self.helper.create_instance(req, body, + self.compute_api.create_all_at_once) reservation_id = result return {'reservation_id': reservation_id} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 1577c922b..22e2a4b5b 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1707,7 +1707,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) + self.assertEqual(res.status_int, 500) def test_resized_server_has_correct_status(self): req = self.webreq('/1', 'GET') -- cgit From 822b5ecede8f18120ae37eb07ff046101f50d3aa Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 11:10:47 -0400 Subject: cleanup tests and fix pep8 issues --- nova/api/openstack/versions.py | 20 +++++++++++--------- nova/tests/api/openstack/test_versions.py | 23 +++++++++++------------ 2 files changed, 22 insertions(+), 21 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 49633df88..8e172ae02 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -43,8 +43,8 @@ class Versions(wsgi.Resource): } serializer = wsgi.ResponseSerializer(body_serializers) - supported_content_types = ('application/json', - 'application/xml', + supported_content_types = ('application/json', + 'application/xml', 'application/atom+xml') deserializer = wsgi.RequestDeserializer( supported_content_types=supported_content_types) @@ -73,6 +73,7 @@ class Versions(wsgi.Resource): versions = [builder.build(version) for version in version_objs] return dict(versions=versions) + class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions): root = self._xml_doc.createElement('versions') @@ -96,13 +97,13 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return version_node - def default(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) return self.to_xml_string(node) + class VersionsAtomSerializer(wsgi.XMLDictSerializer): def __init__(self, metadata=None, xmlns=None): if not xmlns: @@ -121,7 +122,8 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def _get_most_recent_update(self, versions): recent = None for version in versions: - updated = datetime.strptime(version['updated'],'%Y-%m-%dT%H:%M:%SZ') + updated = datetime.strptime(version['updated'], + '%Y-%m-%dT%H:%M:%SZ') if not recent: recent = updated elif updated > recent: @@ -132,7 +134,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def _get_base_url(self, link_href): # Make sure no trailing / link_href = link_href.rstrip('/') - return link_href.rsplit('/',1)[0] + '/' + return link_href.rsplit('/', 1)[0] + '/' def _create_meta(self, root, versions): title = self._create_text_elem('title', 'Available API Versions', @@ -164,7 +166,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): entry = self._xml_doc.createElement('entry') id = self._create_text_elem('id', version['links'][0]['href']) - title = self._create_text_elem('title', + title = self._create_text_elem('title', 'Version %s' % version['id'], type='text') updated = self._create_text_elem('updated', version['updated']) @@ -179,9 +181,9 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): link_node.setAttribute('href', link['href']) entry.appendChild(link_node) - content = self._create_text_elem('content', - 'Version %s %s (%s)' % - (version['id'], + content = self._create_text_elem('content', + 'Version %s %s (%s)' % + (version['id'], version['status'], version['updated']), type='text') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 471afec94..6139731b9 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -47,7 +47,7 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1", + "href": "http://localhost/v1.1/", }], }, { @@ -57,7 +57,7 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.0", + "href": "http://localhost/v1.0/", }], }, ] @@ -72,11 +72,11 @@ class VersionsTest(test.TestCase): expected = """ - + - - + + """.replace(" ", "").replace("\n", "") @@ -120,9 +120,9 @@ class VersionsTest(test.TestCase): - """.replace(" ", "").replace("\n", "").replace("\t", "") + """.replace(" ", "").replace("\n", "") - actual = res.body.replace(" ", "").replace("\n", "").replace("\t","") + actual = res.body.replace(" ", "").replace("\n", "") self.assertEqual(expected, actual) @@ -132,8 +132,7 @@ class VersionsTest(test.TestCase): version_data = { "id": "3.2.1", "status": "CURRENT", - "updated": "2011-07-18T11:30:00Z" - } + "updated": "2011-07-18T11:30:00Z"} expected = { "id": "3.2.1", @@ -142,7 +141,7 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://example.org/3.2.1", + "href": "http://example.org/3.2.1/", }, ], } @@ -156,7 +155,7 @@ class VersionsTest(test.TestCase): base_url = "http://example.org/app/" version_number = "v1.4.6" - expected = "http://example.org/app/v1.4.6" + expected = "http://example.org/app/v1.4.6/" builder = views.versions.ViewBuilder(base_url) actual = builder.generate_href(version_number) -- cgit From 210038b8d36e74ecfa791c1a25b5a8529cecdca0 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 11:54:03 -0400 Subject: cleanup --- nova/tests/api/openstack/test_limits.py | 39 +++++++++++---------------------- 1 file changed, 13 insertions(+), 26 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 9859c17f8..4d032b093 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -25,11 +25,11 @@ import time import unittest import webob -from xml.dom.minidom import parseString +import xml.dom.minidom -from nova import test import nova.context from nova.api.openstack import limits +from nova import test TEST_LIMITS = [ @@ -910,18 +910,8 @@ class WsgiLimiterProxyTest(BaseLimitTestSuite): class LimitsViewBuilderV11Test(test.TestCase): def setUp(self): - self.view_builder = self._get_view_builder() - - def tearDown(self): - pass - - def _get_view_builder(self): - views = nova.api.openstack.views - view_builder = views.limits.ViewBuilderV11() - return view_builder - - def _get_rate_limits(self): - rate_limits = [ + self.view_builder = views.limits.ViewBuilderV11() + self.rate_limits = [ { "URI": "*", "regex": ".*", @@ -941,17 +931,14 @@ class LimitsViewBuilderV11Test(test.TestCase): "resetTime": "2011-12-15T22:42:45Z" }, ] - - return rate_limits - - def _get_absolute_limits(self): - absolute_limits = { + self.absolute_limits = { "metadata_items": 1, "injected_files": 5, "injected_file_content_bytes": 5, } - return absolute_limits + def tearDown(self): + pass def test_build_limits(self): expected_limits = { @@ -993,8 +980,8 @@ class LimitsViewBuilderV11Test(test.TestCase): } } - output = self.view_builder.build(self._get_rate_limits(), - self._get_absolute_limits()) + output = self.view_builder.build(self.rate_limits, + self.absolute_limits) self.assertDictMatch(output, expected_limits) def test_build_limits_empty_limits(self): @@ -1062,7 +1049,7 @@ class LimitsXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture, 'index') - actual = parseString(output.replace(" ", "")) + actual = minidom.parseString(output.replace(" ", "")) expected = parseString(""" @@ -1085,7 +1072,7 @@ class LimitsXMLSerializationTest(test.TestCase): - """.replace(" ", "") % (locals())) + """.replace(" ", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -1100,13 +1087,13 @@ class LimitsXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture, 'index') - actual = parseString(output.replace(" ", "")) + actual = minidom.parseString(output.replace(" ", "")) expected = parseString(""" - """.replace(" ", "") % (locals())) + """.replace(" ", "")) self.assertEqual(expected.toxml(), actual.toxml()) -- cgit From 9a843b10f3145405ebc01ded5d32ce68d02fdd8d Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 11:56:15 -0400 Subject: pep8 issue --- nova/api/openstack/create_instance_helper.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2ef83332b..10aaa23b1 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -157,8 +157,6 @@ class CreateInstanceHelper(object): except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) - - # Let the caller deal with unhandled exceptions. def _handle_quota_error(self, error): -- cgit From ca285e0cdede6f3332856e63d7828616aefdc4cc Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 11:57:44 -0400 Subject: minor cleanup --- nova/tests/api/openstack/test_limits.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 4d032b093..d9f0ac942 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -24,7 +24,6 @@ import stubout import time import unittest import webob - import xml.dom.minidom import nova.context -- cgit From dc616cd633007aa83d7576ae74cf807aa0df6776 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 12:02:01 -0400 Subject: fixed another issue with invalid flavor_id parsing, and added tests --- nova/api/openstack/create_instance_helper.py | 6 +++++- nova/tests/api/openstack/test_servers.py | 32 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 10aaa23b1..2b9256896 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -102,7 +102,11 @@ class CreateInstanceHelper(object): if personality: injected_files = self._get_injected_files(personality) - flavor_id = self.controller._flavor_id_from_req_data(body) + try: + flavor_id = self.controller._flavor_id_from_req_data(body) + except ValueError as error: + msg = _("Invalid flavorRef provided.") + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) if not 'name' in body['server']: msg = _("Server name is not defined") diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 3fc38b73c..7ceb0c075 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -997,6 +997,38 @@ class ServersTest(test.TestCase): self.assertEqual(flavor_ref, server['flavorRef']) self.assertEqual(image_href, server['imageRef']) + def test_create_instance_v1_1_invalid_flavor_href(self): + self._setup_for_create_instance() + + image_href = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/v1.1/flavors/asdf' + body = dict(server=dict( + name='server_test', imageRef=image_href, flavorRef=flavor_ref, + metadata={'hello': 'world', 'open': 'stack'}, + personality={})) + 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_bad_flavor_href(self): + self._setup_for_create_instance() + + image_href = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/v1.1/flavors/17' + body = dict(server=dict( + name='server_test', imageRef=image_href, flavorRef=flavor_ref, + metadata={'hello': 'world', 'open': 'stack'}, + personality={})) + 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_bad_href(self): self._setup_for_create_instance() -- cgit From a57504ef9a8b3e2c5d78cfe4ecf2f21c03653556 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 12:19:09 -0400 Subject: added tests, updated pep8 fixes --- nova/api/openstack/versions.py | 1 - nova/tests/api/openstack/test_versions.py | 91 +++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 8e172ae02..df7a94b7e 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -28,7 +28,6 @@ ATOM_XMLNS = "http://www.w3.org/2005/Atom" class Versions(wsgi.Resource): - def __init__(self): metadata = { "attributes": { diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 6139731b9..0eae18a62 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -21,6 +21,7 @@ import webob from nova import context from nova import test from nova.tests.api.openstack import fakes +from nova.api.openstack import versions from nova.api.openstack import views @@ -161,3 +162,93 @@ class VersionsTest(test.TestCase): actual = builder.generate_href(version_number) self.assertEqual(actual, expected) + + def test_xml_serializer(self): + versions_data = { + 'versions': [ + { + "id": "2.7.1", + "updated": "2011-07-18T11:30:00Z", + "status": "DEPRECATED", + "links": [ + { + "rel": "self", + "href": "http://test/2.7.1", + } + ], + }, + ] + } + + expected = """ + + + + + """.replace(" ", "").replace("\n", "") + + serializer = versions.VersionsXMLSerializer() + response = serializer.default(versions_data) + response = response.replace(" ", "").replace("\n", "") + self.assertEqual(expected, response) + + def test_atom_serializer(self): + versions_data = { + 'versions': [ + { + "id": "2.9.8", + "updated": "2011-07-20T11:40:00Z", + "status": "CURRENT", + "links": [ + { + "rel": "self", + "href": "http://test/2.9.8", + } + ], + }, + ] + } + + expected = """ + + + Available API Versions + + + 2011-07-20T11:40:00Z + + + http://test/ + + + + Rackspace + + + http://www.rackspace.com/ + + + + + + http://test/2.9.8 + + + Version 2.9.8 + + + 2011-07-20T11:40:00Z + + + + Version 2.9.8 CURRENT (2011-07-20T11:40:00Z) + + + """.replace(" ", "").replace("\n", "") + + serializer = versions.VersionsAtomSerializer() + response = serializer.default(versions_data) + print response + response = response.replace(" ", "").replace("\n", "") + self.assertEqual(expected, response) -- cgit From f7988845281ab2fb963eaf059a6308917db79ff8 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 12:51:29 -0400 Subject: Fixed import issue --- nova/tests/api/openstack/test_limits.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index d9f0ac942..9edcf1b73 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -24,10 +24,11 @@ import stubout import time import unittest import webob -import xml.dom.minidom +from xml.dom import minidom import nova.context from nova.api.openstack import limits +from nova.api.openstack import views from nova import test @@ -166,7 +167,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite): request = self._get_index_request("application/xml") response = request.get_response(self.controller) - expected = parseString(""" + expected = minidom.parseString(""" @@ -174,7 +175,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite): """.replace(" ", "")) - body = parseString(response.body.replace(" ", "")) + body = minidom.parseString(response.body.replace(" ", "")) self.assertEqual(expected.toxml(), body.toxml()) @@ -184,7 +185,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite): request = self._populate_limits(request) response = request.get_response(self.controller) - expected = parseString(""" + expected = minidom.parseString(""" @@ -196,7 +197,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite): """.replace(" ", "")) - body = parseString(response.body.replace(" ", "")) + body = minidom.parseString(response.body.replace(" ", "")) self.assertEqual(expected.toxml(), body.toxml()) @@ -458,7 +459,7 @@ class LimitMiddlewareTest(BaseLimitTestSuite): response = request.get_response(self.app) self.assertEqual(response.status_int, 403) - root = parseString(response.body).childNodes[0] + root = minidom.parseString(response.body).childNodes[0] expected = "Only 1 GET request(s) can be made to * every minute." details = root.getElementsByTagName("details") @@ -1050,7 +1051,7 @@ class LimitsXMLSerializationTest(test.TestCase): output = serializer.serialize(fixture, 'index') actual = minidom.parseString(output.replace(" ", "")) - expected = parseString(""" + expected = minidom.parseString(""" @@ -1088,7 +1089,7 @@ class LimitsXMLSerializationTest(test.TestCase): output = serializer.serialize(fixture, 'index') actual = minidom.parseString(output.replace(" ", "")) - expected = parseString(""" + expected = minidom.parseString(""" -- cgit From 1230d93f6154d73fcd8ce1c30c629d8a04c1c874 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Thu, 21 Jul 2011 11:52:58 -0500 Subject: Some broken tests from my other merge --- nova/compute/manager.py | 2 +- nova/tests/test_compute.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c9afa8072..893727f38 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -748,7 +748,7 @@ class ComputeManager(manager.SchedulerDependentManager): raise exception.Error(_( 'Migration error: destination same as source!')) - old_instance_type = self.db.instance_type_get_by_id(context, + old_instance_type = self.db.instance_type_get(context, instance_ref['instance_type_id']) migration_ref = self.db.migration_create(context, diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index dc3f0596d..ce32ff1de 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -535,7 +535,7 @@ class ComputeTestCase(test.TestCase): # Confirm the instance size before the resize starts inst_ref = db.instance_get(context, instance_id) - instance_type_ref = db.instance_type_get_by_id(context, + instance_type_ref = db.instance_type_get(context, inst_ref['instance_type_id']) self.assertEqual(instance_type_ref['flavorid'], 1) @@ -553,7 +553,7 @@ class ComputeTestCase(test.TestCase): # Prove that the instance size is now the new size inst_ref = db.instance_get(context, instance_id) - instance_type_ref = db.instance_type_get_by_id(context, + instance_type_ref = db.instance_type_get(context, inst_ref['instance_type_id']) self.assertEqual(instance_type_ref['flavorid'], 3) @@ -564,7 +564,7 @@ class ComputeTestCase(test.TestCase): migration_ref['id']) inst_ref = db.instance_get(context, instance_id) - instance_type_ref = db.instance_type_get_by_id(context, + instance_type_ref = db.instance_type_get(context, inst_ref['instance_type_id']) self.assertEqual(instance_type_ref['flavorid'], 1) -- cgit From 848de98876aecd084bb568907c3b121a7f5e38f0 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Thu, 21 Jul 2011 12:45:53 -0500 Subject: allow 2 dns servers to be specified on network create --- nova/db/sqlalchemy/models.py | 1 + nova/network/manager.py | 10 +++++++--- nova/tests/__init__.py | 3 ++- nova/tests/test_network.py | 2 ++ 4 files changed, 12 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 45e0f89c9..2393cf40a 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -556,6 +556,7 @@ class Network(BASE, NovaBase): gateway = Column(String(255)) broadcast = Column(String(255)) dns = Column(String(255)) + dns2 = Column(String(255)) vlan = Column(Integer) vpn_public_address = Column(String(255)) diff --git a/nova/network/manager.py b/nova/network/manager.py index 34001657b..a5cf644ab 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -481,6 +481,9 @@ class NetworkManager(manager.SchedulerDependentManager): # TODO(tr3buchet): handle ip6 routes here as well if network['gateway_v6']: info['gateway6'] = network['gateway_v6'] + if network['dns2']: + info['dns'].append(network['dns2']) + network_info.append((network_dict, info)) return network_info @@ -591,7 +594,7 @@ class NetworkManager(manager.SchedulerDependentManager): def create_networks(self, context, label, cidr, multi_host, num_networks, network_size, cidr_v6, gateway_v6, bridge, - bridge_interface, **kwargs): + bridge_interface, dns, dns2=None, **kwargs): """Create networks based on parameters.""" fixed_net = netaddr.IPNetwork(cidr) fixed_net_v6 = netaddr.IPNetwork(cidr_v6) @@ -606,7 +609,8 @@ class NetworkManager(manager.SchedulerDependentManager): net = {} net['bridge'] = bridge net['bridge_interface'] = bridge_interface - net['dns'] = FLAGS.flat_network_dns + net['dns'] = dns + net['dns2'] = dns2 net['cidr'] = cidr net['multi_host'] = multi_host net['netmask'] = str(project_net.netmask) @@ -636,6 +640,7 @@ class NetworkManager(manager.SchedulerDependentManager): if kwargs.get('vpn', False): # this bit here is for vlan-manager del net['dns'] + del net['dns2'] vlan = kwargs['vlan_start'] + index net['vpn_private_address'] = str(project_net[2]) net['dhcp_start'] = str(project_net[3]) @@ -755,7 +760,6 @@ class FlatManager(NetworkManager): """Setup Network on this host.""" net = {} net['injected'] = FLAGS.flat_injected - net['dns'] = FLAGS.flat_network_dns self.db.network_update(context, network_ref['id'], net) diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py index 7df6c06eb..49c1ccc56 100644 --- a/nova/tests/__init__.py +++ b/nova/tests/__init__.py @@ -67,7 +67,8 @@ def setup(): bridge=FLAGS.flat_network_bridge, bridge_interface=bridge_interface, vpn_start=FLAGS.vpn_start, - vlan_start=FLAGS.vlan_start) + vlan_start=FLAGS.vlan_start, + dns=FLAGS.flat_network_dns) for net in db.network_get_all(ctxt): network.set_network_host(ctxt, net) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 7cc174842..9fefc6959 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -56,6 +56,7 @@ networks = [{'id': 0, 'gateway': '192.168.0.1', 'broadcast': '192.168.0.255', 'dns': '192.168.0.1', + 'dns2': '192.168.0.2', 'vlan': None, 'host': None, 'project_id': 'fake_project', @@ -74,6 +75,7 @@ networks = [{'id': 0, 'gateway': '192.168.1.1', 'broadcast': '192.168.1.255', 'dns': '192.168.0.1', + 'dns2': '192.168.0.2', 'vlan': None, 'host': None, 'project_id': 'fake_project', -- cgit From d04b90ef23bd2e02e762ed22bce8729a2c619cf6 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Thu, 21 Jul 2011 12:46:18 -0500 Subject: allow 2 dns servers to be specified on network create --- .../migrate_repo/versions/034_secondary_dns.py | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/034_secondary_dns.py (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/034_secondary_dns.py b/nova/db/sqlalchemy/migrate_repo/versions/034_secondary_dns.py new file mode 100644 index 000000000..d9f82d796 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/034_secondary_dns.py @@ -0,0 +1,36 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 OpenStack, LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import Column, Table, MetaData, Boolean, String + +meta = MetaData() + +dns2 = Column('dns2', String(255)) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + networks = Table('networks', meta, autoload=True) + networks.create_column(dns2) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + networks = Table('networks', meta, autoload=True) + networks.drop_column(dns2) -- cgit From 5913e537ceb352ec4e5999cdadb1d826771d5d72 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 21 Jul 2011 14:21:27 -0400 Subject: Updated the compute API so that has_finished_migration uses instance_uuid. Fixes some regressions with 1295-1296. --- nova/api/openstack/views/servers.py | 2 +- nova/compute/api.py | 4 ++-- nova/db/api.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index ab7e8da61..7131db088 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -82,7 +82,7 @@ class ViewBuilder(object): ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() - if compute_api.has_finished_migration(ctxt, inst['id']): + if compute_api.has_finished_migration(ctxt, inst['uuid']): inst_dict['status'] = 'RESIZE-CONFIRM' # Return the metadata as a dictionary diff --git a/nova/compute/api.py b/nova/compute/api.py index 67aa3c20f..9994e5724 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -467,10 +467,10 @@ class API(base.Base): return [dict(x.iteritems()) for x in instances] - def has_finished_migration(self, context, instance_id): + 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_id, + db.migration_get_by_instance_and_status(context, instance_uuid, 'finished') return True except exception.NotFound: diff --git a/nova/db/api.py b/nova/db/api.py index d69732920..47308bdba 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -314,9 +314,9 @@ def migration_get(context, migration_id): return IMPL.migration_get(context, migration_id) -def migration_get_by_instance_and_status(context, instance_id, status): - """Finds a migration by the instance id its migrating.""" - return IMPL.migration_get_by_instance_and_status(context, instance_id, +def migration_get_by_instance_and_status(context, instance_uuid, status): + """Finds a migration by the instance uuid its migrating.""" + return IMPL.migration_get_by_instance_and_status(context, instance_uuid, status) -- cgit From 5133f92e5c00395b9a9d690e86815aa9c97cda9e Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 21 Jul 2011 14:51:55 -0400 Subject: pep8 and stuff. --- nova/api/openstack/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index f41d25537..57031ebf1 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -77,7 +77,7 @@ def limited(items, request, max_limit=FLAGS.osapi_max_limit): try: offset = int(request.GET.get('offset', 0)) except ValueError: - msg = _('offset param must be an integer') + msg = _('offset param must be an integer') raise webob.exc.HTTPBadRequest(explanation=msg) try: @@ -115,7 +115,7 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit): start_index = i + 1 break if start_index < 0: - msg = _('marker [%s] not found' % marker) + msg = _('marker [%s] not found') % marker raise webob.exc.HTTPBadRequest(explanation=msg) range_end = start_index + limit return items[start_index:range_end] -- cgit From af5c549d7795fcf9fdcb7d8e9193aa985f121fc6 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 21 Jul 2011 15:08:46 -0400 Subject: Moving lp:~rackspace-titan/nova/extensions-xml-serialization to new branch based off of trunk. To remove dep on another branch. --- nova/api/openstack/extensions.py | 43 +++++++++-- nova/api/openstack/wsgi.py | 4 + nova/tests/api/openstack/test_extensions.py | 113 +++++++++++++++++++++++++++- 3 files changed, 152 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index da06ecd15..e500e51fa 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -23,6 +23,7 @@ import sys import routes import webob.dec import webob.exc +from xml.etree import ElementTree from nova import exception from nova import flags @@ -194,7 +195,7 @@ class ExtensionsResource(wsgi.Resource): def show(self, req, id): # NOTE(dprince): the extensions alias is used as the 'id' for show ext = self.extension_manager.extensions[id] - return self._translate(ext) + return dict(extension=self._translate(ext)) def delete(self, req, id): raise faults.Fault(webob.exc.HTTPNotFound()) @@ -258,15 +259,18 @@ class ExtensionMiddleware(base_wsgi.Middleware): mapper = routes.Mapper() + serializer = wsgi.ResponseSerializer( + {'application/xml': ExtensionsXMLSerializer()}) # extended resources for resource in ext_mgr.get_resources(): LOG.debug(_('Extended resource: %s'), resource.collection) mapper.resource(resource.collection, resource.collection, - controller=wsgi.Resource(resource.controller), - collection=resource.collection_actions, - member=resource.member_actions, - parent_resource=resource.parent) + controller=wsgi.Resource( + resource.controller, serializer=serializer), + collection=resource.collection_actions, + member=resource.member_actions, + parent_resource=resource.parent) # extended actions action_resources = self._action_ext_resources(application, ext_mgr, @@ -462,3 +466,32 @@ class ResourceExtension(object): self.parent = parent self.collection_actions = collection_actions self.member_actions = member_actions + + +class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): + + def _add_extension_attributes(self, node, extension): + node.setAttribute('name', extension['name']) + node.setAttribute('namespace', extension['namespace']) + node.setAttribute('alias', extension['alias']) + node.setAttribute('updated', extension['updated']) + + def show(self, ext_dict): + root = ElementTree.Element('extension'); + extension = ext_dict['extension'] + root.set('xmlns', wsgi.XMLNS_V11) + root.set('xmlns:atom', wsgi.XMLNS_ATOM) + root.set('name', extension['name']) + root.set('namespace', extension['namespace']) + root.set('alias', extension['alias']) + root.set('updated', extension['updated']) + desc = ElementTree.Element('description'); + desc.text = extension['description'] + root.append(desc) + for link in extension.get('links', []): + elem = ElementTree.Element('atom:link'); + elem.set('rel', link['rel']) + elem.set('href', link['href']) + elem.set('type', link['type']) + root.append(elem) + return ElementTree.tostring(root) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 9df6fd058..d9993282f 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -13,6 +13,7 @@ from nova import wsgi XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1' +XMLNS_ATOM = 'http://www.w3.org/2005/Atom' LOG = logging.getLogger('nova.api.openstack.wsgi') @@ -352,6 +353,9 @@ class XMLDictSerializer(DictSerializer): link_node = xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) + if link.get('type'): + link_node.setAttribute('type', link['type']) + link_nodes.append(link_node) return link_nodes diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 697c62e5c..6d3c1b1be 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -16,10 +16,11 @@ # under the License. import json +import os.path import stubout import unittest import webob -import os.path +from xml.etree import ElementTree from nova import context from nova import flags @@ -80,19 +81,73 @@ class StubExtensionManager(object): class ExtensionControllerTest(unittest.TestCase): - def test_index(self): + def setUp(self): + FLAGS.osapi_extensions_path = os.path.join( + os.path.dirname(__file__), "extensions") + + def test_list_extensions_json(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) request = webob.Request.blank("/extensions") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) - def test_get_by_alias(self): + # Make sure we have all the extensions. + data = json.loads(response.body) + names = [x['name'] for x in data['extensions']] + names.sort() + self.assertEqual(names, ["FlavorExtraSpecs", "Floating_ips", + "Fox In Socks", "Hosts", "Multinic", "Volumes"]) + + # Make sure that at least Fox in Sox is correct. + (fox_ext,) = [ + x for x in data['extensions'] if x['alias'] == 'FOXNSOX'] + self.assertEqual(fox_ext, { + "namespace" : "http://www.fox.in.socks/api/ext/pie/v1.0", + "name" : "Fox In Socks", + "updated" : "2011-01-22T13:25:27-06:00", + "description" : "The Fox In Socks Extension", + "alias" : "FOXNSOX", + "links" : [] + } + ) + + def test_get_extension_json(self): + app = openstack.APIRouterV11() + ext_midware = extensions.ExtensionMiddleware(app) + request = webob.Request.blank("/extensions/FOXNSOX") + response = request.get_response(ext_midware) + self.assertEqual(200, response.status_int) + + data = json.loads(response.body) + self.assertEqual(data['extension'], { + "namespace" : "http://www.fox.in.socks/api/ext/pie/v1.0", + "name" : "Fox In Socks", + "updated" : "2011-01-22T13:25:27-06:00", + "description" : "The Fox In Socks Extension", + "alias" : "FOXNSOX", + "links" : [] + } + ) + + def test_get_extension_xml(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) request = webob.Request.blank("/extensions/FOXNSOX") + request.accept = "application/xml" response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) + print response.body + + elem = ElementTree.XML(response.body.replace(" ", "")) + self.assertEqual(elem.get('alias'), 'FOXNSOX') + self.assertEqual(elem.get('name'), 'Fox In Socks') + self.assertEqual(elem.get('namespace'), + 'http://www.fox.in.socks/api/ext/pie/v1.0') + self.assertEqual(elem.get('updated'), '2011-01-22T13:25:27-06:00') + ns = "{http://docs.openstack.org/compute/api/v1.1}" + self.assertEqual(elem.findtext('{0}description'.format(ns)), + 'The Fox In Socks Extension') class ResourceExtensionTest(unittest.TestCase): @@ -244,3 +299,55 @@ class RequestExtensionTest(unittest.TestCase): response_data = json.loads(response.body) self.assertEqual('newblue', response_data['flavor']['googoose']) self.assertEqual("Pig Bands!", response_data['big_bands']) + +class ExtensionsXMLSerializerTest(unittest.TestCase): + def test_serialize(self): + serializer = extensions.ExtensionsXMLSerializer() + data = { + 'extension': { + 'name': 'ext1', + "namespace" : "http://docs.rack.com/servers/api/ext/pie/v1.0", + "alias" : "RS-PIE", + "updated" : "2011-01-22T13:25:27-06:00", + "description" : "Adds the capability to share an image.", + "links" : [ + { + "rel" : "describedby", + "type" : "application/pdf", + "href" : "http://docs.rack.com/servers/api/ext/cs.pdf" + }, + { + "rel" : "describedby", + "type" : "application/vnd.sun.wadl+xml", + "href" : "http://docs.rack.com/servers/api/ext/cs.wadl" + } + ] + }, + } + + ns = "{http://docs.openstack.org/compute/api/v1.1}" + atomns = "{http://www.w3.org/2005/Atom}" + elem = ElementTree.XML(serializer.serialize(data, 'show')) + self.assertEqual(elem.tag.split('extension')[0], ns) + self.assertEqual(elem.get('alias'), 'RS-PIE') + self.assertEqual(elem.get('name'), 'ext1') + self.assertEqual(elem.get('namespace'), + 'http://docs.rack.com/servers/api/ext/pie/v1.0') + self.assertEqual(elem.get('updated'), '2011-01-22T13:25:27-06:00') + self.assertEqual(elem.findtext('{0}description'.format(ns)), + 'Adds the capability to share an image.') + + link_nodes = elem.findall('{0}link'.format(atomns)) + self.assertEqual(len(link_nodes), 2) + link_nodes.sort(key=lambda x: x.get('type')) + + self.assertEqual(link_nodes[0].get('type'), 'application/pdf') + self.assertEqual(link_nodes[0].get('rel'), 'describedby') + self.assertEqual(link_nodes[0].get('href'), + 'http://docs.rack.com/servers/api/ext/cs.pdf') + + self.assertEqual(link_nodes[1].get('type'), + 'application/vnd.sun.wadl+xml') + self.assertEqual(link_nodes[1].get('rel'), 'describedby') + self.assertEqual(link_nodes[1].get('href'), + 'http://docs.rack.com/servers/api/ext/cs.wadl') -- cgit From c1b4dd1b8e3a8043b494854bc9ddd5e6cd335ef0 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 16:16:34 -0400 Subject: Updated time-available to be correct format Fixed old tests to respect this --- nova/api/openstack/views/limits.py | 5 ++++- nova/tests/api/openstack/test_limits.py | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index 934b4921a..e34d47e63 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -15,9 +15,11 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import time from nova.api.openstack import common +from nova import utils class ViewBuilder(object): @@ -113,10 +115,11 @@ class ViewBuilderV11(ViewBuilder): return limits def _build_rate_limit(self, rate_limit): + next_avail = datetime.datetime.fromtimestamp(rate_limit["resetTime"]) return { "verb": rate_limit["verb"], "value": rate_limit["value"], "remaining": int(rate_limit["remaining"]), "unit": rate_limit["unit"], - "next-available": rate_limit["resetTime"], + "next-available": utils.isotime(at=next_avail), } diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 9edcf1b73..6a3f7e79a 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -211,6 +211,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite): """Run before each test.""" BaseLimitTestSuite.setUp(self) self.controller = limits.create_resource('1.1') + self.maxDiff = None def _get_index_request(self, accept_header="application/json"): """Helper to set routing arguments.""" @@ -267,14 +268,14 @@ class LimitsControllerV11Test(BaseLimitTestSuite): "limit": [ { "verb": "GET", - "next-available": 0, + "next-available": "1969-12-31T19:00:00Z", "unit": "MINUTE", "value": 10, "remaining": 10, }, { "verb": "POST", - "next-available": 0, + "next-available": "1969-12-31T19:00:00Z", "unit": "HOUR", "value": 5, "remaining": 5, @@ -287,7 +288,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite): "limit": [ { "verb": "GET", - "next-available": 0, + "next-available": "1969-12-31T19:00:00Z", "unit": "MINUTE", "value": 5, "remaining": 5, @@ -329,7 +330,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite): "limit": [ { "verb": "GET", - "next-available": 0, + "next-available": "1969-12-31T19:00:00Z", "unit": "MINUTE", "value": 10, "remaining": 10, @@ -342,7 +343,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite): "limit": [ { "verb": "GET", - "next-available": 0, + "next-available": "1969-12-31T19:00:00Z", "unit": "MINUTE", "value": 10, "remaining": 10, @@ -919,7 +920,7 @@ class LimitsViewBuilderV11Test(test.TestCase): "verb": "POST", "remaining": 2, "unit": "MINUTE", - "resetTime": "2011-12-15T22:42:45Z" + "resetTime": 1311272226 }, { "URI": "*/servers", @@ -928,7 +929,7 @@ class LimitsViewBuilderV11Test(test.TestCase): "verb": "POST", "remaining": 10, "unit": "DAY", - "resetTime": "2011-12-15T22:42:45Z" + "resetTime": 1311272226 }, ] self.absolute_limits = { @@ -953,7 +954,7 @@ class LimitsViewBuilderV11Test(test.TestCase): "verb": "POST", "remaining": 2, "unit": "MINUTE", - "next-available": "2011-12-15T22:42:45Z" + "next-available": "2011-07-21T14:17:06Z" }, ] }, @@ -966,7 +967,7 @@ class LimitsViewBuilderV11Test(test.TestCase): "verb": "POST", "remaining": 10, "unit": "DAY", - "next-available": "2011-12-15T22:42:45Z" + "next-available": "2011-07-21T14:17:06Z" }, ] }, -- cgit From b346a456e9189f95780bf9c9d061969319a6b427 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Thu, 21 Jul 2011 16:03:28 -0500 Subject: only include dns entries if they are not None in the database --- nova/network/manager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index a5cf644ab..7b36c61a3 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -474,13 +474,15 @@ class NetworkManager(manager.SchedulerDependentManager): 'broadcast': network['broadcast'], 'mac': vif['address'], 'rxtx_cap': flavor['rxtx_cap'], - 'dns': [network['dns']], + 'dns': [], 'ips': [ip_dict(ip) for ip in network_IPs]} if network['cidr_v6']: info['ip6s'] = [ip6_dict()] # TODO(tr3buchet): handle ip6 routes here as well if network['gateway_v6']: info['gateway6'] = network['gateway_v6'] + if network['dns']: + info['dns'].append(network['dns']) if network['dns2']: info['dns'].append(network['dns2']) -- cgit From 99d13a49fe32d22e0ed1642fcd790d45a8c201a3 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Thu, 21 Jul 2011 16:09:43 -0500 Subject: rename in preperation for trunk merge --- .../migrate_repo/versions/034_secondary_dns.py | 36 ---------------------- .../migrate_repo/versions/035_secondary_dns.py | 36 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 36 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/034_secondary_dns.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/035_secondary_dns.py (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/034_secondary_dns.py b/nova/db/sqlalchemy/migrate_repo/versions/034_secondary_dns.py deleted file mode 100644 index d9f82d796..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/034_secondary_dns.py +++ /dev/null @@ -1,36 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 OpenStack, LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from sqlalchemy import Column, Table, MetaData, Boolean, String - -meta = MetaData() - -dns2 = Column('dns2', String(255)) - - -def upgrade(migrate_engine): - meta.bind = migrate_engine - - networks = Table('networks', meta, autoload=True) - networks.create_column(dns2) - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - - networks = Table('networks', meta, autoload=True) - networks.drop_column(dns2) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/035_secondary_dns.py b/nova/db/sqlalchemy/migrate_repo/versions/035_secondary_dns.py new file mode 100644 index 000000000..d9f82d796 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/035_secondary_dns.py @@ -0,0 +1,36 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 OpenStack, LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import Column, Table, MetaData, Boolean, String + +meta = MetaData() + +dns2 = Column('dns2', String(255)) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + networks = Table('networks', meta, autoload=True) + networks.create_column(dns2) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + networks = Table('networks', meta, autoload=True) + networks.drop_column(dns2) -- cgit From f6514ba9fbb6b3c1939bde6ba081f632b3d0a24f Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 17:11:57 -0400 Subject: updated next-available to use utc time --- nova/api/openstack/views/limits.py | 2 +- nova/tests/api/openstack/test_limits.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index e34d47e63..c7163e33c 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -115,7 +115,7 @@ class ViewBuilderV11(ViewBuilder): return limits def _build_rate_limit(self, rate_limit): - next_avail = datetime.datetime.fromtimestamp(rate_limit["resetTime"]) + next_avail = datetime.datetime.utcfromtimestamp(rate_limit["resetTime"]) return { "verb": rate_limit["verb"], "value": rate_limit["value"], diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 6a3f7e79a..8a3fe681a 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -268,14 +268,14 @@ class LimitsControllerV11Test(BaseLimitTestSuite): "limit": [ { "verb": "GET", - "next-available": "1969-12-31T19:00:00Z", + "next-available": "1970-01-01T00:00:00Z", "unit": "MINUTE", "value": 10, "remaining": 10, }, { "verb": "POST", - "next-available": "1969-12-31T19:00:00Z", + "next-available": "1970-01-01T00:00:00Z", "unit": "HOUR", "value": 5, "remaining": 5, @@ -288,7 +288,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite): "limit": [ { "verb": "GET", - "next-available": "1969-12-31T19:00:00Z", + "next-available": "1970-01-01T00:00:00Z", "unit": "MINUTE", "value": 5, "remaining": 5, @@ -330,7 +330,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite): "limit": [ { "verb": "GET", - "next-available": "1969-12-31T19:00:00Z", + "next-available": "1970-01-01T00:00:00Z", "unit": "MINUTE", "value": 10, "remaining": 10, @@ -343,7 +343,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite): "limit": [ { "verb": "GET", - "next-available": "1969-12-31T19:00:00Z", + "next-available": "1970-01-01T00:00:00Z", "unit": "MINUTE", "value": 10, "remaining": 10, @@ -954,7 +954,7 @@ class LimitsViewBuilderV11Test(test.TestCase): "verb": "POST", "remaining": 2, "unit": "MINUTE", - "next-available": "2011-07-21T14:17:06Z" + "next-available": "2011-07-21T18:17:06Z" }, ] }, @@ -967,7 +967,7 @@ class LimitsViewBuilderV11Test(test.TestCase): "verb": "POST", "remaining": 10, "unit": "DAY", - "next-available": "2011-07-21T14:17:06Z" + "next-available": "2011-07-21T18:17:06Z" }, ] }, -- cgit From fe708ae810efd17da70659076faa9c2f3b544d3b Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 17:13:12 -0400 Subject: pep8 --- nova/api/openstack/views/limits.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index c7163e33c..f603d7cb4 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -115,7 +115,8 @@ class ViewBuilderV11(ViewBuilder): return limits def _build_rate_limit(self, rate_limit): - next_avail = datetime.datetime.utcfromtimestamp(rate_limit["resetTime"]) + next_avail = \ + datetime.datetime.utcfromtimestamp(rate_limit["resetTime"]) return { "verb": rate_limit["verb"], "value": rate_limit["value"], -- cgit From f087db34b840a74f9ba093128808230fd97ea9ec Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Thu, 21 Jul 2011 16:20:32 -0500 Subject: pep8 --- nova/tests/api/openstack/test_versions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 0eae18a62..da964ee1f 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -174,7 +174,7 @@ class VersionsTest(test.TestCase): { "rel": "self", "href": "http://test/2.7.1", - } + }, ], }, ] @@ -204,7 +204,7 @@ class VersionsTest(test.TestCase): { "rel": "self", "href": "http://test/2.9.8", - } + }, ], }, ] -- cgit From 16aa079b933a8788c926745e0794d85558d442a8 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 21 Jul 2011 22:35:26 -0400 Subject: Added xml serialization for GET => /extensions. Added corresponding tests. --- nova/api/openstack/extensions.py | 43 +++++++++-------- nova/tests/api/openstack/test_extensions.py | 72 ++++++++++++++++++++--------- 2 files changed, 73 insertions(+), 42 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index e500e51fa..ad03a6ac9 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -470,28 +470,31 @@ class ResourceExtension(object): class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): - def _add_extension_attributes(self, node, extension): - node.setAttribute('name', extension['name']) - node.setAttribute('namespace', extension['namespace']) - node.setAttribute('alias', extension['alias']) - node.setAttribute('updated', extension['updated']) - - def show(self, ext_dict): - root = ElementTree.Element('extension'); - extension = ext_dict['extension'] - root.set('xmlns', wsgi.XMLNS_V11) - root.set('xmlns:atom', wsgi.XMLNS_ATOM) - root.set('name', extension['name']) - root.set('namespace', extension['namespace']) - root.set('alias', extension['alias']) - root.set('updated', extension['updated']) + def _create_ext_elem(self, ext_dict): + ext_elem = ElementTree.Element('extension'); + ext_elem.set('xmlns', wsgi.XMLNS_V11) + ext_elem.set('xmlns:atom', wsgi.XMLNS_ATOM) + ext_elem.set('name', ext_dict['name']) + ext_elem.set('namespace', ext_dict['namespace']) + ext_elem.set('alias', ext_dict['alias']) + ext_elem.set('updated', ext_dict['updated']) desc = ElementTree.Element('description'); - desc.text = extension['description'] - root.append(desc) - for link in extension.get('links', []): + desc.text = ext_dict['description'] + ext_elem.append(desc) + for link in ext_dict.get('links', []): elem = ElementTree.Element('atom:link'); elem.set('rel', link['rel']) elem.set('href', link['href']) elem.set('type', link['type']) - root.append(elem) - return ElementTree.tostring(root) + ext_elem.append(elem) + return ext_elem + + def show(self, ext_dict): + ext = self._create_ext_elem(ext_dict['extension']) + return ElementTree.tostring(ext, encoding='UTF-8') + + def index(self, exts_dict): + exts = ElementTree.Element('extensions'); + for ext_dict in exts_dict['extensions']: + exts.append(self._create_ext_elem(ext_dict)) + return ElementTree.tostring(exts, encoding='UTF-8') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 6d3c1b1be..c651c030a 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -103,12 +103,12 @@ class ExtensionControllerTest(unittest.TestCase): (fox_ext,) = [ x for x in data['extensions'] if x['alias'] == 'FOXNSOX'] self.assertEqual(fox_ext, { - "namespace" : "http://www.fox.in.socks/api/ext/pie/v1.0", - "name" : "Fox In Socks", - "updated" : "2011-01-22T13:25:27-06:00", - "description" : "The Fox In Socks Extension", - "alias" : "FOXNSOX", - "links" : [] + 'namespace' : 'http://www.fox.in.socks/api/ext/pie/v1.0', + 'name' : 'Fox In Socks', + 'updated' : '2011-01-22T13:25:27-06:00', + 'description' : 'The Fox In Socks Extension', + 'alias' : 'FOXNSOX', + 'links' : [] } ) @@ -130,6 +130,32 @@ class ExtensionControllerTest(unittest.TestCase): } ) + def test_list_extensions_xml(self): + app = openstack.APIRouterV11() + ext_midware = extensions.ExtensionMiddleware(app) + request = webob.Request.blank("/extensions") + request.accept = "application/xml" + response = request.get_response(ext_midware) + self.assertEqual(200, response.status_int) + print response.body + + ns = "{http://docs.openstack.org/compute/api/v1.1}" + root = ElementTree.XML(response.body) + self.assertEqual(root.tag, 'extensions') + + # Make sure we have all the extensions. + exts = root.findall('{0}extension'.format(ns)) + self.assertEqual(len(exts), 6) + + # Make sure that at least Fox in Sox is correct. + (fox_ext,) = [ x for x in exts if x.get('alias') == 'FOXNSOX' ] + self.assertEqual(fox_ext.get('name'), 'Fox In Socks') + self.assertEqual(fox_ext.get('namespace'), + 'http://www.fox.in.socks/api/ext/pie/v1.0') + self.assertEqual(fox_ext.get('updated'), '2011-01-22T13:25:27-06:00') + self.assertEqual(fox_ext.findtext('{0}description'.format(ns)), + 'The Fox In Socks Extension') + def test_get_extension_xml(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) @@ -139,14 +165,15 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(200, response.status_int) print response.body - elem = ElementTree.XML(response.body.replace(" ", "")) - self.assertEqual(elem.get('alias'), 'FOXNSOX') - self.assertEqual(elem.get('name'), 'Fox In Socks') - self.assertEqual(elem.get('namespace'), - 'http://www.fox.in.socks/api/ext/pie/v1.0') - self.assertEqual(elem.get('updated'), '2011-01-22T13:25:27-06:00') + root = ElementTree.XML(response.body) ns = "{http://docs.openstack.org/compute/api/v1.1}" - self.assertEqual(elem.findtext('{0}description'.format(ns)), + self.assertEqual(root.tag.split('extension')[0], ns) + self.assertEqual(root.get('alias'), 'FOXNSOX') + self.assertEqual(root.get('name'), 'Fox In Socks') + self.assertEqual(root.get('namespace'), + 'http://www.fox.in.socks/api/ext/pie/v1.0') + self.assertEqual(root.get('updated'), '2011-01-22T13:25:27-06:00') + self.assertEqual(root.findtext('{0}description'.format(ns)), 'The Fox In Socks Extension') @@ -247,7 +274,7 @@ class ActionExtensionTest(unittest.TestCase): def test_invalid_action(self): body = dict(blah=dict(name="test")) - response = self._send_server_action_request("/asdf/1/action", body) + response = self._send_server_action_request("/fdsa/1/action", body) self.assertEqual(404, response.status_int) @@ -327,17 +354,18 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): ns = "{http://docs.openstack.org/compute/api/v1.1}" atomns = "{http://www.w3.org/2005/Atom}" - elem = ElementTree.XML(serializer.serialize(data, 'show')) - self.assertEqual(elem.tag.split('extension')[0], ns) - self.assertEqual(elem.get('alias'), 'RS-PIE') - self.assertEqual(elem.get('name'), 'ext1') - self.assertEqual(elem.get('namespace'), + xml = serializer.serialize(data, 'show') + root = ElementTree.XML(xml) + self.assertEqual(root.tag.split('extension')[0], ns) + self.assertEqual(root.get('alias'), 'RS-PIE') + self.assertEqual(root.get('name'), 'ext1') + self.assertEqual(root.get('namespace'), 'http://docs.rack.com/servers/api/ext/pie/v1.0') - self.assertEqual(elem.get('updated'), '2011-01-22T13:25:27-06:00') - self.assertEqual(elem.findtext('{0}description'.format(ns)), + self.assertEqual(root.get('updated'), '2011-01-22T13:25:27-06:00') + self.assertEqual(root.findtext('{0}description'.format(ns)), 'Adds the capability to share an image.') - link_nodes = elem.findall('{0}link'.format(atomns)) + link_nodes = root.findall('{0}link'.format(atomns)) self.assertEqual(len(link_nodes), 2) link_nodes.sort(key=lambda x: x.get('type')) -- cgit From c83546c0c9d077fb69caf1d782e8cb420e399a20 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 21 Jul 2011 23:36:40 -0400 Subject: Correctly add xml namespaces to extensions xml. --- nova/api/openstack/extensions.py | 33 +++++++++++++++++------------ nova/api/openstack/wsgi.py | 1 - nova/tests/api/openstack/test_extensions.py | 2 +- 3 files changed, 20 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index ad03a6ac9..cc889703e 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -470,31 +470,36 @@ class ResourceExtension(object): class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): + def show(self, ext_dict): + ext = self._create_ext_elem(ext_dict['extension']) + return self._to_xml(ext) + + def index(self, exts_dict): + exts = ElementTree.Element('extensions') + for ext_dict in exts_dict['extensions']: + exts.append(self._create_ext_elem(ext_dict)) + return self._to_xml(exts) + def _create_ext_elem(self, ext_dict): - ext_elem = ElementTree.Element('extension'); - ext_elem.set('xmlns', wsgi.XMLNS_V11) - ext_elem.set('xmlns:atom', wsgi.XMLNS_ATOM) + """Create an extension xml element from a dict.""" + ext_elem = ElementTree.Element('extension') ext_elem.set('name', ext_dict['name']) ext_elem.set('namespace', ext_dict['namespace']) ext_elem.set('alias', ext_dict['alias']) ext_elem.set('updated', ext_dict['updated']) - desc = ElementTree.Element('description'); + desc = ElementTree.Element('description') desc.text = ext_dict['description'] ext_elem.append(desc) for link in ext_dict.get('links', []): - elem = ElementTree.Element('atom:link'); + elem = ElementTree.Element('atom:link') elem.set('rel', link['rel']) elem.set('href', link['href']) elem.set('type', link['type']) ext_elem.append(elem) return ext_elem - def show(self, ext_dict): - ext = self._create_ext_elem(ext_dict['extension']) - return ElementTree.tostring(ext, encoding='UTF-8') - - def index(self, exts_dict): - exts = ElementTree.Element('extensions'); - for ext_dict in exts_dict['extensions']: - exts.append(self._create_ext_elem(ext_dict)) - return ElementTree.tostring(exts, encoding='UTF-8') + def _to_xml(self, root): + """Convert the xml tree object to an xml string.""" + root.set('xmlns', wsgi.XMLNS_V11) + root.set('xmlns:atom', wsgi.XMLNS_ATOM) + return ElementTree.tostring(root, encoding='UTF-8') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index d9993282f..ee1572c4f 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -355,7 +355,6 @@ class XMLDictSerializer(DictSerializer): link_node.setAttribute('href', link['href']) if link.get('type'): link_node.setAttribute('type', link['type']) - link_nodes.append(link_node) return link_nodes diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index c651c030a..55f16e9d4 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -141,7 +141,7 @@ class ExtensionControllerTest(unittest.TestCase): ns = "{http://docs.openstack.org/compute/api/v1.1}" root = ElementTree.XML(response.body) - self.assertEqual(root.tag, 'extensions') + self.assertEqual(root.tag.split('extensions')[0], ns) # Make sure we have all the extensions. exts = root.findall('{0}extension'.format(ns)) -- cgit From 7066537c32da8acaff0266634383efaccdfc6e9c Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Fri, 22 Jul 2011 14:17:37 -0400 Subject: pep8 fixes --- nova/tests/api/openstack/test_extensions.py | 57 +++++++++++++++-------------- 1 file changed, 29 insertions(+), 28 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 55f16e9d4..f3c9d9423 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -103,12 +103,12 @@ class ExtensionControllerTest(unittest.TestCase): (fox_ext,) = [ x for x in data['extensions'] if x['alias'] == 'FOXNSOX'] self.assertEqual(fox_ext, { - 'namespace' : 'http://www.fox.in.socks/api/ext/pie/v1.0', - 'name' : 'Fox In Socks', - 'updated' : '2011-01-22T13:25:27-06:00', - 'description' : 'The Fox In Socks Extension', - 'alias' : 'FOXNSOX', - 'links' : [] + 'namespace': 'http://www.fox.in.socks/api/ext/pie/v1.0', + 'name': 'Fox In Socks', + 'updated': '2011-01-22T13:25:27-06:00', + 'description': 'The Fox In Socks Extension', + 'alias': 'FOXNSOX', + 'links': [] } ) @@ -121,12 +121,12 @@ class ExtensionControllerTest(unittest.TestCase): data = json.loads(response.body) self.assertEqual(data['extension'], { - "namespace" : "http://www.fox.in.socks/api/ext/pie/v1.0", - "name" : "Fox In Socks", - "updated" : "2011-01-22T13:25:27-06:00", - "description" : "The Fox In Socks Extension", - "alias" : "FOXNSOX", - "links" : [] + "namespace": "http://www.fox.in.socks/api/ext/pie/v1.0", + "name": "Fox In Socks", + "updated": "2011-01-22T13:25:27-06:00", + "description": "The Fox In Socks Extension", + "alias": "FOXNSOX", + "links": [] } ) @@ -148,7 +148,7 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(len(exts), 6) # Make sure that at least Fox in Sox is correct. - (fox_ext,) = [ x for x in exts if x.get('alias') == 'FOXNSOX' ] + (fox_ext,) = [x for x in exts if x.get('alias') == 'FOXNSOX'] self.assertEqual(fox_ext.get('name'), 'Fox In Socks') self.assertEqual(fox_ext.get('namespace'), 'http://www.fox.in.socks/api/ext/pie/v1.0') @@ -327,29 +327,30 @@ class RequestExtensionTest(unittest.TestCase): self.assertEqual('newblue', response_data['flavor']['googoose']) self.assertEqual("Pig Bands!", response_data['big_bands']) + class ExtensionsXMLSerializerTest(unittest.TestCase): def test_serialize(self): serializer = extensions.ExtensionsXMLSerializer() data = { 'extension': { 'name': 'ext1', - "namespace" : "http://docs.rack.com/servers/api/ext/pie/v1.0", - "alias" : "RS-PIE", - "updated" : "2011-01-22T13:25:27-06:00", - "description" : "Adds the capability to share an image.", - "links" : [ + 'namespace': 'http://docs.rack.com/servers/api/ext/pie/v1.0', + 'alias': 'RS-PIE', + 'updated': '2011-01-22T13:25:27-06:00', + 'description': 'Adds the capability to share an image.', + 'links': [ { - "rel" : "describedby", - "type" : "application/pdf", - "href" : "http://docs.rack.com/servers/api/ext/cs.pdf" + 'rel': 'describedby', + 'type': 'application/pdf', + 'href': 'http://docs.rack.com/servers/api/ext/cs.pdf' }, { - "rel" : "describedby", - "type" : "application/vnd.sun.wadl+xml", - "href" : "http://docs.rack.com/servers/api/ext/cs.wadl" + 'rel': 'describedby', + 'type': 'application/vnd.sun.wadl+xml', + 'href': 'http://docs.rack.com/servers/api/ext/cs.wadl' } ] - }, + } } ns = "{http://docs.openstack.org/compute/api/v1.1}" @@ -371,11 +372,11 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): self.assertEqual(link_nodes[0].get('type'), 'application/pdf') self.assertEqual(link_nodes[0].get('rel'), 'describedby') - self.assertEqual(link_nodes[0].get('href'), + self.assertEqual(link_nodes[0].get('href'), 'http://docs.rack.com/servers/api/ext/cs.pdf') - self.assertEqual(link_nodes[1].get('type'), + self.assertEqual(link_nodes[1].get('type'), 'application/vnd.sun.wadl+xml') self.assertEqual(link_nodes[1].get('rel'), 'describedby') - self.assertEqual(link_nodes[1].get('href'), + self.assertEqual(link_nodes[1].get('href'), 'http://docs.rack.com/servers/api/ext/cs.wadl') -- cgit From 14ca0fd2db37699191432405585211c257a7433e Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 22 Jul 2011 15:47:33 -0500 Subject: fixed networks not defined error when creating instances when no networks exist --- nova/network/manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 75c3f668d..8ac66cdc3 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -370,8 +370,7 @@ class NetworkManager(manager.SchedulerDependentManager): try: networks = self.db.network_get_all(context) except exception.NoNetworksFound: - # we don't care if no networks are found - pass + return [] # return only networks which are not vlan networks return [network for network in networks if -- cgit From e54f0cb05a3e10ad4234c672e2096da2a3eaa2da Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Fri, 22 Jul 2011 16:47:37 -0500 Subject: default to None in the method signature --- nova/db/sqlalchemy/migrate_repo/versions/035_secondary_dns.py | 2 ++ nova/db/sqlalchemy/models.py | 2 +- nova/network/manager.py | 10 +++++----- nova/tests/test_network.py | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/035_secondary_dns.py b/nova/db/sqlalchemy/migrate_repo/versions/035_secondary_dns.py index d9f82d796..c938eb716 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/035_secondary_dns.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/035_secondary_dns.py @@ -26,6 +26,7 @@ def upgrade(migrate_engine): meta.bind = migrate_engine networks = Table('networks', meta, autoload=True) + networks.c.dns.alter(Column('dns1', String(255))) networks.create_column(dns2) @@ -33,4 +34,5 @@ def downgrade(migrate_engine): meta.bind = migrate_engine networks = Table('networks', meta, autoload=True) + networks.c.dns1.alter(Column('dns', String(255))) networks.drop_column(dns2) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index cf00177c4..7e35c2cba 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -556,7 +556,7 @@ class Network(BASE, NovaBase): bridge_interface = Column(String(255)) gateway = Column(String(255)) broadcast = Column(String(255)) - dns = Column(String(255)) + dns1 = Column(String(255)) dns2 = Column(String(255)) vlan = Column(Integer) diff --git a/nova/network/manager.py b/nova/network/manager.py index a11d9a5ed..3b3f524c4 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -480,8 +480,8 @@ class NetworkManager(manager.SchedulerDependentManager): # TODO(tr3buchet): handle ip6 routes here as well if network['gateway_v6']: info['gateway6'] = network['gateway_v6'] - if network['dns']: - info['dns'].append(network['dns']) + if network['dns1']: + info['dns'].append(network['dns1']) if network['dns2']: info['dns'].append(network['dns2']) @@ -595,7 +595,7 @@ class NetworkManager(manager.SchedulerDependentManager): def create_networks(self, context, label, cidr, multi_host, num_networks, network_size, cidr_v6, gateway_v6, bridge, - bridge_interface, dns, dns2=None, **kwargs): + bridge_interface, dns1=None, dns2=None, **kwargs): """Create networks based on parameters.""" fixed_net = netaddr.IPNetwork(cidr) fixed_net_v6 = netaddr.IPNetwork(cidr_v6) @@ -610,7 +610,7 @@ class NetworkManager(manager.SchedulerDependentManager): net = {} net['bridge'] = bridge net['bridge_interface'] = bridge_interface - net['dns'] = dns + net['dns1'] = dns1 net['dns2'] = dns2 net['cidr'] = cidr net['multi_host'] = multi_host @@ -640,7 +640,7 @@ class NetworkManager(manager.SchedulerDependentManager): if kwargs.get('vpn', False): # this bit here is for vlan-manager - del net['dns'] + del net['dns1'] del net['dns2'] vlan = kwargs['vlan_start'] + index net['vpn_private_address'] = str(project_net[2]) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index c37e30c05..fbe7d769e 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -55,7 +55,7 @@ networks = [{'id': 0, 'bridge_interface': 'fake_fa0', 'gateway': '192.168.0.1', 'broadcast': '192.168.0.255', - 'dns': '192.168.0.1', + 'dns1': '192.168.0.1', 'dns2': '192.168.0.2', 'vlan': None, 'host': None, @@ -74,7 +74,7 @@ networks = [{'id': 0, 'bridge_interface': 'fake_fa1', 'gateway': '192.168.1.1', 'broadcast': '192.168.1.255', - 'dns': '192.168.0.1', + 'dns1': '192.168.0.1', 'dns2': '192.168.0.2', 'vlan': None, 'host': None, -- cgit From e92595896531d56ce38169468a740b98624cb714 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Fri, 22 Jul 2011 17:01:35 -0500 Subject: pass in the right argument --- nova/tests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py index 49c1ccc56..720d5b0e6 100644 --- a/nova/tests/__init__.py +++ b/nova/tests/__init__.py @@ -68,7 +68,7 @@ def setup(): bridge_interface=bridge_interface, vpn_start=FLAGS.vpn_start, vlan_start=FLAGS.vlan_start, - dns=FLAGS.flat_network_dns) + dns1=FLAGS.flat_network_dns) for net in db.network_get_all(ctxt): network.set_network_host(ctxt, net) -- cgit From fc00b24ddd4d2eceb68199b059fb895604ce361d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 22:39:08 +0000 Subject: make sure dhcp_server is available in vlan mode --- nova/network/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 75c3f668d..3c1d58af3 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -893,7 +893,6 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): def _setup_network(self, context, network_ref): """Sets up network on this host.""" - network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) if not network_ref['vpn_public_address']: net = {} address = FLAGS.vpn_ip @@ -901,6 +900,7 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): network_ref = db.network_update(context, network_ref['id'], net) else: address = network_ref['vpn_public_address'] + network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) self.driver.ensure_vlan_bridge(network_ref['vlan'], network_ref['bridge'], network_ref['bridge_interface'], -- cgit From 0206da24a6d9175b9dd50000fb9d8435355696bd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 23 Jul 2011 00:00:04 +0000 Subject: update netutils in libvirt to match the 2 dns setup --- nova/virt/libvirt/netutils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt/netutils.py b/nova/virt/libvirt/netutils.py index 8d36c0a05..041eacb2d 100644 --- a/nova/virt/libvirt/netutils.py +++ b/nova/virt/libvirt/netutils.py @@ -91,9 +91,14 @@ def get_network_info(instance): 'broadcast': network['broadcast'], 'mac': vif['address'], 'rxtx_cap': flavor['rxtx_cap'], - 'dns': [network['dns']], + 'dns': [], 'ips': [ip_dict(ip) for ip in network_ips]} + if network['dns1']: + mapping['dns'].append(network['dns1']) + if network['dns2']: + mapping['dns'].append(network['dns2']) + if FLAGS.use_ipv6: mapping['ip6s'] = [ip6_dict()] mapping['gateway6'] = network['gateway_v6'] -- cgit From a7fdcb0f7dacfe41b5c85faee38c027b31ddaf81 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Sat, 23 Jul 2011 16:13:48 -0400 Subject: Added test_serialize_extenstions to test ExtensionsXMLSerializer.index(). --- nova/tests/api/openstack/test_extensions.py | 115 ++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 32 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index f3c9d9423..47ec5ec89 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -31,7 +31,8 @@ from nova.api.openstack import wsgi from nova.tests.api.openstack import fakes FLAGS = flags.FLAGS - +NS = "{http://docs.openstack.org/compute/api/v1.1}" +ATOMNS = "{http://www.w3.org/2005/Atom}" response_body = "Try to say this Mr. Knox, sir..." @@ -139,12 +140,11 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(200, response.status_int) print response.body - ns = "{http://docs.openstack.org/compute/api/v1.1}" root = ElementTree.XML(response.body) - self.assertEqual(root.tag.split('extensions')[0], ns) + self.assertEqual(root.tag.split('extensions')[0], NS) # Make sure we have all the extensions. - exts = root.findall('{0}extension'.format(ns)) + exts = root.findall('{0}extension'.format(NS)) self.assertEqual(len(exts), 6) # Make sure that at least Fox in Sox is correct. @@ -153,7 +153,7 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(fox_ext.get('namespace'), 'http://www.fox.in.socks/api/ext/pie/v1.0') self.assertEqual(fox_ext.get('updated'), '2011-01-22T13:25:27-06:00') - self.assertEqual(fox_ext.findtext('{0}description'.format(ns)), + self.assertEqual(fox_ext.findtext('{0}description'.format(NS)), 'The Fox In Socks Extension') def test_get_extension_xml(self): @@ -166,14 +166,13 @@ class ExtensionControllerTest(unittest.TestCase): print response.body root = ElementTree.XML(response.body) - ns = "{http://docs.openstack.org/compute/api/v1.1}" - self.assertEqual(root.tag.split('extension')[0], ns) + self.assertEqual(root.tag.split('extension')[0], NS) self.assertEqual(root.get('alias'), 'FOXNSOX') self.assertEqual(root.get('name'), 'Fox In Socks') self.assertEqual(root.get('namespace'), 'http://www.fox.in.socks/api/ext/pie/v1.0') self.assertEqual(root.get('updated'), '2011-01-22T13:25:27-06:00') - self.assertEqual(root.findtext('{0}description'.format(ns)), + self.assertEqual(root.findtext('{0}description'.format(NS)), 'The Fox In Socks Extension') @@ -329,7 +328,8 @@ class RequestExtensionTest(unittest.TestCase): class ExtensionsXMLSerializerTest(unittest.TestCase): - def test_serialize(self): + + def test_serialize_extenstion(self): serializer = extensions.ExtensionsXMLSerializer() data = { 'extension': { @@ -353,30 +353,81 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): } } - ns = "{http://docs.openstack.org/compute/api/v1.1}" - atomns = "{http://www.w3.org/2005/Atom}" xml = serializer.serialize(data, 'show') root = ElementTree.XML(xml) - self.assertEqual(root.tag.split('extension')[0], ns) - self.assertEqual(root.get('alias'), 'RS-PIE') - self.assertEqual(root.get('name'), 'ext1') - self.assertEqual(root.get('namespace'), - 'http://docs.rack.com/servers/api/ext/pie/v1.0') - self.assertEqual(root.get('updated'), '2011-01-22T13:25:27-06:00') - self.assertEqual(root.findtext('{0}description'.format(ns)), - 'Adds the capability to share an image.') + ext_dict = data['extension'] + self.assertEqual(root.findtext('{0}description'.format(NS)), + ext_dict['description']) + + for key in ['name', 'namespace', 'alias', 'updated']: + self.assertEqual(root.get(key), ext_dict[key]) - link_nodes = root.findall('{0}link'.format(atomns)) + link_nodes = root.findall('{0}link'.format(ATOMNS)) self.assertEqual(len(link_nodes), 2) - link_nodes.sort(key=lambda x: x.get('type')) - - self.assertEqual(link_nodes[0].get('type'), 'application/pdf') - self.assertEqual(link_nodes[0].get('rel'), 'describedby') - self.assertEqual(link_nodes[0].get('href'), - 'http://docs.rack.com/servers/api/ext/cs.pdf') - - self.assertEqual(link_nodes[1].get('type'), - 'application/vnd.sun.wadl+xml') - self.assertEqual(link_nodes[1].get('rel'), 'describedby') - self.assertEqual(link_nodes[1].get('href'), - 'http://docs.rack.com/servers/api/ext/cs.wadl') + for i, link in enumerate(ext_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + def test_serialize_extenstions(self): + serializer = extensions.ExtensionsXMLSerializer() + data = { + "extensions": [ + { + "name": "Public Image Extension", + "namespace": "http://foo.com/servers/api/ext/pie/v1.0", + "alias": "RS-PIE", + "updated": "2011-01-22T13:25:27-06:00", + "description": "Adds the capability to share an image.", + "links": [ + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://foo.com/servers/api/ext/cs-pie.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://foo.com/servers/api/ext/cs-pie.wadl" + } + ] + }, + { + "name": "Cloud Block Storage", + "namespace": "http://foo.com/servers/api/ext/cbs/v1.0", + "alias": "RS-CBS", + "updated": "2011-01-12T11:22:33-06:00", + "description": "Allows mounting cloud block storage.", + "links": [ + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://foo.com/servers/api/ext/cs-cbs.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://foo.com/servers/api/ext/cs-cbs.wadl" + } + ] + } + ] + } + + xml = serializer.serialize(data, 'index') + print xml + root = ElementTree.XML(xml) + ext_elems = root.findall('{0}extension'.format(NS)) + self.assertEqual(len(ext_elems), 2) + for i, ext_elem in enumerate(ext_elems): + ext_dict = data['extensions'][i] + self.assertEqual(ext_elem.findtext('{0}description'.format(NS)), + ext_dict['description']) + + for key in ['name', 'namespace', 'alias', 'updated']: + self.assertEqual(ext_elem.get(key), ext_dict[key]) + + link_nodes = ext_elem.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(ext_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) -- cgit From 2df7470b89289c1a2cae4db8c21b5588622baf3c Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Mon, 25 Jul 2011 12:43:10 -0400 Subject: pep8 + spelling fixes --- nova/tests/api/openstack/test_extensions.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 47ec5ec89..d459c694f 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -368,13 +368,13 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): for key, value in link.items(): self.assertEqual(link_nodes[i].get(key), value) - def test_serialize_extenstions(self): + def test_serialize_extensions(self): serializer = extensions.ExtensionsXMLSerializer() data = { "extensions": [ { "name": "Public Image Extension", - "namespace": "http://foo.com/servers/api/ext/pie/v1.0", + "namespace": "http://foo.com/api/ext/pie/v1.0", "alias": "RS-PIE", "updated": "2011-01-22T13:25:27-06:00", "description": "Adds the capability to share an image.", @@ -382,18 +382,18 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): { "rel": "describedby", "type": "application/pdf", - "href": "http://foo.com/servers/api/ext/cs-pie.pdf" + "href": "http://foo.com/api/ext/cs-pie.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", - "href": "http://foo.com/servers/api/ext/cs-pie.wadl" + "href": "http://foo.com/api/ext/cs-pie.wadl" } ] }, { "name": "Cloud Block Storage", - "namespace": "http://foo.com/servers/api/ext/cbs/v1.0", + "namespace": "http://foo.com/api/ext/cbs/v1.0", "alias": "RS-CBS", "updated": "2011-01-12T11:22:33-06:00", "description": "Allows mounting cloud block storage.", @@ -401,12 +401,12 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): { "rel": "describedby", "type": "application/pdf", - "href": "http://foo.com/servers/api/ext/cs-cbs.pdf" + "href": "http://foo.com/api/ext/cs-cbs.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", - "href": "http://foo.com/servers/api/ext/cs-cbs.wadl" + "href": "http://foo.com/api/ext/cs-cbs.wadl" } ] } -- cgit