diff options
32 files changed, 413 insertions, 217 deletions
@@ -1,3 +1,4 @@ +Adam Gandelman <adamg@canonical.com> Alex Meade <alex.meade@rackspace.com> Alexander Sakhnov <asakhnov@mirantis.com> Andrey Brindeyev <abrindeyev@griddynamics.com> diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge index 6d9d85896..325642d52 100755 --- a/bin/nova-dhcpbridge +++ b/bin/nova-dhcpbridge @@ -91,7 +91,7 @@ def init_leases(interface): """Get the list of hosts for an interface.""" ctxt = context.get_admin_context() network_ref = db.network_get_by_bridge(ctxt, interface) - return linux_net.get_dhcp_leases(ctxt, network_ref['id']) + return linux_net.get_dhcp_leases(ctxt, network_ref) def main(): diff --git a/bin/nova-manage b/bin/nova-manage index 2446309e8..b6029bb52 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -479,7 +479,7 @@ class ProjectCommands(object): except db.api.NoMoreNetworks: print _('No more networks available. If this is a new ' 'installation, you need\nto call something like this:\n\n' - ' nova-manage network create 10.0.0.0/8 10 64\n\n') + ' nova-manage network create pvt 10.0.0.0/8 10 64\n\n') except exception.ProcessExecutionError, e: print e print _("The above error may show that the certificate db has not " @@ -500,7 +500,7 @@ class FixedIpCommands(object): if host is None: fixed_ips = db.fixed_ip_get_all(ctxt) else: - fixed_ips = db.fixed_ip_get_all_by_host(ctxt, host) + fixed_ips = db.fixed_ip_get_all_by_instance_host(ctxt, host) except exception.NotFound as ex: print "error: %s" % ex sys.exit(2) @@ -564,17 +564,17 @@ class NetworkCommands(object): """Class for managing networks.""" def create(self, label=None, fixed_range=None, num_networks=None, - network_size=None, vlan_start=None, + network_size=None, multi_host=None, vlan_start=None, vpn_start=None, fixed_range_v6=None, gateway_v6=None, flat_network_bridge=None, bridge_interface=None): """Creates fixed ips for host by range arguments: label, fixed_range, [num_networks=FLAG], - [network_size=FLAG], [vlan_start=FLAG], + [network_size=FLAG], [multi_host=FLAG], [vlan_start=FLAG], [vpn_start=FLAG], [fixed_range_v6=FLAG], [gateway_v6=FLAG], [flat_network_bridge=FLAG], [bridge_interface=FLAG] - If you wish to use a later argument fill in the gaps with 0s - Ex: network create private 10.0.0.0/8 1 15 0 0 0 0 xenbr1 eth1 - network create private 10.0.0.0/8 1 15 + If you wish to use a later argument fill in the gaps with ""s + Ex: network create private 10.0.0.0/8 1 16 T "" "" "" "" xenbr1 eth1 + network create private 10.0.0.0/8 1 16 """ if not label: msg = _('a label (ex: public) is required to create networks.') @@ -589,6 +589,10 @@ class NetworkCommands(object): num_networks = FLAGS.num_networks if not network_size: network_size = FLAGS.network_size + if not multi_host: + multi_host = FLAGS.multi_host + else: + multi_host = multi_host == 'T' if not vlan_start: vlan_start = FLAGS.vlan_start if not vpn_start: @@ -607,6 +611,7 @@ class NetworkCommands(object): net_manager.create_networks(context.get_admin_context(), label=label, cidr=fixed_range, + multi_host=multi_host, num_networks=int(num_networks), network_size=int(network_size), vlan_start=int(vlan_start), 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/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index a242efa45..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.""" @@ -50,22 +53,37 @@ 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)) + + 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) 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/compute/manager.py b/nova/compute/manager.py index bfc7694e7..24b957abf 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') @@ -278,16 +276,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 cb4da169c..2efbf957d 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -332,13 +332,14 @@ 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. 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) def fixed_ip_create(context, values): @@ -361,9 +362,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 +377,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 23179151a..1037bd3db 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 @@ -672,7 +670,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, @@ -682,6 +680,7 @@ def fixed_ip_associate_pool(context, network_id, instance_id): 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, @@ -692,9 +691,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'] @@ -750,7 +752,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).\ @@ -800,6 +802,20 @@ def fixed_ip_get_by_instance(context, instance_id): @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).\ + first() + 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() rv = session.query(models.FixedIp).\ @@ -1484,8 +1500,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 @@ -1501,7 +1515,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() @@ -1708,9 +1721,16 @@ 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).\ - filter_by(host=host).\ + join(models.Network.fixed_ips).\ + filter(host_filter).\ + filter_by(deleted=False).\ all() @@ -1742,6 +1762,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/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..3a5f7eba8 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/033_ha_network.py @@ -0,0 +1,44 @@ +# 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() + +fixed_ips_host = Column('host', String(255)) + +networks_multi_host = Column('multi_host', Boolean, default=False) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + 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) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/033_change_instance_id_in_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/034_change_instance_id_in_migrations.py index b002ba064..b002ba064 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/033_change_instance_id_in_migrations.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/034_change_instance_id_in_migrations.py diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 7b1c026cc..c1150f7ca 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 @@ -546,6 +547,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)) @@ -578,6 +580,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): @@ -604,6 +618,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/exception.py b/nova/exception.py index eff322ea0..38e705417 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -408,6 +408,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 FixedIpNotFoundForSpecificInstance(FixedIpNotFound): message = _("Instance %(instance_id)s doesn't have fixed ip '%(ip)s'.") diff --git a/nova/network/api.py b/nova/network/api.py index f03081be4..c2360f0d0 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 @@ -108,7 +107,11 @@ class API(base.Base): '(%(project)s)') % {'address': floating_ip['address'], 'project': context.project_id}) - host = fixed_ip['network']['host'] + # 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'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.network_topic, host), {'method': 'associate_floating_ip', @@ -123,7 +126,11 @@ 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'] + # 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'] rpc.call(context, self.db.queue_get_for(context, FLAGS.network_topic, host), {'method': 'disassociate_floating_ip', @@ -137,7 +144,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}) @@ -176,7 +185,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 283a5aca1..a8ce1c16a 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', @@ -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) @@ -573,18 +579,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)) # Make sure dnsmasq can actually read it (it setuid()s to "nobody") os.chmod(conffile, 0644) @@ -612,9 +616,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 +652,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): @@ -701,10 +700,11 @@ 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'), - '--listen-address=%s' % net['gateway'], + '--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 24736f53d..34001657b 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 @@ -49,7 +48,6 @@ import datetime import math import netaddr import socket -import pickle from eventlet import greenpool from nova import context @@ -78,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') @@ -87,6 +83,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', @@ -104,7 +102,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(), @@ -124,16 +123,26 @@ 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, **kwargs): + def _allocate_fixed_ips(self, context, instance_id, host, networks, + **kwargs): """Calls allocate_fixed_ip once for each network.""" green_pool = greenpool.GreenPool() vpn = kwargs.pop('vpn') for network in networks: - if network['host'] != self.host: + # 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, - network['host']) + topic = self.db.queue_get_for(context, + FLAGS.network_topic, + host) args = {} args['instance_id'] = instance_id args['network_id'] = network['id'] @@ -149,12 +158,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): @@ -193,7 +203,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 @@ -300,15 +310,36 @@ class NetworkManager(manager.SchedulerDependentManager): super(NetworkManager, self).__init__(service_name='network', *args, **kwargs) + @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'] + try: + fip = self.db.fixed_ip_get_by_network_host(context, + network_id, + host) + return fip['address'] + except exception.FixedIpNotFoundForNetworkHost: + elevated = context.elevated() + return self.db.fixed_ip_associate_pool(elevated, + network_id, + host=host) + 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']) + self._setup_network(ctxt, network) def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval.""" @@ -323,33 +354,14 @@ 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.""" - try: - networks = self.db.network_get_all(context) - except exception.NoNetworksFound: - # we don't care if no networks are found - pass - - 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']) - 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 @@ -361,9 +373,9 @@ class NetworkManager(manager.SchedulerDependentManager): # we don't care if no networks are found pass - # return only networks which are not vlan networks and have host set + # return only networks which are not vlan networks return [network for network in networks if - not network['vlan'] and network['host']] + not network['vlan']] def allocate_for_instance(self, context, **kwargs): """Handles allocating the various network resources for an instance. @@ -371,6 +383,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') vpn = kwargs.pop('vpn') @@ -379,9 +392,11 @@ 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, networks, vpn=vpn) - return self.get_instance_nw_info(context, instance_id, type_id) + self._allocate_fixed_ips(admin_context, instance_id, host, networks, + vpn=vpn) + 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. @@ -401,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): + 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 @@ -445,9 +461,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': dhcp_server, 'broadcast': network['broadcast'], 'mac': vif['address'], 'rxtx_cap': flavor['rxtx_cap'], @@ -487,10 +510,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 remove_fixed_ip_from_instance(self, context, instance_id, address): """Removes a fixed ip from an instance from specified network.""" @@ -517,6 +540,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): @@ -562,10 +586,10 @@ 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) + self._setup_network(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.""" @@ -584,6 +608,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) @@ -659,12 +684,13 @@ class NetworkManager(manager.SchedulerDependentManager): 'address': address, 'reserved': reserved}) - def _allocate_fixed_ips(self, context, instance_id, networks, **kwargs): + def _allocate_fixed_ips(self, context, instance_id, host, networks, + **kwargs): """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): @@ -706,7 +732,8 @@ class FlatManager(NetworkManager): timeout_fixed_ips = False - def _allocate_fixed_ips(self, context, instance_id, networks, **kwargs): + def _allocate_fixed_ips(self, context, instance_id, host, networks, + **kwargs): """Calls allocate_fixed_ip once for each network.""" for network in networks: self.allocate_fixed_ip(context, instance_id, network) @@ -724,12 +751,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): @@ -760,30 +787,23 @@ 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']) - - 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 _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) - self.driver.ensure_bridge(network['bridge'], - network['bridge_interface'], - network) + if not network['multi_host']: + self.driver.ensure_bridge(network['bridge'], + network['bridge_interface']) + + 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: - self.driver.update_dhcp(context, network_id) + self.driver.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_ref['id'], + {'gateway_v6': gateway}) class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): @@ -832,8 +852,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.driver.update_dhcp(context, network['id']) + self._setup_network(context, network) + return address def add_network_to_project(self, context, project_id): """Force adds another network to a project.""" @@ -845,17 +865,15 @@ 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.""" # 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 self.db.project_get_networks(context, project_id) def create_networks(self, context, **kwargs): """Create networks based on parameters.""" @@ -874,32 +892,35 @@ 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['id'], net) else: - address = network['vpn_public_address'] - 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: - self.driver.update_dhcp(context, network_id) + self.driver.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_ref['id'], + {'gateway_v6': gateway}) @property def _bottom_reserved_ips(self): diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py index e4ed75d37..7df6c06eb 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, @@ -68,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/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) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 1577c922b..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'}, @@ -447,6 +448,50 @@ 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) + 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}, + {'addr': '2001:4860::12', 'version': 6}, + ], + } + + 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) @@ -473,6 +518,7 @@ class ServersTest(test.TestCase): self.assertEqual(addresses, expected) def test_get_server_addresses_v1_1(self): + FLAGS.use_ipv6 = True interfaces = [ { 'network': {'label': 'network_1'}, @@ -492,6 +538,7 @@ class ServersTest(test.TestCase): }, {'address': '172.19.0.2'}, ], + 'fixed_ipv6': '2001:4860::12', }, ] @@ -514,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'}, ], }, } @@ -521,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'}, @@ -540,6 +589,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 +606,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) 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}]} diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index b09021e13..7cc174842 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -45,6 +45,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', @@ -62,6 +63,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', @@ -122,20 +124,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') @@ -149,7 +137,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) @@ -164,6 +152,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, diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 977bb7dfe..342dea98f 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) @@ -928,7 +931,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 +953,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, } @@ -1014,10 +1016,9 @@ 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': - xml_info['vncserver_host'] = FLAGS.vncserver_host - xml_info['vnc_keymap'] = FLAGS.vnc_keymap + 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: if instance['kernel_id']: xml_info['kernel'] = xml_info['basepath'] + "/kernel" diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c332c27b0..7995576a6 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -340,6 +340,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) @@ -349,9 +350,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 _handle_spawn_error(self, vdis, spawn_error): diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent index 288ccc78a..d609a88ab 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent @@ -72,7 +72,9 @@ def key_init(self, arg_dict): info to be passed, such as passwords. Returns the shared secret key value. """ - pub = int(arg_dict["pub"]) + # WARNING: Some older Windows agents will crash if the public key isn't + # a string + pub = arg_dict["pub"] arg_dict["value"] = json.dumps({"name": "keyinit", "value": pub}) request_id = arg_dict["id"] arg_dict["path"] = "data/host/%s" % request_id diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance index fbe080b22..fbe080b22 100644..100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration b/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration index ac1c50ad9..ac1c50ad9 100644..100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/objectstore b/plugins/xenserver/xenapi/etc/xapi.d/plugins/objectstore index d0313b4ed..d0313b4ed 100644..100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/objectstore +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/objectstore diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py index f51f5fce4..f51f5fce4 100755..100644 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost index 292bbce12..292bbce12 100644..100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost |