diff options
| author | William Wolf <throughnothing@gmail.com> | 2011-09-19 22:09:53 -0400 |
|---|---|---|
| committer | William Wolf <throughnothing@gmail.com> | 2011-09-19 22:09:53 -0400 |
| commit | f27ed550fae070c8cfeaf638b2a00a9b80f188c1 (patch) | |
| tree | 7b8afa180e5f2ada4c8bf35c340fcc043b185f98 | |
| parent | a0e705359353cb6a9b0c3fc8178e714e4350c585 (diff) | |
| parent | b90503021b5d55c51cf082fd168512bd4586383d (diff) | |
| download | nova-f27ed550fae070c8cfeaf638b2a00a9b80f188c1.tar.gz nova-f27ed550fae070c8cfeaf638b2a00a9b80f188c1.tar.xz nova-f27ed550fae070c8cfeaf638b2a00a9b80f188c1.zip | |
merged with trunk
28 files changed, 763 insertions, 395 deletions
diff --git a/bin/nova-manage b/bin/nova-manage index 089b2eeae..4e9307273 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -61,6 +61,7 @@ import math import netaddr from optparse import OptionParser import os +import StringIO import sys import time @@ -274,6 +275,58 @@ class ShellCommands(object): arguments: path""" exec(compile(open(path).read(), path, 'exec'), locals(), globals()) + @args('--filename', dest='filename', metavar='<path>', default=False, + help='Export file path') + def export(self, filename): + """Export Nova users into a file that can be consumed by Keystone""" + + def create_file(filename): + data = generate_data() + with open(filename, 'w') as f: + f.write(data.getvalue()) + + def tenants(data, am): + for project in am.get_projects(): + print >> data, ("tenant add '%s'" % + (project.name)) + for u in project.member_ids: + user = am.get_user(u) + print >> data, ("user add '%s' '%s' '%s'" % + (user.name, user.access, project.name)) + print >> data, ("credentials add 'EC2' '%s:%s' '%s' '%s'" % + (user.access, project.id, user.secret, project.id)) + + def roles(data, am): + for role in am.get_roles(): + print >> data, ("role add '%s'" % (role)) + + def grant_roles(data, am): + roles = am.get_roles() + for project in am.get_projects(): + for u in project.member_ids: + user = am.get_user(u) + for role in db.user_get_roles_for_project(ctxt, u, + project.id): + print >> data, ("role grant '%s', '%s', '%s')," % + (user.name, role, project.name)) + print >> data + + def generate_data(): + data = StringIO.StringIO() + am = manager.AuthManager() + tenants(data, am) + roles(data, am) + grant_roles(data, am) + data.seek(0) + return data + + ctxt = context.get_admin_context() + if filename: + create_file(filename) + else: + data = generate_data() + print data.getvalue() + class RoleCommands(object): """Class for managing roles.""" diff --git a/nova/api/openstack/contrib/virtual_interfaces.py b/nova/api/openstack/contrib/virtual_interfaces.py index dab61efc8..1981cd372 100644 --- a/nova/api/openstack/contrib/virtual_interfaces.py +++ b/nova/api/openstack/contrib/virtual_interfaces.py @@ -15,15 +15,10 @@ """The virtual interfaces extension.""" -from webob import exc -import webob - -from nova import compute -from nova import exception from nova import log as logging +from nova import network from nova.api.openstack import common from nova.api.openstack import extensions -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -50,19 +45,14 @@ class ServerVirtualInterfaceController(object): """ def __init__(self): - self.compute_api = compute.API() + self.network_api = network.API() super(ServerVirtualInterfaceController, self).__init__() def _items(self, req, server_id, entity_maker): """Returns a list of VIFs, transformed through entity_maker.""" context = req.environ['nova.context'] - try: - instance = self.compute_api.get(context, server_id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - vifs = instance['virtual_interfaces'] + vifs = self.network_api.get_vifs_by_instance(context, server_id) limited_list = common.limited(vifs, req) res = [entity_maker(context, vif) for vif in limited_list] return {'virtual_interfaces': res} diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index ac6b4918a..211925bd0 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -661,7 +661,11 @@ class ControllerV11(Controller): def _get_key_name(self, req, body): if 'server' in body: - return body['server'].get('key_name') + try: + return body['server'].get('key_name') + except AttributeError: + msg = _("Malformed server entity") + raise exc.HTTPBadRequest(explanation=msg) def _image_ref_from_req_data(self, data): try: diff --git a/nova/compute/api.py b/nova/compute/api.py index d8657d403..76e1e7a60 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -905,7 +905,7 @@ class API(base.Base): if 'reservation_id' in filters: recurse_zones = True - instances = self.db.instance_get_all_by_filters(context, filters) + instances = self._get_instances_by_filters(context, filters) if not recurse_zones: return instances @@ -930,6 +930,18 @@ class API(base.Base): return instances + def _get_instances_by_filters(self, context, filters): + ids = None + if 'ip6' in filters or 'ip' in filters: + res = self.network_api.get_instance_uuids_by_ip_filter(context, + filters) + # NOTE(jkoelker) It is possible that we will get the same + # instance uuid twice (one for ipv4 and ipv6) + uuids = set([r['instance_uuid'] for r in res]) + filters['uuid'] = uuids + + return self.db.instance_get_all_by_filters(context, filters) + def _cast_compute_message(self, method, context, instance_id, host=None, params=None): """Generic handler for RPC casts to compute. diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 46c643aee..cb5d10f83 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -79,6 +79,9 @@ 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_integer("resize_confirm_window", 0, + "Automatically confirm resizes after N seconds." + " Set to 0 to disable.") flags.DEFINE_integer('host_state_interval', 120, 'Interval in seconds for querying the host status') @@ -1648,14 +1651,23 @@ class ComputeManager(manager.SchedulerDependentManager): self.driver.poll_rescued_instances(FLAGS.rescue_timeout) except Exception as ex: LOG.warning(_("Error during poll_rescued_instances: %s"), - unicode(ex)) + unicode(ex)) + error_list.append(ex) + + try: + if FLAGS.resize_confirm_window > 0: + self.driver.poll_unconfirmed_resizes( + FLAGS.resize_confirm_window) + except Exception as ex: + LOG.warning(_("Error during poll_unconfirmed_resizes: %s"), + unicode(ex)) error_list.append(ex) try: self._report_driver_status() except Exception as ex: LOG.warning(_("Error during report_driver_status(): %s"), - unicode(ex)) + unicode(ex)) error_list.append(ex) try: diff --git a/nova/db/api.py b/nova/db/api.py index 05d81d8b2..8c4c78374 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -323,6 +323,11 @@ def migration_get_by_instance_and_status(context, instance_uuid, status): status) +def migration_get_all_unconfirmed(context, confirm_window): + """Finds all unconfirmed migrations within the confirmation window.""" + return IMPL.migration_get_all_unconfirmed(context, confirm_window) + + #################### @@ -462,6 +467,11 @@ def virtual_interface_delete_by_instance(context, instance_id): return IMPL.virtual_interface_delete_by_instance(context, instance_id) +def virtual_interface_get_all(context): + """Gets all virtual interfaces from the table""" + return IMPL.virtual_interface_get_all(context) + + #################### @@ -606,6 +616,11 @@ def instance_get_actions(context, instance_id): return IMPL.instance_get_actions(context, instance_id) +def instance_get_id_to_uuid_mapping(context, ids): + """Return a dictionary containing 'ID: UUID' given the ids""" + return IMPL.instance_get_id_to_uuid_mapping(context, ids) + + ################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 5889f5fc3..d8b5ef5c2 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -15,9 +15,10 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -""" -Implementation of SQLAlchemy backend. -""" + +"""Implementation of SQLAlchemy backend.""" + +import datetime import re import warnings @@ -930,7 +931,6 @@ def virtual_interface_get(context, vif_id, session=None): vif_ref = session.query(models.VirtualInterface).\ filter_by(id=vif_id).\ options(joinedload('network')).\ - options(joinedload('instance')).\ options(joinedload('fixed_ips')).\ first() return vif_ref @@ -946,7 +946,6 @@ def virtual_interface_get_by_address(context, address): vif_ref = session.query(models.VirtualInterface).\ filter_by(address=address).\ options(joinedload('network')).\ - options(joinedload('instance')).\ options(joinedload('fixed_ips')).\ first() return vif_ref @@ -962,7 +961,6 @@ def virtual_interface_get_by_uuid(context, vif_uuid): vif_ref = session.query(models.VirtualInterface).\ filter_by(uuid=vif_uuid).\ options(joinedload('network')).\ - options(joinedload('instance')).\ options(joinedload('fixed_ips')).\ first() return vif_ref @@ -978,7 +976,6 @@ def virtual_interface_get_by_fixed_ip(context, fixed_ip_id): vif_ref = session.query(models.VirtualInterface).\ filter_by(fixed_ip_id=fixed_ip_id).\ options(joinedload('network')).\ - options(joinedload('instance')).\ options(joinedload('fixed_ips')).\ first() return vif_ref @@ -995,7 +992,6 @@ def virtual_interface_get_by_instance(context, instance_id): vif_refs = session.query(models.VirtualInterface).\ filter_by(instance_id=instance_id).\ options(joinedload('network')).\ - options(joinedload('instance')).\ options(joinedload('fixed_ips')).\ all() return vif_refs @@ -1010,7 +1006,6 @@ def virtual_interface_get_by_instance_and_network(context, instance_id, filter_by(instance_id=instance_id).\ filter_by(network_id=network_id).\ options(joinedload('network')).\ - options(joinedload('instance')).\ options(joinedload('fixed_ips')).\ first() return vif_ref @@ -1026,7 +1021,6 @@ def virtual_interface_get_by_network(context, network_id): vif_refs = session.query(models.VirtualInterface).\ filter_by(network_id=network_id).\ options(joinedload('network')).\ - options(joinedload('instance')).\ options(joinedload('fixed_ips')).\ all() return vif_refs @@ -1056,6 +1050,17 @@ def virtual_interface_delete_by_instance(context, instance_id): virtual_interface_delete(context, vif_ref['id']) +@require_context +def virtual_interface_get_all(context): + """Get all vifs""" + session = get_session() + vif_refs = session.query(models.VirtualInterface).\ + options(joinedload('network')).\ + options(joinedload('fixed_ips')).\ + all() + return vif_refs + + ################### @@ -1172,7 +1177,6 @@ def _build_instance_get(context, session=None): partial = session.query(models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ options(joinedload_all('fixed_ips.network')).\ - options(joinedload('virtual_interfaces')).\ options(joinedload_all('security_groups.rules')).\ options(joinedload('volumes')).\ options(joinedload('metadata')).\ @@ -1191,10 +1195,6 @@ def instance_get_all(context): session = get_session() return session.query(models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload_all('virtual_interfaces.network')).\ - options(joinedload_all( - 'virtual_interfaces.fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces.instance')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ @@ -1209,27 +1209,6 @@ def instance_get_all_by_filters(context, filters): will be returned by default, unless there's a filter that says otherwise""" - def _regexp_filter_by_ipv6(instance, filter_re): - for interface in instance['virtual_interfaces']: - fixed_ipv6 = interface.get('fixed_ipv6') - if fixed_ipv6 and filter_re.match(fixed_ipv6): - return True - return False - - def _regexp_filter_by_ip(instance, filter_re): - for interface in instance['virtual_interfaces']: - for fixed_ip in interface['fixed_ips']: - if not fixed_ip or not fixed_ip['address']: - continue - if filter_re.match(fixed_ip['address']): - return True - for floating_ip in fixed_ip.get('floating_ips', []): - if not floating_ip or not floating_ip['address']: - continue - if filter_re.match(floating_ip['address']): - return True - return False - def _regexp_filter_by_metadata(instance, meta): inst_metadata = [{node['key']: node['value']} \ for node in instance['metadata']] @@ -1256,7 +1235,7 @@ def instance_get_all_by_filters(context, filters): """Do exact match against a column. value to match can be a list so you can match any value in the list. """ - if isinstance(value, list): + if isinstance(value, list) or isinstance(value, set): column_attr = getattr(models.Instance, column) return query.filter(column_attr.in_(value)) else: @@ -1266,13 +1245,7 @@ def instance_get_all_by_filters(context, filters): session = get_session() query_prefix = session.query(models.Instance).\ - options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload_all('virtual_interfaces.network')).\ - options(joinedload_all( - 'virtual_interfaces.fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces.instance')).\ options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ options(joinedload('instance_type')).\ order_by(desc(models.Instance.created_at)) @@ -1296,7 +1269,7 @@ def instance_get_all_by_filters(context, filters): # Filters for exact matches that we can do along with the SQL query... # For other filters that don't match this, we will do regexp matching exact_match_filter_names = ['project_id', 'user_id', 'image_ref', - 'vm_state', 'instance_type_id', 'deleted'] + 'vm_state', 'instance_type_id', 'deleted', 'uuid'] query_filters = [key for key in filters.iterkeys() if key in exact_match_filter_names] @@ -1308,15 +1281,13 @@ def instance_get_all_by_filters(context, filters): filters.pop(filter_name)) instances = query_prefix.all() - if not instances: return [] # Now filter on everything else for regexp matching.. # For filters not in the list, we'll attempt to use the filter_name # as a column name in Instance.. - regexp_filter_funcs = {'ip6': _regexp_filter_by_ipv6, - 'ip': _regexp_filter_by_ip} + regexp_filter_funcs = {} for filter_name in filters.iterkeys(): filter_func = regexp_filter_funcs.get(filter_name, None) @@ -1330,6 +1301,8 @@ def instance_get_all_by_filters(context, filters): filter_l = lambda instance: _regexp_filter_by_column(instance, filter_name, filter_re) instances = filter(filter_l, instances) + if not instances: + break return instances @@ -1376,7 +1349,6 @@ def instance_get_all_by_user(context, user_id): session = get_session() return session.query(models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ @@ -1391,7 +1363,6 @@ def instance_get_all_by_host(context, host): session = get_session() return session.query(models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ @@ -1408,7 +1379,6 @@ def instance_get_all_by_project(context, project_id): session = get_session() return session.query(models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ @@ -1424,7 +1394,6 @@ def instance_get_all_by_reservation(context, reservation_id): query = session.query(models.Instance).\ filter_by(reservation_id=reservation_id).\ options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ @@ -1441,36 +1410,11 @@ def instance_get_all_by_reservation(context, reservation_id): all() -@require_context -def instance_get_by_fixed_ip(context, address): - """Return instance ref by exact match of FixedIP""" - fixed_ip_ref = fixed_ip_get_by_address(context, address) - return fixed_ip_ref.instance - - -@require_context -def instance_get_by_fixed_ipv6(context, address): - """Return instance ref by exact match of IPv6""" - session = get_session() - - # convert IPv6 address to mac - mac = ipv6.to_mac(address) - - # get virtual interface - vif_ref = virtual_interface_get_by_address(context, mac) - - # look up instance based on instance_id from vif row - result = session.query(models.Instance).\ - filter_by(id=vif_ref['instance_id']) - return result - - @require_admin_context def instance_get_project_vpn(context, project_id): session = get_session() return session.query(models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ @@ -1602,6 +1546,18 @@ def instance_get_actions(context, instance_id): all() +@require_context +def instance_get_id_to_uuid_mapping(context, ids): + session = get_session() + instances = session.query(models.Instance).\ + filter(models.Instance.id.in_(ids)).\ + all() + mapping = {} + for instance in instances: + mapping[instance['id']] = instance['uuid'] + return mapping + + ################### @@ -3199,6 +3155,21 @@ def migration_get_by_instance_and_status(context, instance_uuid, status): return result +@require_admin_context +def migration_get_all_unconfirmed(context, confirm_window, session=None): + confirm_window = datetime.datetime.utcnow() - datetime.timedelta( + seconds=confirm_window) + + if not session: + session = get_session() + + results = session.query(models.Migration).\ + filter(models.Migration.updated_at <= confirm_window).\ + filter_by(status="FINISHED").all() + + return results + + ################## diff --git a/nova/db/sqlalchemy/migrate_repo/versions/047_remove_instances_fk_from_vif.py b/nova/db/sqlalchemy/migrate_repo/versions/047_remove_instances_fk_from_vif.py new file mode 100644 index 000000000..ae9486e58 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/047_remove_instances_fk_from_vif.py @@ -0,0 +1,68 @@ +# Copyright 2011 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import Column, Integer, MetaData, Table +from migrate import ForeignKeyConstraint + +from nova import log as logging + + +meta = MetaData() + + +instances = Table('instances', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + + +vifs = Table('virtual_interfaces', meta, + Column('id', Integer(), primary_key=True, nullable=False), + Column('instance_id', Integer()), + ) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + dialect = migrate_engine.url.get_dialect().name + if dialect.startswith('sqlite'): + return + + try: + ForeignKeyConstraint(columns=[vifs.c.instance_id], + refcolumns=[instances.c.id]).drop() + except Exception: + try: + migrate_engine.execute("ALTER TABLE virtual_interfaces DROP " \ + "FOREIGN KEY " \ + "`virtual_interfaces_ibfk_2`;") + except Exception: + logging.error(_("foreign key constraint couldn't be removed")) + raise + + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + meta.bind = migrate_engine + dialect = migrate_engine.url.get_dialect().name + if dialect.startswith('sqlite'): + return + + try: + ForeignKeyConstraint(columns=[vifs.c.instance_id], + refcolumns=[instances.c.id]).create() + except Exception: + logging.error(_("foreign key constraint couldn't be added")) + raise diff --git a/nova/db/sqlalchemy/migrate_repo/versions/047_sqlite_downgrade.sql b/nova/db/sqlalchemy/migrate_repo/versions/047_sqlite_downgrade.sql new file mode 100644 index 000000000..cf9afbb09 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/047_sqlite_downgrade.sql @@ -0,0 +1,47 @@ +COMMIT; +BEGIN TRANSACTION; + CREATE TEMPORARY TABLE virtual_interfaces_backup ( + created_at DATETIME, + updated_at DATETIME, + deleted_at DATETIME, + deleted BOOLEAN, + id INTEGER NOT NULL, + address VARCHAR(255), + network_id INTEGER, + instance_id INTEGER NOT NULL, + uuid VARCHAR(36), + PRIMARY KEY (id) + ); + + INSERT INTO virtual_interfaces_backup + SELECT created_at, updated_at, deleted_at, deleted, id, address, + network_id, instance_id, uuid + FROM virtual_interfaces; + + DROP TABLE virtual_interfaces; + + CREATE TABLE virtual_interfaces ( + created_at DATETIME, + updated_at DATETIME, + deleted_at DATETIME, + deleted BOOLEAN, + id INTEGER NOT NULL, + address VARCHAR(255), + network_id INTEGER, + instance_id INTEGER NOT NULL, + uuid VARCHAR(36), + PRIMARY KEY (id), + FOREIGN KEY(network_id) REFERENCES networks (id), + FOREIGN KEY(instance_id) REFERENCES instances (id), + UNIQUE (address), + CHECK (deleted IN (0, 1)) + ); + + INSERT INTO virtual_interfaces + SELECT created_at, updated_at, deleted_at, deleted, id, address, + network_id, instance_id, uuid + FROM virtual_interfaces_backup; + + DROP TABLE virtual_interfaces_backup; + +COMMIT; diff --git a/nova/db/sqlalchemy/migrate_repo/versions/047_sqlite_upgrade.sql b/nova/db/sqlalchemy/migrate_repo/versions/047_sqlite_upgrade.sql new file mode 100644 index 000000000..2c0919f1d --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/047_sqlite_upgrade.sql @@ -0,0 +1,45 @@ +BEGIN TRANSACTION; + CREATE TEMPORARY TABLE virtual_interfaces_backup ( + created_at DATETIME, + updated_at DATETIME, + deleted_at DATETIME, + deleted BOOLEAN, + id INTEGER NOT NULL, + address VARCHAR(255), + network_id INTEGER, + instance_id INTEGER NOT NULL, + uuid VARCHAR(36), + PRIMARY KEY (id) + ); + + INSERT INTO virtual_interfaces_backup + SELECT created_at, updated_at, deleted_at, deleted, id, address, + network_id, instance_id, uuid + FROM virtual_interfaces; + + DROP TABLE virtual_interfaces; + + CREATE TABLE virtual_interfaces ( + created_at DATETIME, + updated_at DATETIME, + deleted_at DATETIME, + deleted BOOLEAN, + id INTEGER NOT NULL, + address VARCHAR(255), + network_id INTEGER, + instance_id INTEGER NOT NULL, + uuid VARCHAR(36), + PRIMARY KEY (id), + FOREIGN KEY(network_id) REFERENCES networks (id), + UNIQUE (address), + CHECK (deleted IN (0, 1)) + ); + + INSERT INTO virtual_interfaces + SELECT created_at, updated_at, deleted_at, deleted, id, address, + network_id, instance_id, uuid + FROM virtual_interfaces_backup; + + DROP TABLE virtual_interfaces_backup; + +COMMIT; diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index b5f30a1e3..089cd5450 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -642,10 +642,7 @@ class VirtualInterface(BASE, NovaBase): address = Column(String(255), unique=True) network_id = Column(Integer, ForeignKey('networks.id')) network = relationship(Network, backref=backref('virtual_interfaces')) - - # TODO(tr3buchet): cut the cord, removed foreign key and backrefs - instance_id = Column(Integer, ForeignKey('instances.id'), nullable=False) - instance = relationship(Instance, backref=backref('virtual_interfaces')) + instance_id = Column(Integer, nullable=False) uuid = Column(String(36)) diff --git a/nova/network/api.py b/nova/network/api.py index 78580d360..a1ed28496 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -46,8 +46,9 @@ class API(base.Base): return ips def get_vifs_by_instance(self, context, instance_id): - vifs = self.db.virtual_interface_get_by_instance(context, instance_id) - return vifs + return rpc.call(context, FLAGS.network_topic, + {'method': 'get_vifs_by_instance', + 'args': {'instance_id': instance_id}}) def allocate_floating_ip(self, context): """Adds a floating ip to a project.""" @@ -210,3 +211,12 @@ class API(base.Base): return rpc.call(context, FLAGS.network_topic, {'method': 'validate_networks', 'args': args}) + + def get_instance_uuids_by_ip_filter(self, context, filters): + """Returns a list of dicts in the form of + {'instance_uuid': uuid, 'ip': ip} that matched the ip_filter + """ + args = {'filters': filters} + return rpc.call(context, FLAGS.network_topic, + {'method': 'get_instance_uuids_by_ip_filter', + 'args': args}) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index e693e5939..9bf98fc27 100755 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -550,6 +550,10 @@ def get_dhcp_opts(context, network_ref): return '\n'.join(hosts) +def release_dhcp(dev, address, mac_address): + utils.execute('dhcp_release', dev, address, mac_address, run_as_root=True) + + # NOTE(ja): Sending a HUP only reloads the hostfile, so any # configuration options (like dchp-range, vlan, ...) # aren't reloaded. @@ -594,7 +598,6 @@ def update_dhcp(context, dev, network_ref): 'dnsmasq', '--strict-order', '--bind-interfaces', - '--interface=%s' % dev, '--conf-file=%s' % FLAGS.dnsmasq_config_file, '--domain=%s' % FLAGS.dhcp_domain, '--pid-file=%s' % _dhcp_file(dev, 'pid'), @@ -800,6 +803,10 @@ def unplug(network): return interface_driver.unplug(network) +def get_dev(network): + return interface_driver.get_dev(network) + + class LinuxNetInterfaceDriver(object): """Abstract class that defines generic network host API""" """ for for all Linux interface drivers.""" @@ -812,6 +819,10 @@ class LinuxNetInterfaceDriver(object): """Destory Linux device, return device name""" raise NotImplementedError() + def get_dev(self, network): + """Get device name""" + raise NotImplementedError() + # plugs interfaces using Linux Bridge class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver): @@ -833,6 +844,9 @@ class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver): return network['bridge'] def unplug(self, network): + return self.get_dev(network) + + def get_dev(self, network): return network['bridge'] @classmethod @@ -957,6 +971,9 @@ class LinuxOVSInterfaceDriver(LinuxNetInterfaceDriver): return dev def unplug(self, network): + return self.get_dev(network) + + def get_dev(self, network): dev = "gw-" + str(network['id']) return dev diff --git a/nova/network/manager.py b/nova/network/manager.py index 70e51888f..ffb9f976c 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -48,6 +48,7 @@ import datetime import itertools import math import netaddr +import re import socket from eventlet import greenpool @@ -110,6 +111,8 @@ flags.DEFINE_string('network_host', socket.gethostname(), 'Network host to use for ip allocation in flat modes') flags.DEFINE_bool('fake_call', False, 'If True, skip using the queue and make local calls') +flags.DEFINE_bool('force_dhcp_release', False, + 'If True, send a dhcp release on instance termination') class AddressAlreadyAllocated(exception.Error): @@ -397,6 +400,59 @@ class NetworkManager(manager.SchedulerDependentManager): self.compute_api.trigger_security_group_members_refresh(admin_context, group_ids) + def get_vifs_by_instance(self, context, instance_id): + vifs = self.db.virtual_interface_get_by_instance(context, + instance_id) + return vifs + + def get_instance_uuids_by_ip_filter(self, context, filters): + fixed_ip_filter = filters.get('fixed_ip') + ip_filter = re.compile(str(filters.get('ip'))) + ipv6_filter = re.compile(str(filters.get('ip6'))) + + # NOTE(jkoelker) Should probably figure out a better way to do + # this. But for now it "works", this could suck on + # large installs. + + vifs = self.db.virtual_interface_get_all(context) + results = [] + + for vif in vifs: + if vif['instance_id'] is None: + continue + + fixed_ipv6 = vif.get('fixed_ipv6') + if fixed_ipv6 and ipv6_filter.match(fixed_ipv6): + # NOTE(jkoelker) Will need to update for the UUID flip + results.append({'instance_id': vif['instance_id'], + 'ip': fixed_ipv6}) + + for fixed_ip in vif['fixed_ips']: + if not fixed_ip or not fixed_ip['address']: + continue + if fixed_ip['address'] == fixed_ip_filter: + results.append({'instance_id': vif['instance_id'], + 'ip': fixed_ip['address']}) + continue + if ip_filter.match(fixed_ip['address']): + results.append({'instance_id': vif['instance_id'], + 'ip': fixed_ip['address']}) + continue + for floating_ip in fixed_ip.get('floating_ips', []): + if not floating_ip or not floating_ip['address']: + continue + if ip_filter.match(floating_ip['address']): + results.append({'instance_id': vif['instance_id'], + 'ip': floating_ip['address']}) + continue + + # NOTE(jkoelker) Until we switch over to instance_uuid ;) + ids = [res['instance_id'] for res in results] + uuid_map = self.db.instance_get_id_to_uuid_mapping(context, ids) + for res in results: + res['instance_uuid'] = uuid_map.get(res['instance_id']) + return results + def _get_networks_for_instance(self, context, instance_id, project_id, requested_networks=None): """Determine & return which networks an instance should connect to.""" @@ -629,6 +685,11 @@ class NetworkManager(manager.SchedulerDependentManager): instance_id = instance_ref['id'] self._do_trigger_security_group_members_refresh_for_instance( instance_id) + if FLAGS.force_dhcp_release: + dev = self.driver.get_dev(fixed_ip_ref['network']) + vif = self.db.virtual_interface_get_by_instance_and_network( + context, instance_ref['id'], fixed_ip_ref['network']['id']) + self.driver.release_dhcp(dev, address, vif['address']) def lease_fixed_ip(self, context, address): """Called by dhcp-bridge when ip is leased.""" diff --git a/nova/tests/api/openstack/contrib/test_virtual_interfaces.py b/nova/tests/api/openstack/contrib/test_virtual_interfaces.py index 1db253b35..0260e89d4 100644 --- a/nova/tests/api/openstack/contrib/test_virtual_interfaces.py +++ b/nova/tests/api/openstack/contrib/test_virtual_interfaces.py @@ -14,22 +14,20 @@ # under the License. import json -import stubout import webob from nova import test -from nova import compute +from nova import network from nova.tests.api.openstack import fakes from nova.api.openstack.contrib.virtual_interfaces import \ ServerVirtualInterfaceController -def compute_api_get(self, context, server_id): - return {'virtual_interfaces': [ - {'uuid': '00000000-0000-0000-0000-00000000000000000', - 'address': '00-00-00-00-00-00'}, - {'uuid': '11111111-1111-1111-1111-11111111111111111', - 'address': '11-11-11-11-11-11'}]} +def get_vifs_by_instance(self, context, server_id): + return [{'uuid': '00000000-0000-0000-0000-00000000000000000', + 'address': '00-00-00-00-00-00'}, + {'uuid': '11111111-1111-1111-1111-11111111111111111', + 'address': '11-11-11-11-11-11'}] class ServerVirtualInterfaceTest(test.TestCase): @@ -37,7 +35,8 @@ class ServerVirtualInterfaceTest(test.TestCase): def setUp(self): super(ServerVirtualInterfaceTest, self).setUp() self.controller = ServerVirtualInterfaceController() - self.stubs.Set(compute.api.API, "get", compute_api_get) + self.stubs.Set(network.api.API, "get_vifs_by_instance", + get_vifs_by_instance) def tearDown(self): super(ServerVirtualInterfaceTest, self).tearDown() diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index b3e4ad0cf..cccbafcde 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1309,7 +1309,7 @@ class ServersTest(test.TestCase): self.assertEqual(servers[0]['id'], 100) def test_tenant_id_filter_converts_to_project_id_for_admin(self): - def fake_get_all(context, filters=None): + def fake_get_all(context, filters=None, instances=None): self.assertNotEqual(filters, None) self.assertEqual(filters['project_id'], 'faketenant') self.assertFalse(filters.get('tenant_id')) @@ -2255,6 +2255,58 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_create_instance_v1_1_malformed_entity(self): + self._setup_for_create_instance() + req = webob.Request.blank('/v1.1/fake/servers') + req.method = 'POST' + req.body = json.dumps({'server': 'string'}) + 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_malformed_body_string(self): + self._setup_for_create_instance() + req = webob.Request.blank('/v1.1/fake/servers') + req.method = 'POST' + req.body = 'string' + 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_malformed_body_list(self): + self._setup_for_create_instance() + body = ['string'] + req = webob.Request.blank('/v1.1/fake/servers') + req.method = 'POST' + req.body = json.dumps(['string']) + req.headers['content-type'] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 422) + + def test_create_instance_v1_0_malformed_entity(self): + req = webob.Request.blank('/v1.0/servers') + req.method = 'POST' + req.body = json.dumps({'server': 'string'}) + req.headers['content-type'] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_create_instance_v1_0_malformed_body_string(self): + req = webob.Request.blank('/v1.0/servers') + req.method = 'POST' + req.body = 'string' + req.headers['content-type'] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_create_instance_v1_0_malformed_body_list(self): + req = webob.Request.blank('/v1.0/servers') + req.method = 'POST' + req.body = json.dumps(['string']) + req.headers['content-type'] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 422) + def test_update_server_no_body(self): req = webob.Request.blank('/v1.0/servers/1') req.method = 'PUT' diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 142206755..febac5e09 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -16,8 +16,9 @@ # under the License. from nova import db +from nova import exception from nova import flags -from nova import test +from nova import utils from nova.network import manager as network_manager @@ -64,6 +65,64 @@ class FakeModel(dict): return self[name] +class FakeNetworkManager(network_manager.NetworkManager): + """This NetworkManager doesn't call the base class so we can bypass all + inherited service cruft and just perform unit tests. + """ + + class FakeDB: + def fixed_ip_get_by_instance(self, context, instance_id): + return [dict(address='10.0.0.0'), dict(address='10.0.0.1'), + dict(address='10.0.0.2')] + + def network_get_by_cidr(self, context, cidr): + raise exception.NetworkNotFoundForCidr() + + def network_create_safe(self, context, net): + fakenet = dict(net) + fakenet['id'] = 999 + return fakenet + + def network_get_all(self, context): + raise exception.NoNetworksFound() + + def virtual_interface_get_all(self, context): + floats = [{'address': '172.16.1.1'}, + {'address': '172.16.1.2'}, + {'address': '173.16.1.2'}] + + vifs = [{'instance_id': 0, + 'fixed_ipv6': '2001:db8::dcad:beff:feef:1', + 'fixed_ips': [{'address': '172.16.0.1', + 'floating_ips': [floats[0]]}]}, + {'instance_id': 20, + 'fixed_ipv6': '2001:db8::dcad:beff:feef:2', + 'fixed_ips': [{'address': '172.16.0.2', + 'floating_ips': [floats[1]]}]}, + {'instance_id': 30, + 'fixed_ipv6': '2002:db8::dcad:beff:feef:2', + 'fixed_ips': [{'address': '173.16.0.2', + 'floating_ips': [floats[2]]}]}] + return vifs + + def instance_get_id_to_uuid_mapping(self, context, ids): + # NOTE(jkoelker): This is just here until we can rely on UUIDs + mapping = {} + for id in ids: + mapping[id] = str(utils.gen_uuid()) + return mapping + + def __init__(self): + self.db = self.FakeDB() + self.deallocate_called = None + + def deallocate_fixed_ip(self, context, address): + self.deallocate_called = address + + def _create_fixed_ips(self, context, network_id): + pass + + flavor = {'id': 0, 'name': 'fake_flavor', 'memory_mb': 2048, diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 356412dbf..948c7ad40 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -21,22 +21,24 @@ Tests For Compute """ from nova import compute -from nova.compute import instance_types -from nova.compute import manager as compute_manager -from nova.compute import power_state -from nova.compute import vm_states from nova import context from nova import db -from nova.db.sqlalchemy import models -from nova.db.sqlalchemy import api as sqlalchemy_api from nova import exception from nova import flags -import nova.image.fake from nova import log as logging from nova import rpc from nova import test from nova import utils + +from nova.compute import instance_types +from nova.compute import manager as compute_manager +from nova.compute import power_state +from nova.compute import vm_states +from nova.db.sqlalchemy import models +from nova.image import fake as fake_image from nova.notifier import test_notifier +from nova.tests import fake_network + LOG = logging.getLogger('nova.tests.compute') FLAGS = flags.FLAGS @@ -74,7 +76,7 @@ class ComputeTestCase(test.TestCase): def fake_show(meh, context, id): return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} - self.stubs.Set(nova.image.fake._FakeImageService, 'show', fake_show) + self.stubs.Set(fake_image._FakeImageService, 'show', fake_show) def _create_instance(self, params=None): """Create a test instance""" @@ -1023,190 +1025,19 @@ class ComputeTestCase(test.TestCase): db.instance_destroy(c, instance_id2) db.instance_destroy(c, instance_id3) - def test_get_by_fixed_ip(self): - """Test getting 1 instance by Fixed IP""" - c = context.get_admin_context() - instance_id1 = self._create_instance() - instance_id2 = self._create_instance({'id': 20}) - instance_id3 = self._create_instance({'id': 30}) - - vif_ref1 = db.virtual_interface_create(c, - {'address': '12:34:56:78:90:12', - 'instance_id': instance_id1, - 'network_id': 1}) - vif_ref2 = db.virtual_interface_create(c, - {'address': '90:12:34:56:78:90', - 'instance_id': instance_id2, - 'network_id': 1}) - - db.fixed_ip_create(c, - {'address': '1.1.1.1', - 'instance_id': instance_id1, - 'virtual_interface_id': vif_ref1['id']}) - db.fixed_ip_create(c, - {'address': '1.1.2.1', - 'instance_id': instance_id2, - 'virtual_interface_id': vif_ref2['id']}) - - # regex not allowed - instances = self.compute_api.get_all(c, - search_opts={'fixed_ip': '.*'}) - self.assertEqual(len(instances), 0) - - instances = self.compute_api.get_all(c, - search_opts={'fixed_ip': '1.1.3.1'}) - self.assertEqual(len(instances), 0) - - instances = self.compute_api.get_all(c, - search_opts={'fixed_ip': '1.1.1.1'}) - self.assertEqual(len(instances), 1) - self.assertEqual(instances[0].id, instance_id1) - - instances = self.compute_api.get_all(c, - search_opts={'fixed_ip': '1.1.2.1'}) - self.assertEqual(len(instances), 1) - self.assertEqual(instances[0].id, instance_id2) - - db.virtual_interface_delete(c, vif_ref1['id']) - db.virtual_interface_delete(c, vif_ref2['id']) - db.instance_destroy(c, instance_id1) - db.instance_destroy(c, instance_id2) - - def test_get_all_by_ip_regexp(self): - """Test searching by Floating and Fixed IP""" - c = context.get_admin_context() - instance_id1 = self._create_instance({'display_name': 'woot'}) - instance_id2 = self._create_instance({ - 'display_name': 'woo', - 'id': 20}) - instance_id3 = self._create_instance({ - 'display_name': 'not-woot', - 'id': 30}) - - vif_ref1 = db.virtual_interface_create(c, - {'address': '12:34:56:78:90:12', - 'instance_id': instance_id1, - 'network_id': 1}) - vif_ref2 = db.virtual_interface_create(c, - {'address': '90:12:34:56:78:90', - 'instance_id': instance_id2, - 'network_id': 1}) - vif_ref3 = db.virtual_interface_create(c, - {'address': '34:56:78:90:12:34', - 'instance_id': instance_id3, - 'network_id': 1}) - - db.fixed_ip_create(c, - {'address': '1.1.1.1', - 'instance_id': instance_id1, - 'virtual_interface_id': vif_ref1['id']}) - db.fixed_ip_create(c, - {'address': '1.1.2.1', - 'instance_id': instance_id2, - 'virtual_interface_id': vif_ref2['id']}) - fix_addr = db.fixed_ip_create(c, - {'address': '1.1.3.1', - 'instance_id': instance_id3, - 'virtual_interface_id': vif_ref3['id']}) - fix_ref = db.fixed_ip_get_by_address(c, fix_addr) - flo_ref = db.floating_ip_create(c, - {'address': '10.0.0.2', - 'fixed_ip_id': fix_ref['id']}) - - # ends up matching 2nd octet here.. so all 3 match - instances = self.compute_api.get_all(c, - search_opts={'ip': '.*\.1'}) - self.assertEqual(len(instances), 3) - - instances = self.compute_api.get_all(c, - search_opts={'ip': '1.*'}) - self.assertEqual(len(instances), 3) - - instances = self.compute_api.get_all(c, - search_opts={'ip': '.*\.1.\d+$'}) - self.assertEqual(len(instances), 1) - instance_ids = [instance.id for instance in instances] - self.assertTrue(instance_id1 in instance_ids) - - instances = self.compute_api.get_all(c, - search_opts={'ip': '.*\.2.+'}) - self.assertEqual(len(instances), 1) - self.assertEqual(instances[0].id, instance_id2) - - instances = self.compute_api.get_all(c, - search_opts={'ip': '10.*'}) - self.assertEqual(len(instances), 1) - self.assertEqual(instances[0].id, instance_id3) - - db.virtual_interface_delete(c, vif_ref1['id']) - db.virtual_interface_delete(c, vif_ref2['id']) - db.virtual_interface_delete(c, vif_ref3['id']) - db.floating_ip_destroy(c, '10.0.0.2') - db.instance_destroy(c, instance_id1) - db.instance_destroy(c, instance_id2) - db.instance_destroy(c, instance_id3) - - def test_get_all_by_ipv6_regexp(self): - """Test searching by IPv6 address""" - - c = context.get_admin_context() - instance_id1 = self._create_instance({'display_name': 'woot'}) - instance_id2 = self._create_instance({ - 'display_name': 'woo', - 'id': 20}) - instance_id3 = self._create_instance({ - 'display_name': 'not-woot', - 'id': 30}) - - vif_ref1 = db.virtual_interface_create(c, - {'address': '12:34:56:78:90:12', - 'instance_id': instance_id1, - 'network_id': 1}) - vif_ref2 = db.virtual_interface_create(c, - {'address': '90:12:34:56:78:90', - 'instance_id': instance_id2, - 'network_id': 1}) - vif_ref3 = db.virtual_interface_create(c, - {'address': '34:56:78:90:12:34', - 'instance_id': instance_id3, - 'network_id': 1}) - - # This will create IPv6 addresses of: - # 1: fd00::1034:56ff:fe78:9012 - # 20: fd00::9212:34ff:fe56:7890 - # 30: fd00::3656:78ff:fe90:1234 - - instances = self.compute_api.get_all(c, - search_opts={'ip6': '.*1034.*'}) - self.assertEqual(len(instances), 1) - self.assertEqual(instances[0].id, instance_id1) - - instances = self.compute_api.get_all(c, - search_opts={'ip6': '^fd00.*'}) - self.assertEqual(len(instances), 3) - instance_ids = [instance.id for instance in instances] - self.assertTrue(instance_id1 in instance_ids) - self.assertTrue(instance_id2 in instance_ids) - self.assertTrue(instance_id3 in instance_ids) - - instances = self.compute_api.get_all(c, - search_opts={'ip6': '^.*12.*34.*'}) - self.assertEqual(len(instances), 2) - instance_ids = [instance.id for instance in instances] - self.assertTrue(instance_id2 in instance_ids) - self.assertTrue(instance_id3 in instance_ids) - - db.virtual_interface_delete(c, vif_ref1['id']) - db.virtual_interface_delete(c, vif_ref2['id']) - db.virtual_interface_delete(c, vif_ref3['id']) - db.instance_destroy(c, instance_id1) - db.instance_destroy(c, instance_id2) - db.instance_destroy(c, instance_id3) - def test_get_all_by_multiple_options_at_once(self): """Test searching by multiple options at once""" c = context.get_admin_context() - instance_id1 = self._create_instance({'display_name': 'woot'}) + network_manager = fake_network.FakeNetworkManager() + self.stubs.Set(self.compute_api.network_api, + 'get_instance_uuids_by_ip_filter', + network_manager.get_instance_uuids_by_ip_filter) + self.stubs.Set(network_manager.db, + 'instance_get_id_to_uuid_mapping', + db.instance_get_id_to_uuid_mapping) + + instance_id1 = self._create_instance({'display_name': 'woot', + 'id': 0}) instance_id2 = self._create_instance({ 'display_name': 'woo', 'id': 20}) @@ -1214,36 +1045,6 @@ class ComputeTestCase(test.TestCase): 'display_name': 'not-woot', 'id': 30}) - vif_ref1 = db.virtual_interface_create(c, - {'address': '12:34:56:78:90:12', - 'instance_id': instance_id1, - 'network_id': 1}) - vif_ref2 = db.virtual_interface_create(c, - {'address': '90:12:34:56:78:90', - 'instance_id': instance_id2, - 'network_id': 1}) - vif_ref3 = db.virtual_interface_create(c, - {'address': '34:56:78:90:12:34', - 'instance_id': instance_id3, - 'network_id': 1}) - - db.fixed_ip_create(c, - {'address': '1.1.1.1', - 'instance_id': instance_id1, - 'virtual_interface_id': vif_ref1['id']}) - db.fixed_ip_create(c, - {'address': '1.1.2.1', - 'instance_id': instance_id2, - 'virtual_interface_id': vif_ref2['id']}) - fix_addr = db.fixed_ip_create(c, - {'address': '1.1.3.1', - 'instance_id': instance_id3, - 'virtual_interface_id': vif_ref3['id']}) - fix_ref = db.fixed_ip_get_by_address(c, fix_addr) - flo_ref = db.floating_ip_create(c, - {'address': '10.0.0.2', - 'fixed_ip_id': fix_ref['id']}) - # ip ends up matching 2nd octet here.. so all 3 match ip # but 'name' only matches one instances = self.compute_api.get_all(c, @@ -1251,18 +1052,18 @@ class ComputeTestCase(test.TestCase): self.assertEqual(len(instances), 1) self.assertEqual(instances[0].id, instance_id3) - # ip ends up matching any ip with a '2' in it.. so instance - # 2 and 3.. but name should only match #2 + # ip ends up matching any ip with a '1' in the last octet.. + # so instance 1 and 3.. but name should only match #1 # but 'name' only matches one instances = self.compute_api.get_all(c, - search_opts={'ip': '.*2', 'name': '^woo.*'}) + search_opts={'ip': '.*\.1$', 'name': '^woo.*'}) self.assertEqual(len(instances), 1) - self.assertEqual(instances[0].id, instance_id2) + self.assertEqual(instances[0].id, instance_id1) # same as above but no match on name (name matches instance_id1 # but the ip query doesn't instances = self.compute_api.get_all(c, - search_opts={'ip': '.*2.*', 'name': '^woot.*'}) + search_opts={'ip': '.*\.2$', 'name': '^woot.*'}) self.assertEqual(len(instances), 0) # ip matches all 3... ipv6 matches #2+#3...name matches #3 @@ -1273,10 +1074,6 @@ class ComputeTestCase(test.TestCase): self.assertEqual(len(instances), 1) self.assertEqual(instances[0].id, instance_id3) - db.virtual_interface_delete(c, vif_ref1['id']) - db.virtual_interface_delete(c, vif_ref2['id']) - db.virtual_interface_delete(c, vif_ref3['id']) - db.floating_ip_destroy(c, '10.0.0.2') db.instance_destroy(c, instance_id1) db.instance_destroy(c, instance_id2) db.instance_destroy(c, instance_id3) diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index 60d7abd8c..81194e3f9 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -18,6 +18,8 @@ """Unit tests for the DB API""" +import datetime + from nova import test from nova import context from nova import db @@ -92,6 +94,32 @@ class DbApiTestCase(test.TestCase): db.instance_destroy(self.context, inst1.id) result = db.instance_get_all_by_filters(self.context.elevated(), {}) self.assertEqual(2, len(result)) - self.assertEqual(result[0].id, inst2.id) - self.assertEqual(result[1].id, inst1.id) - self.assertTrue(result[1].deleted) + self.assertIn(inst1.id, [result[0].id, result[1].id]) + self.assertIn(inst2.id, [result[0].id, result[1].id]) + if inst1.id == result[0].id: + self.assertTrue(result[0].deleted) + else: + self.assertTrue(result[1].deleted) + + def test_migration_get_all_unconfirmed(self): + ctxt = context.get_admin_context() + + # Ensure no migrations are returned. + results = db.migration_get_all_unconfirmed(ctxt, 10) + self.assertEqual(0, len(results)) + + # Ensure one migration older than 10 seconds is returned. + updated_at = datetime.datetime(2000, 01, 01, 12, 00, 00) + values = {"status": "FINISHED", "updated_at": updated_at} + migration = db.migration_create(ctxt, values) + results = db.migration_get_all_unconfirmed(ctxt, 10) + self.assertEqual(1, len(results)) + db.migration_update(ctxt, migration.id, {"status": "CONFIRMED"}) + + # Ensure the new migration is not returned. + updated_at = datetime.datetime.utcnow() + values = {"status": "FINISHED", "updated_at": updated_at} + migration = db.migration_create(ctxt, values) + results = db.migration_get_all_unconfirmed(ctxt, 10) + self.assertEqual(0, len(results)) + db.migration_update(ctxt, migration.id, {"status": "CONFIRMED"}) diff --git a/nova/tests/test_metadata.py b/nova/tests/test_metadata.py index b06e5c136..2f82132fa 100644 --- a/nova/tests/test_metadata.py +++ b/nova/tests/test_metadata.py @@ -19,22 +19,21 @@ """Tests for the testing the metadata code.""" import base64 -import httplib import webob from nova import exception from nova import test -from nova import wsgi from nova.api.ec2 import metadatarequesthandler from nova.db.sqlalchemy import api +from nova.tests import fake_network USER_DATA_STRING = ("This is an encoded string") ENCODE_USER_DATA_STRING = base64.b64encode(USER_DATA_STRING) -def return_non_existing_server_by_address(context, address): +def return_non_existing_server_by_address(context, address, *args, **kwarg): raise exception.NotFound() @@ -69,6 +68,10 @@ class MetadataTestCase(test.TestCase): self.stubs.Set(api, 'instance_get_all_by_filters', instance_get_list) self.stubs.Set(api, 'instance_get_floating_address', floating_get) self.app = metadatarequesthandler.MetadataRequestHandler() + network_manager = fake_network.FakeNetworkManager() + self.stubs.Set(self.app.cc.network_api, + 'get_instance_uuids_by_ip_filter', + network_manager.get_instance_uuids_by_ip_filter) def request(self, relative_url): request = webob.Request.blank(relative_url) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 926ea065a..15c179177 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -438,55 +438,23 @@ class VlanNetworkTestCase(test.TestCase): class CommonNetworkTestCase(test.TestCase): - - class FakeNetworkManager(network_manager.NetworkManager): - """This NetworkManager doesn't call the base class so we can bypass all - inherited service cruft and just perform unit tests. - """ - - class FakeDB: - def fixed_ip_get_by_instance(self, context, instance_id): - return [dict(address='10.0.0.0'), dict(address='10.0.0.1'), - dict(address='10.0.0.2')] - - def network_get_by_cidr(self, context, cidr): - raise exception.NetworkNotFoundForCidr() - - def network_create_safe(self, context, net): - fakenet = dict(net) - fakenet['id'] = 999 - return fakenet - - def network_get_all(self, context): - raise exception.NoNetworksFound() - - def __init__(self): - self.db = self.FakeDB() - self.deallocate_called = None - - def deallocate_fixed_ip(self, context, address): - self.deallocate_called = address - - def _create_fixed_ips(self, context, network_id): - pass - def fake_create_fixed_ips(self, context, network_id): return None def test_remove_fixed_ip_from_instance(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() manager.remove_fixed_ip_from_instance(None, 99, '10.0.0.1') self.assertEquals(manager.deallocate_called, '10.0.0.1') def test_remove_fixed_ip_from_instance_bad_input(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() self.assertRaises(exception.FixedIpNotFoundForSpecificInstance, manager.remove_fixed_ip_from_instance, None, 99, 'bad input') def test_validate_cidrs(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() nets = manager.create_networks(None, 'fake', '192.168.0.0/24', False, 1, 256, None, None, None, None) @@ -495,7 +463,7 @@ class CommonNetworkTestCase(test.TestCase): self.assertTrue('192.168.0.0/24' in cidrs) def test_validate_cidrs_split_exact_in_half(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() nets = manager.create_networks(None, 'fake', '192.168.0.0/24', False, 2, 128, None, None, None, None) @@ -505,7 +473,7 @@ class CommonNetworkTestCase(test.TestCase): self.assertTrue('192.168.0.128/25' in cidrs) def test_validate_cidrs_split_cidr_in_use_middle_of_range(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() self.mox.StubOutWithMock(manager.db, 'network_get_all') ctxt = mox.IgnoreArg() manager.db.network_get_all(ctxt).AndReturn([{'id': 1, @@ -523,7 +491,7 @@ class CommonNetworkTestCase(test.TestCase): self.assertFalse('192.168.2.0/24' in cidrs) def test_validate_cidrs_smaller_subnet_in_use(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() self.mox.StubOutWithMock(manager.db, 'network_get_all') ctxt = mox.IgnoreArg() manager.db.network_get_all(ctxt).AndReturn([{'id': 1, @@ -536,7 +504,7 @@ class CommonNetworkTestCase(test.TestCase): self.assertRaises(ValueError, manager.create_networks, *args) def test_validate_cidrs_split_smaller_cidr_in_use(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() self.mox.StubOutWithMock(manager.db, 'network_get_all') ctxt = mox.IgnoreArg() manager.db.network_get_all(ctxt).AndReturn([{'id': 1, @@ -553,7 +521,7 @@ class CommonNetworkTestCase(test.TestCase): self.assertFalse('192.168.2.0/24' in cidrs) def test_validate_cidrs_split_smaller_cidr_in_use2(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() self.mox.StubOutWithMock(manager.db, 'network_get_all') ctxt = mox.IgnoreArg() manager.db.network_get_all(ctxt).AndReturn([{'id': 1, @@ -569,7 +537,7 @@ class CommonNetworkTestCase(test.TestCase): self.assertFalse('192.168.2.0/27' in cidrs) def test_validate_cidrs_split_all_in_use(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() self.mox.StubOutWithMock(manager.db, 'network_get_all') ctxt = mox.IgnoreArg() in_use = [{'id': 1, 'cidr': '192.168.2.9/29'}, @@ -585,14 +553,14 @@ class CommonNetworkTestCase(test.TestCase): self.assertRaises(ValueError, manager.create_networks, *args) def test_validate_cidrs_one_in_use(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() args = (None, 'fake', '192.168.0.0/24', False, 2, 256, None, None, None, None) # ValueError: network_size * num_networks exceeds cidr size self.assertRaises(ValueError, manager.create_networks, *args) def test_validate_cidrs_already_used(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() self.mox.StubOutWithMock(manager.db, 'network_get_all') ctxt = mox.IgnoreArg() manager.db.network_get_all(ctxt).AndReturn([{'id': 1, @@ -604,7 +572,7 @@ class CommonNetworkTestCase(test.TestCase): self.assertRaises(ValueError, manager.create_networks, *args) def test_validate_cidrs_too_many(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() args = (None, 'fake', '192.168.0.0/24', False, 200, 256, None, None, None, None) # ValueError: Not enough subnets avail to satisfy requested @@ -612,7 +580,7 @@ class CommonNetworkTestCase(test.TestCase): self.assertRaises(ValueError, manager.create_networks, *args) def test_validate_cidrs_split_partial(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() nets = manager.create_networks(None, 'fake', '192.168.0.0/16', False, 2, 256, None, None, None, None) returned_cidrs = [str(net['cidr']) for net in nets] @@ -620,7 +588,7 @@ class CommonNetworkTestCase(test.TestCase): self.assertTrue('192.168.1.0/24' in returned_cidrs) def test_validate_cidrs_conflict_existing_supernet(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() self.mox.StubOutWithMock(manager.db, 'network_get_all') ctxt = mox.IgnoreArg() fakecidr = [{'id': 1, 'cidr': '192.168.0.0/8'}] @@ -634,16 +602,15 @@ class CommonNetworkTestCase(test.TestCase): def test_create_networks(self): cidr = '192.168.0.0/24' - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() self.stubs.Set(manager, '_create_fixed_ips', self.fake_create_fixed_ips) args = [None, 'foo', cidr, None, 1, 256, 'fd00::/48', None, None, None] - result = manager.create_networks(*args) self.assertTrue(manager.create_networks(*args)) def test_create_networks_cidr_already_used(self): - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() self.mox.StubOutWithMock(manager.db, 'network_get_all') ctxt = mox.IgnoreArg() fakecidr = [{'id': 1, 'cidr': '192.168.0.0/24'}] @@ -655,9 +622,124 @@ class CommonNetworkTestCase(test.TestCase): def test_create_networks_many(self): cidr = '192.168.0.0/16' - manager = self.FakeNetworkManager() + manager = fake_network.FakeNetworkManager() self.stubs.Set(manager, '_create_fixed_ips', self.fake_create_fixed_ips) args = [None, 'foo', cidr, None, 10, 256, 'fd00::/48', None, None, None] self.assertTrue(manager.create_networks(*args)) + + def test_get_instance_uuids_by_ip_regex(self): + manager = fake_network.FakeNetworkManager() + _vifs = manager.db.virtual_interface_get_all(None) + + # Greedy get eveything + res = manager.get_instance_uuids_by_ip_filter(None, {'ip': '.*'}) + self.assertEqual(len(res), len(_vifs)) + + # Doesn't exist + res = manager.get_instance_uuids_by_ip_filter(None, {'ip': '10.0.0.1'}) + self.assertFalse(res) + + # Get instance 1 + res = manager.get_instance_uuids_by_ip_filter(None, + {'ip': '172.16.0.2'}) + self.assertTrue(res) + self.assertEqual(len(res), 1) + self.assertEqual(res[0]['instance_id'], _vifs[1]['instance_id']) + + # Get instance 2 + res = manager.get_instance_uuids_by_ip_filter(None, + {'ip': '173.16.0.2'}) + self.assertTrue(res) + self.assertEqual(len(res), 1) + self.assertEqual(res[0]['instance_id'], _vifs[2]['instance_id']) + + # Get instance 0 and 1 + res = manager.get_instance_uuids_by_ip_filter(None, + {'ip': '172.16.0.*'}) + self.assertTrue(res) + self.assertEqual(len(res), 2) + self.assertEqual(res[0]['instance_id'], _vifs[0]['instance_id']) + self.assertEqual(res[1]['instance_id'], _vifs[1]['instance_id']) + + # Get instance 1 and 2 + res = manager.get_instance_uuids_by_ip_filter(None, + {'ip': '17..16.0.2'}) + self.assertTrue(res) + self.assertEqual(len(res), 2) + self.assertEqual(res[0]['instance_id'], _vifs[1]['instance_id']) + self.assertEqual(res[1]['instance_id'], _vifs[2]['instance_id']) + + def test_get_instance_uuids_by_ipv6_regex(self): + manager = fake_network.FakeNetworkManager() + _vifs = manager.db.virtual_interface_get_all(None) + + # Greedy get eveything + res = manager.get_instance_uuids_by_ip_filter(None, {'ip6': '.*'}) + self.assertEqual(len(res), len(_vifs)) + + # Doesn't exist + res = manager.get_instance_uuids_by_ip_filter(None, + {'ip6': '.*1034.*'}) + self.assertFalse(res) + + # Get instance 1 + res = manager.get_instance_uuids_by_ip_filter(None, + {'ip6': '2001:.*:2'}) + self.assertTrue(res) + self.assertEqual(len(res), 1) + self.assertEqual(res[0]['instance_id'], _vifs[1]['instance_id']) + + # Get instance 2 + ip6 = '2002:db8::dcad:beff:feef:2' + res = manager.get_instance_uuids_by_ip_filter(None, {'ip6': ip6}) + self.assertTrue(res) + self.assertEqual(len(res), 1) + self.assertEqual(res[0]['instance_id'], _vifs[2]['instance_id']) + + # Get instance 0 and 1 + res = manager.get_instance_uuids_by_ip_filter(None, {'ip6': '2001:.*'}) + self.assertTrue(res) + self.assertEqual(len(res), 2) + self.assertEqual(res[0]['instance_id'], _vifs[0]['instance_id']) + self.assertEqual(res[1]['instance_id'], _vifs[1]['instance_id']) + + # Get instance 1 and 2 + ip6 = '200.:db8::dcad:beff:feef:2' + res = manager.get_instance_uuids_by_ip_filter(None, {'ip6': ip6}) + self.assertTrue(res) + self.assertEqual(len(res), 2) + self.assertEqual(res[0]['instance_id'], _vifs[1]['instance_id']) + self.assertEqual(res[1]['instance_id'], _vifs[2]['instance_id']) + + def test_get_instance_uuids_by_ip(self): + manager = fake_network.FakeNetworkManager() + _vifs = manager.db.virtual_interface_get_all(None) + + # No regex for you! + res = manager.get_instance_uuids_by_ip_filter(None, + {'fixed_ip': '.*'}) + self.assertFalse(res) + + # Doesn't exist + ip = '10.0.0.1' + res = manager.get_instance_uuids_by_ip_filter(None, + {'fixed_ip': ip}) + self.assertFalse(res) + + # Get instance 1 + ip = '172.16.0.2' + res = manager.get_instance_uuids_by_ip_filter(None, + {'fixed_ip': ip}) + self.assertTrue(res) + self.assertEqual(len(res), 1) + self.assertEqual(res[0]['instance_id'], _vifs[1]['instance_id']) + + # Get instance 2 + ip = '173.16.0.2' + res = manager.get_instance_uuids_by_ip_filter(None, + {'fixed_ip': ip}) + self.assertTrue(res) + self.assertEqual(len(res), 1) + self.assertEqual(res[0]['instance_id'], _vifs[2]['instance_id']) diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py index 440d3401b..8e20e999f 100644 --- a/nova/tests/test_virt_drivers.py +++ b/nova/tests/test_virt_drivers.py @@ -177,6 +177,10 @@ class _VirtDriverTestCase(test.TestCase): self.connection.poll_rescued_instances(10) @catch_notimplementederror + def test_poll_unconfirmed_resizes(self): + self.connection.poll_unconfirmed_resizes(10) + + @catch_notimplementederror def test_migrate_disk_and_power_off(self): instance_ref = test_utils.get_test_instance() network_info = test_utils.get_test_network_info() diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 301346c6b..fc47d8d2d 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -469,6 +469,11 @@ class ComputeDriver(object): # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() + def poll_unconfirmed_resizes(self, resize_confirm_window): + """Poll for unconfirmed resizes""" + # TODO(Vek): Need to pass context in for access to auth_token + raise NotImplementedError() + def host_power_action(self, host, action): """Reboots, shuts down or powers up the host.""" raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 3596d8353..96f521ee7 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -130,6 +130,9 @@ class FakeConnection(driver.ComputeDriver): def poll_rescued_instances(self, timeout): pass + def poll_unconfirmed_resizes(self, resize_confirm_window): + pass + def migrate_disk_and_power_off(self, instance, dest): pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 76925b405..fbf898317 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -487,6 +487,9 @@ class HyperVConnection(driver.ComputeDriver): def poll_rescued_instances(self, timeout): pass + def poll_unconfirmed_resizes(self, resize_confirm_window): + pass + def update_available_resource(self, ctxt, host): """This method is supported only by libvirt.""" return diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 4a0849127..b3c8ebf6e 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -588,6 +588,10 @@ class LibvirtConnection(driver.ComputeDriver): def poll_rescued_instances(self, timeout): pass + @exception.wrap_exception() + def poll_unconfirmed_resizes(self, resize_confirm_window): + pass + # NOTE(ilyaalekseyev): Implementation like in multinics # for xenapi(tr3buchet) @exception.wrap_exception() diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 8f7eb3795..210b8fe65 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -38,6 +38,7 @@ from nova import ipv6 from nova import log as logging from nova import utils +from nova.compute import api as compute from nova.compute import power_state from nova.virt import driver from nova.virt.xenapi.network_utils import NetworkHelper @@ -77,6 +78,7 @@ class VMOps(object): """ def __init__(self, session): self.XenAPI = session.get_imported_xenapi() + self.compute_api = compute.API() self._session = session self.poll_rescue_last_ran = None VMHelper.XenAPI = self.XenAPI @@ -1039,6 +1041,27 @@ class VMOps(object): self._session.call_xenapi("VM.start", original_vm_ref, False, False) + def poll_unconfirmed_resizes(self, resize_confirm_window): + """Poll for unconfirmed resizes. + + Look for any unconfirmed resizes that are older than + `resize_confirm_window` and automatically confirm them. + """ + ctxt = nova_context.get_admin_context() + migrations = db.migration_get_all_unconfirmed(ctxt, + resize_confirm_window) + + migrations_info = dict(migration_count=len(migrations), + confirm_window=FLAGS.resize_confirm_window) + + if migrations_info["migration_count"] > 0: + LOG.info(_("Found %(migration_count)d unconfirmed migrations " + "older than %(confirm_window)d seconds") % migrations_info) + + for migration in migrations: + LOG.info(_("Automatically confirming migration %d"), migration.id) + self.compute_api.confirm_resize(ctxt, migration.instance_uuid) + def get_info(self, instance): """Return data about VM instance.""" vm_ref = self._get_vm_opaque_ref(instance) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index f6dbc19f8..7fc683a9f 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -254,6 +254,10 @@ class XenAPIConnection(driver.ComputeDriver): """Poll for rescued instances""" self._vmops.poll_rescued_instances(timeout) + def poll_unconfirmed_resizes(self, resize_confirm_window): + """Poll for unconfirmed resizes""" + self._vmops.poll_unconfirmed_resizes(resize_confirm_window) + def reset_network(self, instance): """reset networking for specified instance""" self._vmops.reset_network(instance) |
