From 3403e773f5a38c5d415e4ab66799c6e239223a0d Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 17 Jan 2011 19:40:35 -0500 Subject: [PATCH 01/53] Stubbed-out code for working with provider-firewalls. --- nova/adminclient.py | 21 +++++++++++++++++++++ nova/api/ec2/admin.py | 8 ++++++++ 2 files changed, 29 insertions(+) diff --git a/nova/adminclient.py b/nova/adminclient.py index b2609c8c40e2..90ad4d9dd90b 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -190,6 +190,22 @@ class HostInfo(object): setattr(self, name, value) +class SimpleResponse(object): + def __init__(self, connection=None): + self.connection = connection + self.status = None + self.message = '' + + def __repr__(self): + return 'Status:%s' % self.status + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + setattr(self, name.lower(), str(value)) + + class NovaAdminClient(object): def __init__( @@ -373,3 +389,8 @@ class NovaAdminClient(object): def get_hosts(self): return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)]) + + def block_ips(self, cidr): + """Block incoming traffic from specified hosts.""" + return self.apiconn.get_object('BlockExternalAddresses', + {'Cidr': cidr}, SimpleResponse) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 758b612e80fb..b784c5a0087e 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -209,3 +209,11 @@ class AdminController(object): def describe_host(self, _context, name, **_kwargs): """Returns status info for single node.""" return host_dict(db.host_get(name)) + + def block_external_addresses(self, context, cidr): + """Add provider-level firewall rules to block incoming traffic.""" + LOG.audit(_("Blocking access to all projects incoming from %s"), + cidr, context=context) + raise NotImplementedError(_("Awaiting implementation.")) + # TODO(todd): implement + # return {'status': 'OK', 'message': 'Disabled (number) IPs'} From d4e7eb818c9f4ec51fd3a88a0e92d557867511d4 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 17 Jan 2011 23:18:46 -0500 Subject: [PATCH 02/53] Add rules to database, cast refresh message and trickle down to firewall driver. --- nova/api/ec2/admin.py | 43 ++++++++++++++++++++++++++++++++---- nova/compute/api.py | 9 ++++++++ nova/compute/manager.py | 5 +++++ nova/db/api.py | 8 +++++++ nova/db/sqlalchemy/api.py | 12 ++++++++++ nova/db/sqlalchemy/models.py | 11 +++++++++ nova/virt/connection.py | 1 + nova/virt/libvirt_conn.py | 10 +++++++++ 8 files changed, 95 insertions(+), 4 deletions(-) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index b784c5a0087e..1ae5f7094b93 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -21,7 +21,10 @@ Admin API controller, exposed through http via the api worker. """ import base64 +import IPy +import urllib +from nova import compute from nova import db from nova import exception from nova import log as logging @@ -70,6 +73,9 @@ class AdminController(object): def __str__(self): return 'AdminController' + def __init__(self): + self.compute_api = compute.API() + def describe_user(self, _context, name, **_kwargs): """Returns user data, including access and secret keys.""" return user_dict(manager.AuthManager().get_user(name)) @@ -210,10 +216,39 @@ class AdminController(object): """Returns status info for single node.""" return host_dict(db.host_get(name)) + def _provider_fw_rule_exists(context, rule): + for old_rule in db.provider_fw_rule_get_all(context): + for key in ('cidr', 'from_port', 'to_port', 'protocol'): + dupe = True + if rule[key] != old_rule[key]: + dupe = False + if dupe: + return dupe + return False + + def block_external_addresses(self, context, cidr): """Add provider-level firewall rules to block incoming traffic.""" - LOG.audit(_("Blocking access to all projects incoming from %s"), + LOG.audit(_("Blocking traffic to all projects incoming from %s"), cidr, context=context) - raise NotImplementedError(_("Awaiting implementation.")) - # TODO(todd): implement - # return {'status': 'OK', 'message': 'Disabled (number) IPs'} + rule = {'cidr': IPy.IP(urllib.unquote(cidr).decode())} + tcp_rule = rule.copy() + tcp_rule.update({"protocol": "TCP", "from_port": 1, "to_port": 65535}) + udp_rule = rule.copy() + udp_rule.update({"protocol": "UDP", "from_port": 1, "to_port": 65535}) + icmp_rule = rule.copy() + icmp_rule.update({"protocol": "ICMP", "from_port": -1, "to_port": -1}) + rules_added = 0 + if not self._provider_fw_rule_exists(context, tcp_rule): + db.provider_fw_rule_create(context, tcp_rule) + rules_added += 1 + if not self._provider_fw_rule_exists(context, udp_rule): + db.provider_fw_rule_create(context, udp_rule) + rules_added += 1 + if not self._provider_fw_rule_exists(context, icmp_rule): + db.provider_fw_rule_create(context, icmp_rule) + rules_added += 1 + if rules_added == 0: + raise exception.ApiError(_('Duplicate rule')) + self.compute_api.trigger_provider_fw_rules_refresh(context) + return {'status': 'OK', 'message': 'Disabled (number) IPs'} diff --git a/nova/compute/api.py b/nova/compute/api.py index a6b99c1cb81f..607a9ef25269 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -268,6 +268,15 @@ class API(base.Base): {"method": "refresh_security_group_members", "args": {"security_group_id": group_id}}) + def trigger_provider_fw_rules_refresh(self, context): + """Called when a rule is added to or removed from a security_group""" + + hosts = [x['host'] for x in db.service_get_all_compute_sorted(context)] + for host in hosts: + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "refresh_provider_fw_rules", "args": {}}) + def update(self, context, instance_id, **kwargs): """Updates the instance in the datastore. diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 6f09ce674a51..9c4a23d08cc3 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -169,6 +169,11 @@ class ComputeManager(manager.Manager): """This call passes straight through to the virtualization driver.""" return self.driver.refresh_security_group_members(security_group_id) + @exception.wrap_exception + def refresh_provider_fw_rules(self, context, **_kwargs): + """This call passes straight through to the virtualization driver.""" + return self.driver.refresh_security_group_rules() + @exception.wrap_exception def run_instance(self, context, instance_id, **_kwargs): """Launch a new instance with specified options.""" diff --git a/nova/db/api.py b/nova/db/api.py index f9d561587b19..a05c8159edec 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -813,6 +813,14 @@ def security_group_rule_destroy(context, security_group_rule_id): ################### +def provider_fw_rule_create(context, rule): + """Add a firewall rule at the provider level (all hosts & instances).""" + return IMPL.provider_fw_rule_create(context, rule) + + +################### + + def user_get(context, id): """Get user by id.""" return IMPL.user_get(context, id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b63b84bed595..4223aa0f75cd 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1687,6 +1687,18 @@ def security_group_rule_destroy(context, security_group_rule_id): ################### + +@require_admin_context +def provider_fw_rule_create(context, rule): + fw_rule_ref = models.ProviderFirewallRule() + fw_rule_ref.update(rule) + fw_rule_ref.save() + return fw_rule_ref + + +################### + + @require_admin_context def user_get(context, id, session=None): if not session: diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index bf5e48b04699..1d82cff18a98 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -394,6 +394,17 @@ class SecurityGroupIngressRule(BASE, NovaBase): group_id = Column(Integer, ForeignKey('security_groups.id')) +class ProviderFirewallRule(BASE, NovaBase): + """Represents a rule in a security group.""" + __tablename__ = 'provider_fw_rules' + id = Column(Integer, primary_key=True) + + protocol = Column(String(5)) # "tcp", "udp", or "icmp" + from_port = Column(Integer) + to_port = Column(Integer) + cidr = Column(String(255)) + + class KeyPair(BASE, NovaBase): """Represents a public key pair for ssh.""" __tablename__ = 'key_pairs' diff --git a/nova/virt/connection.py b/nova/virt/connection.py index 13181b7303aa..f5a97899758e 100644 --- a/nova/virt/connection.py +++ b/nova/virt/connection.py @@ -54,6 +54,7 @@ def get_connection(read_only=False): * fake * libvirt * xenapi + * hyperv """ # TODO(termie): maybe lazy load after initial check for permissions # TODO(termie): check whether we can be disconnected diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f38af5ed8ea1..fa5dc502ed48 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -848,6 +848,9 @@ class LibvirtConnection(object): def refresh_security_group_members(self, security_group_id): self.firewall_driver.refresh_security_group_members(security_group_id) + def refresh_provier_fw_rules(self): + self.firewall_driver.refresh_provider_fw_rules() + class FirewallDriver(object): def prepare_instance_filter(self, instance): @@ -884,6 +887,13 @@ class FirewallDriver(object): the security group.""" raise NotImplementedError() + def refresh_provider_fw_rules(self): + """Refresh common rules for all hosts/instances from data store. + + Gets called when a rule has been added to or removed from + the list of rules (via admin api).""" + raise NotImplementedError() + class NWFilterFirewall(FirewallDriver): """ From 46c1c554e7d98959a2b20597d6b0f2b0f648cdc9 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 19 Jan 2011 15:16:12 -0500 Subject: [PATCH 03/53] Whitespace (pep8) cleanups. --- nova/adminclient.py | 29 ++++++++++++++--------------- nova/api/ec2/admin.py | 1 - 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/nova/adminclient.py b/nova/adminclient.py index 90ad4d9dd90b..0cdb8c6fb4ab 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -190,20 +190,20 @@ class HostInfo(object): setattr(self, name, value) -class SimpleResponse(object): - def __init__(self, connection=None): - self.connection = connection - self.status = None +class SimpleResponse(object): + def __init__(self, connection=None): + self.connection = connection + self.status = None self.message = '' - - def __repr__(self): - return 'Status:%s' % self.status - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - setattr(self, name.lower(), str(value)) + + def __repr__(self): + return 'Status:%s' % self.status + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + setattr(self, name.lower(), str(value)) class NovaAdminClient(object): @@ -224,8 +224,7 @@ class NovaAdminClient(object): self.apiconn = boto.connect_ec2(aws_access_key_id=access_key, aws_secret_access_key=secret_key, is_secure=parts['is_secure'], - region=RegionInfo(None, - region, + region=RegionInfo(None, region, parts['ip']), port=parts['port'], path='/services/Admin', diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 1ae5f7094b93..3a8ed39ebb47 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -226,7 +226,6 @@ class AdminController(object): return dupe return False - def block_external_addresses(self, context, cidr): """Add provider-level firewall rules to block incoming traffic.""" LOG.audit(_("Blocking traffic to all projects incoming from %s"), From c58a8edb5c282f661d5be361ce68131516c741ba Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 19 Jan 2011 15:17:06 -0500 Subject: [PATCH 04/53] Implement provider-level firewall rules in nwfilter. --- nova/db/api.py | 5 ++++ nova/db/sqlalchemy/api.py | 11 +++++++++ nova/virt/libvirt_conn.py | 51 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/nova/db/api.py b/nova/db/api.py index a42e06e3bd03..f55bde9087c0 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -848,6 +848,11 @@ def provider_fw_rule_create(context, rule): return IMPL.provider_fw_rule_create(context, rule) +def provider_fw_rule_get_all(context): + """Get all provider-level firewall rules.""" + return IMPL.provider_fw_rule_get_all(context) + + ################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 95ed2d59ede7..7a3a6e89a55d 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1760,6 +1760,17 @@ def provider_fw_rule_create(context, rule): return fw_rule_ref +def provider_fw_rule_get_all(context): + session = get_session() + return session.query(models.ProviderFirewallRule).\ + filter_by(deleted=can_read_deleted(context)).\ + all() + fw_rule_ref = models.ProviderFirewallRule() + fw_rule_ref.update(rule) + fw_rule_ref.save() + return fw_rule_ref + + ################### diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index b189a5b31db3..68503ef68ab2 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1408,6 +1408,8 @@ class NWFilterFirewall(FirewallDriver): instance_secgroup_filter_children += [('nova-secgroup-%s' % security_group['id'])] + instance_filter_children += ['nova-provider-rules'] + self._define_filter( self._filter_container(instance_secgroup_filter_name, instance_secgroup_filter_children)) @@ -1422,6 +1424,18 @@ class NWFilterFirewall(FirewallDriver): return self._define_filter( self.security_group_to_nwfilter_xml(security_group_id)) + def refresh_provider_fw_rules(self): + """Update rules for all instances. + + This is part of the FirewallDriver API and is called when the + provider firewall rules change in the database. In the + `prepare_instance_filter` we add a reference to the + 'nova-provider-rules' filter for each instance's firewall, and + by changing that filter we update them all. + """ + xml = self.provider_fw_to_nwfilter_xml(self) + return self._define_filter(xml) + def security_group_to_nwfilter_xml(self, security_group_id): security_group = db.security_group_get(context.get_admin_context(), security_group_id) @@ -1460,6 +1474,43 @@ class NWFilterFirewall(FirewallDriver): xml += "chain='ipv4'>%s" % rule_xml return xml + def provider_fw_to_nwfilter_xml(self): + """Compose a filter of drop rules from specified cidrs.""" + rule_xml = "" + v6protocol = {'tcp': 'tcp-ipv6', 'udp': 'udp-ipv6', 'icmp': 'icmpv6'} + rules = db.provider_fw_rule_get_all(context.get_admin_context()) + for rule in rules: + rule_xml += "" + version = _get_ip_version(rule.cidr) + if(FLAGS.use_ipv6 and version == 6): + net, prefixlen = _get_net_and_prefixlen(rule.cidr) + rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \ + (v6protocol[rule.protocol], net, prefixlen) + else: + net, mask = _get_net_and_mask(rule.cidr) + rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \ + (rule.protocol, net, mask) + if rule.protocol in ['tcp', 'udp']: + rule_xml += "dstportstart='%s' dstportend='%s' " % \ + (rule.from_port, rule.to_port) + elif rule.protocol == 'icmp': + LOG.info('rule.protocol: %r, rule.from_port: %r, ' + 'rule.to_port: %r', rule.protocol, + rule.from_port, rule.to_port) + if rule.from_port != -1: + rule_xml += "type='%s' " % rule.from_port + if rule.to_port != -1: + rule_xml += "code='%s' " % rule.to_port + + rule_xml += '/>\n' + rule_xml += "\n" + xml = " Date: Thu, 20 Jan 2011 11:37:15 -0500 Subject: [PATCH 05/53] A couple of copypasta errors. --- nova/compute/manager.py | 2 +- nova/virt/libvirt_conn.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 9c4a23d08cc3..a9734f13ba0f 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -172,7 +172,7 @@ class ComputeManager(manager.Manager): @exception.wrap_exception def refresh_provider_fw_rules(self, context, **_kwargs): """This call passes straight through to the virtualization driver.""" - return self.driver.refresh_security_group_rules() + return self.driver.refresh_provider_fw_rules() @exception.wrap_exception def run_instance(self, context, instance_id, **_kwargs): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index dc5a9fc06683..4f3107c88a41 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -848,7 +848,7 @@ class LibvirtConnection(object): def refresh_security_group_members(self, security_group_id): self.firewall_driver.refresh_security_group_members(security_group_id) - def refresh_provier_fw_rules(self): + def refresh_provider_fw_rules(self): self.firewall_driver.refresh_provider_fw_rules() From f02c9e781bdddd609601da81b97a438b6d5b9781 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 21 Jan 2011 12:30:26 -0500 Subject: [PATCH 06/53] Add provider_fw_rules awareness to iptables firewall driver. --- nova/api/ec2/admin.py | 3 ++- nova/virt/libvirt_conn.py | 49 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 3a8ed39ebb47..c83f84b1584b 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -236,7 +236,8 @@ class AdminController(object): udp_rule = rule.copy() udp_rule.update({"protocol": "UDP", "from_port": 1, "to_port": 65535}) icmp_rule = rule.copy() - icmp_rule.update({"protocol": "ICMP", "from_port": -1, "to_port": -1}) + icmp_rule.update({"protocol": "ICMP", "from_port": -1, + "to_port": None}) rules_added = 0 if not self._provider_fw_rule_exists(context, tcp_rule): db.provider_fw_rule_create(context, tcp_rule) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 4f3107c88a41..38eddf748a98 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1290,7 +1290,51 @@ class IptablesFirewallDriver(FirewallDriver): our_rules = ['-A nova-fallback -j DROP'] our_chains += [':nova-local - [0:0]'] - our_rules += ['-A FORWARD -j nova-local'] + + our_chains += [':nova-provider - [0:0]'] + our_rules += ['-A FORWARD -j nova-provider'] + + rules = db.provider_fw_rule_get_all(ctxt) + for rule in rules: + logging.info('%r', rule) + version = _get_ip_version(rule.cidr) + if version != ip_version: + continue + protocol = rule.protocol + if version == 6 and rule.protocol == 'icmp': + protocol = 'icmpv6' + args = ['-A nova-provider -p', protocol, '-s', rule.cidr] + + if rule.protocol in ['udp', 'tcp']: + if rule.from_port == rule.to_port: + args += ['--dport', '%s' % (rule.from_port,)] + else: + args += ['-m', 'multiport', + '--dports', '%s:%s' % (rule.from_port, + rule.to_port)] + elif rule.protocol == 'icmp': + icmp_type = rule.from_port + icmp_code = rule.to_port + + if icmp_type == -1: + icmp_type_arg = None + else: + icmp_type_arg = '%s' % icmp_type + if not icmp_code == -1: + icmp_type_arg += '/%s' % icmp_code + + if icmp_type_arg: + if(ip_version == 4): + args += ['-m', 'icmp', '--icmp-type', + icmp_type_arg] + elif(ip_version == 6): + args += ['-m', 'icmp6', '--icmpv6-type', + icmp_type_arg] + + args += ['-j DROP'] + our_rules += [' '.join(args)] + + our_rules += ['-A nova-provider -j nova-local'] security_groups = {} # Add our chains @@ -1409,6 +1453,9 @@ class IptablesFirewallDriver(FirewallDriver): def refresh_security_group_rules(self, security_group): self.apply_ruleset() + def refresh_provider_fw_rules(self): + self.apply_ruleset() + def _security_group_chain_name(self, security_group_id): return 'nova-sg-%s' % (security_group_id,) From 4e3524c57f6fa0f917bdb30ec15c8d4633a307e5 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 25 Jan 2011 12:52:00 -0800 Subject: [PATCH 07/53] Updates for provider_fw_rules in admin api. --- nova/api/ec2/admin.py | 7 +++++-- nova/compute/api.py | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 4a34476d3b3e..0dabf20929f2 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -223,7 +223,7 @@ class AdminController(object): """Returns status info for single node.""" return host_dict(db.host_get(name)) - def _provider_fw_rule_exists(context, rule): + def _provider_fw_rule_exists(self, context, rule): for old_rule in db.provider_fw_rule_get_all(context): for key in ('cidr', 'from_port', 'to_port', 'protocol'): dupe = True @@ -237,7 +237,10 @@ class AdminController(object): """Add provider-level firewall rules to block incoming traffic.""" LOG.audit(_("Blocking traffic to all projects incoming from %s"), cidr, context=context) - rule = {'cidr': IPy.IP(urllib.unquote(cidr).decode())} + cidr = urllib.unquote(cidr).decode() + # raise if invalid + IPy.IP(cidr) + rule = {'cidr': cidr} tcp_rule = rule.copy() tcp_rule.update({"protocol": "TCP", "from_port": 1, "to_port": 65535}) udp_rule = rule.copy() diff --git a/nova/compute/api.py b/nova/compute/api.py index cb1a57a444e5..a8eed7aa53dd 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -277,7 +277,8 @@ class API(base.Base): def trigger_provider_fw_rules_refresh(self, context): """Called when a rule is added to or removed from a security_group""" - hosts = [x['host'] for x in db.service_get_all_compute_sorted(context)] + hosts = [x['host'] for (x,idx) + in db.service_get_all_compute_sorted(context)] for host in hosts: rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), From bbea3a093f3e9be5052a2e64b6d5d0b909ae33ee Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 25 Jan 2011 13:05:47 -0800 Subject: [PATCH 08/53] Migration for provider firewall rules. --- .../migrate_repo/versions/003_cactus.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py b/nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py new file mode 100644 index 000000000000..7e5c55811bad --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py @@ -0,0 +1,76 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import * +from migrate import * + +from nova import log as logging + + +meta = MetaData() + + +# Just for the ForeignKey and column creation to succeed, these are not the +# actual definitions of instances or services. +instances = Table('instances', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + + +services = Table('services', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + + +networks = Table('networks', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + + +# +# New Tables +# +provider_fw_rules = Table('provider_fw_rules', meta, + Column('created_at', DateTime(timezone=False)), + Column('updated_at', DateTime(timezone=False)), + Column('deleted_at', DateTime(timezone=False)), + Column('deleted', Boolean(create_constraint=True, name=None)), + Column('id', Integer(), primary_key=True, nullable=False), + Column('protocol', + String(length=5, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False)), + Column('from_port', Integer()), + Column('to_port', Integer()), + Column('cidr', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False)) + ) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + for table in (provider_fw_rules,): + try: + table.create() + except Exception: + logging.info(repr(table)) + logging.exception('Exception while creating table') + raise + From d6c6d8115b9dda07716d85fb1201cde0e907a9bd Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 26 Jan 2011 22:54:39 -0800 Subject: [PATCH 09/53] A couple of bugfixes. --- nova/adminclient.py | 3 ++- nova/api/ec2/admin.py | 3 ++- nova/virt/libvirt_conn.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/nova/adminclient.py b/nova/adminclient.py index 9b43995febe0..9652dcb9cc2b 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -230,7 +230,8 @@ class InstanceType(object): class SimpleResponse(object): - def __init__(self): + def __init__(self, connection=None): + self.connection = connection self.status = None self.message = '' diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 478032ce9b97..b019e8e8ba84 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -237,9 +237,10 @@ class AdminController(object): return host_dict(db.host_get(name)) def _provider_fw_rule_exists(self, context, rule): + # TODO(todd): we call this repeatedly, can we filter by protocol? for old_rule in db.provider_fw_rule_get_all(context): + dupe = True for key in ('cidr', 'from_port', 'to_port', 'protocol'): - dupe = True if rule[key] != old_rule[key]: dupe = False if dupe: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 453824d82e9c..5b5b329ed4b0 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1171,7 +1171,7 @@ class NWFilterFirewall(FirewallDriver): 'nova-provider-rules' filter for each instance's firewall, and by changing that filter we update them all. """ - xml = self.provider_fw_to_nwfilter_xml(self) + xml = self.provider_fw_to_nwfilter_xml() return self._define_filter(xml) def security_group_to_nwfilter_xml(self, security_group_id): From ece7d2fa493e901c2a826e42a86ca93bb0afaed4 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 26 Jan 2011 22:56:34 -0800 Subject: [PATCH 10/53] Apply lp:707675 to this branch to be able to test. --- nova/tests/test_virt.py | 7 ++++++- nova/virt/libvirt_conn.py | 29 +++++++++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 0b9b847a0f3d..1008f32ae17a 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -221,7 +221,12 @@ class IptablesFirewallTestCase(test.TestCase): self.project = self.manager.create_project('fake', 'fake', 'fake') self.context = context.RequestContext('fake', 'fake') self.network = utils.import_object(FLAGS.network_manager) - self.fw = libvirt_conn.IptablesFirewallDriver() + + class FakeLibvirtConnection(object): + pass + self.fake_libvirt_connection = FakeLibvirtConnection() + self.fw = libvirt_conn.IptablesFirewallDriver( + get_connection=lambda: self.fake_libvirt_connection) def tearDown(self): self.manager.delete_project(self.project) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 5b5b329ed4b0..cac6a4440c47 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -149,13 +149,8 @@ class LibvirtConnection(object): self._wrapped_conn = None self.read_only = read_only - self.nwfilter = NWFilterFirewall(self._get_connection) - - if not FLAGS.firewall_driver: - self.firewall_driver = self.nwfilter - self.nwfilter.handle_security_groups = True - else: - self.firewall_driver = utils.import_object(FLAGS.firewall_driver) + fw_class = utils.import_class(FLAGS.firewall_driver) + self.firewall_driver = fw_class(get_connection=self._get_connection) def init_host(self, host): # Adopt existing VM's running here @@ -409,7 +404,7 @@ class LibvirtConnection(object): instance['id'], power_state.NOSTATE, 'launching') - self.nwfilter.setup_basic_filtering(instance) + self.firewall_driver.setup_basic_filtering(instance) self.firewall_driver.prepare_instance_filter(instance) self._create_image(instance, xml) self._conn.createXML(xml, 0) @@ -915,6 +910,15 @@ class FirewallDriver(object): the list of rules (via admin api).""" raise NotImplementedError() + def setup_basic_filtering(self, instance): + """Create rules to block spoofing and allow dhcp. + + This gets called when spawning an instance, before + :method:`prepare_instance_filter`. + + """ + raise NotImplementedError() + class NWFilterFirewall(FirewallDriver): """ @@ -962,7 +966,7 @@ class NWFilterFirewall(FirewallDriver): """ - def __init__(self, get_connection): + def __init__(self, get_connection, **kwargs): self._libvirt_get_connection = get_connection self.static_filters_configured = False self.handle_security_groups = False @@ -1254,9 +1258,14 @@ class NWFilterFirewall(FirewallDriver): class IptablesFirewallDriver(FirewallDriver): - def __init__(self, execute=None): + def __init__(self, execute=None, **kwargs): self.execute = execute or utils.execute self.instances = {} + self.nwfilter = NWFilterFirewall(kwargs['get_connection']) + + def setup_basic_filtering(self, instance): + """Use NWFilter from libvirt for this.""" + return self.nwfilter.setup_basic_filtering(instance) def apply_instance_filter(self, instance): """No-op. Everything is done in prepare_instance_filter""" From d47886e16504cc92d0f9b33e02417229970d3efb Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 31 Jan 2011 16:02:29 -0500 Subject: [PATCH 11/53] Reorder insance rules for provider rules immediately after base, before secgroups. --- nova/virt/libvirt_conn.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 2f99a0bb1cce..ec6572d3f15f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1161,7 +1161,8 @@ class NWFilterFirewall(FirewallDriver): instance_filter_name = self._instance_filter_name(instance) instance_secgroup_filter_name = '%s-secgroup' % (instance_filter_name,) - instance_filter_children = [base_filter, instance_secgroup_filter_name] + instance_filter_children = [base_filter, 'nova-provider-rules', + instance_secgroup_filter_name] instance_secgroup_filter_children = ['nova-base-ipv4', 'nova-base-ipv6', 'nova-allow-dhcp-server'] @@ -1185,8 +1186,6 @@ class NWFilterFirewall(FirewallDriver): instance_secgroup_filter_children += [('nova-secgroup-%s' % security_group['id'])] - instance_filter_children += ['nova-provider-rules'] - self._define_filter( self._filter_container(instance_secgroup_filter_name, instance_secgroup_filter_children)) From d552158b19bf1652da795e1681c9dc904bdc425b Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 1 Feb 2011 12:32:58 -0500 Subject: [PATCH 12/53] Add and document the provider_fw method in virt/FakeConnection. --- nova/virt/fake.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 161445b86e1f..b16d536340b9 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -359,6 +359,22 @@ class FakeConnection(object): """ return True + def refresh_provider_fw_rules(self): + """This triggers a firewall update based on database changes. + + When this is called, rules have either been added or removed from the + datastore. You can retrieve rules with + :method:`nova.db.api.provider_fw_rule_get_all`. + + Provider rules take precedence over security group rules. If an IP + would be allowed by a security group ingress rule, but blocked by + a provider rule, then packets from the IP are dropped. This includes + intra-project traffic in the case of the allow_project_net_traffic + flag for the libvirt-derived classes. + + """ + pass + class FakeInstance(object): From 5da75737ddfb876fd397b71986af42a5f8d0d04c Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 16 Feb 2011 12:49:04 -0500 Subject: [PATCH 13/53] Merge bfschott's patch for migations in. --- nova/db/sqlalchemy/migration.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/migration.py b/nova/db/sqlalchemy/migration.py index 2a13c54660ae..f11f9f67e0a8 100644 --- a/nova/db/sqlalchemy/migration.py +++ b/nova/db/sqlalchemy/migration.py @@ -17,12 +17,21 @@ # under the License. import os +import sys from nova import flags import sqlalchemy from migrate.versioning import api as versioning_api -from migrate.versioning import exceptions as versioning_exceptions +try: + from migrate.versioning import exceptions as versioning_exceptions +except ImportError: + try: + # python-migration changed location of exceptions after 1.6.3 + # See LP Bug #717467 + from migrate import exceptions as versioning_exceptions + except ImportError: + sys.exit(_("python-migrate is not installed. Exiting.")) FLAGS = flags.FLAGS From dc887ab39641039817cdddce062bd398e69d07e5 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 5 Apr 2011 15:17:17 -0400 Subject: [PATCH 14/53] Remove file leftover from conflict. --- nova/adminclient.py.THIS | 451 --------------------------------------- 1 file changed, 451 deletions(-) delete mode 100644 nova/adminclient.py.THIS diff --git a/nova/adminclient.py.THIS b/nova/adminclient.py.THIS deleted file mode 100644 index c96d6e1fe2e2..000000000000 --- a/nova/adminclient.py.THIS +++ /dev/null @@ -1,451 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Nova User API client library. -""" - -import base64 -import boto -import boto.exception -import httplib - -from boto.ec2.regioninfo import RegionInfo - - -DEFAULT_CLC_URL = 'http://127.0.0.1:8773' -DEFAULT_REGION = 'nova' - - -class UserInfo(object): - """ - Information about a Nova user, as parsed through SAX. - - **Fields Include** - - * username - * accesskey - * secretkey - * file (optional) containing zip of X509 cert & rc file - - """ - - def __init__(self, connection=None, username=None, endpoint=None): - self.connection = connection - self.username = username - self.endpoint = endpoint - - def __repr__(self): - return 'UserInfo:%s' % self.username - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'username': - self.username = str(value) - elif name == 'file': - self.file = base64.b64decode(str(value)) - elif name == 'accesskey': - self.accesskey = str(value) - elif name == 'secretkey': - self.secretkey = str(value) - - -class UserRole(object): - """ - Information about a Nova user's role, as parsed through SAX. - - **Fields include** - - * role - - """ - - def __init__(self, connection=None): - self.connection = connection - self.role = None - - def __repr__(self): - return 'UserRole:%s' % self.role - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'role': - self.role = value - else: - setattr(self, name, str(value)) - - -class ProjectInfo(object): - """ - Information about a Nova project, as parsed through SAX. - - **Fields include** - - * projectname - * description - * projectManagerId - * memberIds - - """ - - def __init__(self, connection=None): - self.connection = connection - self.projectname = None - self.description = None - self.projectManagerId = None - self.memberIds = [] - - def __repr__(self): - return 'ProjectInfo:%s' % self.projectname - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'projectname': - self.projectname = value - elif name == 'description': - self.description = value - elif name == 'projectManagerId': - self.projectManagerId = value - elif name == 'memberId': - self.memberIds.append(value) - else: - setattr(self, name, str(value)) - - -class ProjectMember(object): - """ - Information about a Nova project member, as parsed through SAX. - - **Fields include** - - * memberId - - """ - - def __init__(self, connection=None): - self.connection = connection - self.memberId = None - - def __repr__(self): - return 'ProjectMember:%s' % self.memberId - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'member': - self.memberId = value - else: - setattr(self, name, str(value)) - - -class HostInfo(object): - """ - Information about a Nova Host, as parsed through SAX. - - **Fields Include** - - * Disk stats - * Running Instances - * Memory stats - * CPU stats - * Network address info - * Firewall info - * Bridge and devices - - """ - - def __init__(self, connection=None): - self.connection = connection - self.hostname = None - - def __repr__(self): - return 'Host:%s' % self.hostname - - # this is needed by the sax parser, so ignore the ugly name - def startElement(self, name, attrs, connection): - return None - - # this is needed by the sax parser, so ignore the ugly name - def endElement(self, name, value, connection): - setattr(self, name, value) - - -class InstanceType(object): - """ - Information about a Nova instance type, as parsed through SAX. - - **Fields include** - - * name - * vcpus - * disk_gb - * memory_mb - * flavor_id - - """ - - def __init__(self, connection=None): - self.connection = connection - self.name = None - self.vcpus = None - self.disk_gb = None - self.memory_mb = None - self.flavor_id = None - - def __repr__(self): - return 'InstanceType:%s' % self.name - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == "memoryMb": - self.memory_mb = str(value) - elif name == "flavorId": - self.flavor_id = str(value) - elif name == "diskGb": - self.disk_gb = str(value) - else: - setattr(self, name, str(value)) - - -class SimpleResponse(object): - def __init__(self, connection=None): - self.connection = connection - self.status = None - self.message = '' - - def __repr__(self): - return 'Status:%s' % self.status - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - setattr(self, name.lower(), str(value)) - - -class NovaAdminClient(object): - - def __init__( - self, - clc_url=DEFAULT_CLC_URL, - region=DEFAULT_REGION, - access_key=None, - secret_key=None, - **kwargs): - parts = self.split_clc_url(clc_url) - - self.clc_url = clc_url - self.region = region - self.access = access_key - self.secret = secret_key - self.apiconn = boto.connect_ec2(aws_access_key_id=access_key, - aws_secret_access_key=secret_key, - is_secure=parts['is_secure'], - region=RegionInfo(None, region, - parts['ip']), - port=parts['port'], - path='/services/Admin', - **kwargs) - self.apiconn.APIVersion = 'nova' - - def connection_for(self, username, project, clc_url=None, region=None, - **kwargs): - """Returns a boto ec2 connection for the given username.""" - if not clc_url: - clc_url = self.clc_url - if not region: - region = self.region - parts = self.split_clc_url(clc_url) - user = self.get_user(username) - access_key = '%s:%s' % (user.accesskey, project) - return boto.connect_ec2(aws_access_key_id=access_key, - aws_secret_access_key=user.secretkey, - is_secure=parts['is_secure'], - region=RegionInfo(None, - self.region, - parts['ip']), - port=parts['port'], - path='/services/Cloud', - **kwargs) - - def split_clc_url(self, clc_url): - """Splits a cloud controller endpoint url.""" - parts = httplib.urlsplit(clc_url) - is_secure = parts.scheme == 'https' - ip, port = parts.netloc.split(':') - return {'ip': ip, 'port': int(port), 'is_secure': is_secure} - - def get_users(self): - """Grabs the list of all users.""" - return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)]) - - def get_user(self, name): - """Grab a single user by name.""" - try: - return self.apiconn.get_object('DescribeUser', - {'Name': name}, - UserInfo) - except boto.exception.BotoServerError, e: - if e.status == 400 and e.error_code == 'NotFound': - return None - raise - - def has_user(self, username): - """Determine if user exists.""" - return self.get_user(username) != None - - def create_user(self, username): - """Creates a new user, returning the userinfo object with - access/secret.""" - return self.apiconn.get_object('RegisterUser', {'Name': username}, - UserInfo) - - def delete_user(self, username): - """Deletes a user.""" - return self.apiconn.get_object('DeregisterUser', {'Name': username}, - UserInfo) - - def get_roles(self, project_roles=True): - """Returns a list of available roles.""" - return self.apiconn.get_list('DescribeRoles', - {'ProjectRoles': project_roles}, - [('item', UserRole)]) - - def get_user_roles(self, user, project=None): - """Returns a list of roles for the given user. - - Omitting project will return any global roles that the user has. - Specifying project will return only project specific roles. - - """ - params = {'User': user} - if project: - params['Project'] = project - return self.apiconn.get_list('DescribeUserRoles', - params, - [('item', UserRole)]) - - def add_user_role(self, user, role, project=None): - """Add a role to a user either globally or for a specific project.""" - return self.modify_user_role(user, role, project=project, - operation='add') - - def remove_user_role(self, user, role, project=None): - """Remove a role from a user either globally or for a specific - project.""" - return self.modify_user_role(user, role, project=project, - operation='remove') - - def modify_user_role(self, user, role, project=None, operation='add', - **kwargs): - """Add or remove a role for a user and project.""" - params = {'User': user, - 'Role': role, - 'Project': project, - 'Operation': operation} - return self.apiconn.get_status('ModifyUserRole', params) - - def get_projects(self, user=None): - """Returns a list of all projects.""" - if user: - params = {'User': user} - else: - params = {} - return self.apiconn.get_list('DescribeProjects', - params, - [('item', ProjectInfo)]) - - def get_project(self, name): - """Returns a single project with the specified name.""" - project = self.apiconn.get_object('DescribeProject', - {'Name': name}, - ProjectInfo) - - if project.projectname != None: - return project - - def create_project(self, projectname, manager_user, description=None, - member_users=None): - """Creates a new project.""" - params = {'Name': projectname, - 'ManagerUser': manager_user, - 'Description': description, - 'MemberUsers': member_users} - return self.apiconn.get_object('RegisterProject', params, ProjectInfo) - - def modify_project(self, projectname, manager_user=None, description=None): - """Modifies an existing project.""" - params = {'Name': projectname, - 'ManagerUser': manager_user, - 'Description': description} - return self.apiconn.get_status('ModifyProject', params) - - def delete_project(self, projectname): - """Permanently deletes the specified project.""" - return self.apiconn.get_object('DeregisterProject', - {'Name': projectname}, - ProjectInfo) - - def get_project_members(self, name): - """Returns a list of members of a project.""" - return self.apiconn.get_list('DescribeProjectMembers', - {'Name': name}, - [('item', ProjectMember)]) - - def add_project_member(self, user, project): - """Adds a user to a project.""" - return self.modify_project_member(user, project, operation='add') - - def remove_project_member(self, user, project): - """Removes a user from a project.""" - return self.modify_project_member(user, project, operation='remove') - - def modify_project_member(self, user, project, operation='add'): - """Adds or removes a user from a project.""" - params = {'User': user, - 'Project': project, - 'Operation': operation} - return self.apiconn.get_status('ModifyProjectMember', params) - - def get_zip(self, user, project): - """Returns the content of a zip file containing novarc and access - credentials.""" - params = {'Name': user, 'Project': project} - zip = self.apiconn.get_object('GenerateX509ForUser', params, UserInfo) - return zip.file - - def get_hosts(self): - return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)]) - - def get_instance_types(self): - """Grabs the list of all users.""" - return self.apiconn.get_list('DescribeInstanceTypes', {}, - [('item', InstanceType)]) - - def block_ips(self, cidr): - """Block incoming traffic from specified hosts.""" - return self.apiconn.get_object('BlockExternalAddresses', - {'Cidr': cidr}, SimpleResponse) From 29b7a087efc64965e079733fe62e552fac70d13a Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 5 Apr 2011 15:17:37 -0400 Subject: [PATCH 15/53] Fix a giant batch of copypasta. --- nova/compute/manager.py | 1 + nova/virt/libvirt_conn.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index b0c301925064..916c1db5e9fe 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -195,6 +195,7 @@ class ComputeManager(manager.SchedulerDependentManager): """This call passes straight through to the virtualization driver.""" return self.driver.refresh_provider_fw_rules() + @exception.wrap_exception def run_instance(self, context, instance_id, **kwargs): """Launch a new instance with specified options.""" context = context.elevated() diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f3d73103b7ba..c5ada729a8d7 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1790,6 +1790,11 @@ class NWFilterFirewall(FirewallDriver): else: base_filter = 'nova-base' + ctxt = context.get_admin_context() + + instance_secgroup_filter_name = \ + '%s-secgroup' % (self._instance_filter_name(instance)) + instance_secgroup_filter_children = ['nova-base-ipv4', 'nova-base-ipv6', 'nova-allow-dhcp-server'] From cd4748abfdc5014aac1d867c2ede261060375e2e Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 5 Apr 2011 19:31:12 -0400 Subject: [PATCH 16/53] Don't double-apply provider fw rules in NWFilter and Iptables. Don't create provider fw rules for each instance, use a chain and jump to it. Fix docstrings. --- nova/network/linux_net.py | 7 ++ nova/virt/connection.py | 1 - nova/virt/libvirt_conn.py | 184 ++++++++++++++++++++++---------------- 3 files changed, 112 insertions(+), 80 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index d11d21daded1..322524a411f3 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -185,6 +185,13 @@ class IptablesTable(object): {'chain': chain, 'rule': rule, 'top': top, 'wrap': wrap}) + def empty_chain(self, chain, wrap=True): + """Remove all rules from a chain.""" + chained_rules = [rule for rule in self.rules + if rule.chain == chain and rule.wrap == wrap] + for rule in chained_rules: + self.rules.remove(rule) + class IptablesManager(object): """Wrapper for iptables diff --git a/nova/virt/connection.py b/nova/virt/connection.py index 875d558a0040..99a8849f1b35 100644 --- a/nova/virt/connection.py +++ b/nova/virt/connection.py @@ -57,7 +57,6 @@ def get_connection(read_only=False): * fake * libvirt * xenapi - * hyperv """ # TODO(termie): maybe lazy load after initial check for permissions # TODO(termie): check whether we can be disconnected diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c5ada729a8d7..9bc7ca05a19f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1539,7 +1539,9 @@ class FirewallDriver(object): """Refresh common rules for all hosts/instances from data store. Gets called when a rule has been added to or removed from - the list of rules (via admin api).""" + the list of rules (via admin api). + + """ raise NotImplementedError() def setup_basic_filtering(self, instance, network_info=None): @@ -1601,7 +1603,6 @@ class NWFilterFirewall(FirewallDriver): def __init__(self, get_connection, **kwargs): self._libvirt_get_connection = get_connection self.static_filters_configured = False - self.intermediate_filters_configured = False self.handle_security_groups = False def apply_instance_filter(self, instance): @@ -1658,9 +1659,6 @@ class NWFilterFirewall(FirewallDriver): logging.info('ensuring static filters') self._ensure_static_filters() - logging.info('ensuring intermediate filters') - self._ensure_intermediate_filters() - for (network, mapping) in network_info: nic_id = mapping['mac'].replace(':', '') instance_filter_name = self._instance_filter_name(instance, nic_id) @@ -1695,20 +1693,6 @@ class NWFilterFirewall(FirewallDriver): self.static_filters_configured = True - def _ensure_intermediate_filters(self): - """Intermediate filters are filters that are configurable nova-wide. - - Unlike static filters, they must be set up and maintainted based - on the network topology of nova. They are still required to be setup - before any instance can be launched. - - """ - - if self.intermediate_filters_configured: - return - self.refresh_provider_fw_rules() - self.intermediate_filters_configured = True - def _filter_container(self, name, filters): xml = '''%s''' % ( name, @@ -1778,10 +1762,11 @@ class NWFilterFirewall(FirewallDriver): pass def prepare_instance_filter(self, instance, network_info=None): - """ - Creates an NWFilter for the given instance. In the process, - it makes sure the filters for the security groups as well as - the base filter are all in place. + """Creates an NWFilter for the given instance. + + In the process, it makes sure the filters for the provider blocks, + security groups, and base filter are all in place. + """ if not network_info: network_info = _get_network_info(instance) @@ -1790,6 +1775,8 @@ class NWFilterFirewall(FirewallDriver): else: base_filter = 'nova-base' + self.refresh_provider_fw_rules() + ctxt = context.get_admin_context() instance_secgroup_filter_name = \ @@ -1847,6 +1834,7 @@ class NWFilterFirewall(FirewallDriver): `prepare_instance_filter` we add a reference to the 'nova-provider-rules' filter for each instance's firewall, and by changing that filter we update them all. + """ xml = self.provider_fw_to_nwfilter_xml() return self._define_filter(xml) @@ -1890,7 +1878,7 @@ class NWFilterFirewall(FirewallDriver): return xml def provider_fw_to_nwfilter_xml(self): - """Compose a filter of drop rules from specified cidrs.""" + """Compose a filter of drop rules from specified cidrs.""" rule_xml = "" v6protocol = {'tcp': 'tcp-ipv6', 'udp': 'udp-ipv6', 'icmp': 'icmpv6'} rules = db.provider_fw_rule_get_all(context.get_admin_context()) @@ -1938,6 +1926,7 @@ class IptablesFirewallDriver(FirewallDriver): self.iptables = linux_net.iptables_manager self.instances = {} self.nwfilter = NWFilterFirewall(kwargs['get_connection']) + self.basicly_filtered = False self.iptables.ipv4['filter'].add_chain('sg-fallback') self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP') @@ -1945,10 +1934,13 @@ class IptablesFirewallDriver(FirewallDriver): self.iptables.ipv6['filter'].add_rule('sg-fallback', '-j DROP') def setup_basic_filtering(self, instance, network_info=None): - """Use NWFilter from libvirt for this.""" + """Set up provider rules and basic NWFilter.""" if not network_info: network_info = _get_network_info(instance) - return self.nwfilter.setup_basic_filtering(instance, network_info) + self.nwfilter.setup_basic_filtering(instance, network_info) + if not self.basicly_filtered: + self.refresh_provider_fw_rules() + self.basicly_filtered = True def apply_instance_filter(self, instance): """No-op. Everything is done in prepare_instance_filter""" @@ -1996,6 +1988,8 @@ class IptablesFirewallDriver(FirewallDriver): chain_name)) ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info) + for rule in ipv4_rules: + self.iptables.ipv4['filter'].add_rule(chain_name, rule) for rule in ipv4_rules: self.iptables.ipv4['filter'].add_rule(chain_name, rule) @@ -2023,6 +2017,10 @@ class IptablesFirewallDriver(FirewallDriver): ipv4_rules += ['-m state --state ' 'INVALID -j DROP'] ipv6_rules += ['-m state --state ' 'INVALID -j DROP'] + # Pass through provider-wide drops + ipv4_rules += ['-j $provider'] + ipv6_rules += ['-j $provider'] + # Allow established connections ipv4_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT'] ipv6_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT'] @@ -2058,57 +2056,6 @@ class IptablesFirewallDriver(FirewallDriver): for cidrv6 in cidrv6s: ipv6_rules.append('-s %s -j ACCEPT' % (cidrv6,)) - # block provider-identified sources - rules = db.provider_fw_rule_get_all(ctxt) - for rule in rules: - logging.info('%r', rule) - - if not rule.cidr: - # Eventually, a mechanism to grant access for security - # groups will turn up here. It'll use ipsets. - continue - - version = _get_ip_version(rule.cidr) - if version == 4: - fw_rules = ipv4_rules - else: - fw_rules = ipv6_rules - - protocol = rule.protocol - if version == 6 and rule.protocol == 'icmp': - protocol = 'icmpv6' - - args = ['-A', 'nova-provider', '-p', protocol, '-s', rule.cidr] - - if rule.protocol in ['udp', 'tcp']: - if rule.from_port == rule.to_port: - args += ['--dport', '%s' % (rule.from_port,)] - else: - args += ['-m', 'multiport', - '--dports', '%s:%s' % (rule.from_port, - rule.to_port)] - elif rule.protocol == 'icmp': - icmp_type = rule.from_port - icmp_code = rule.to_port - - if icmp_type == -1: - icmp_type_arg = None - else: - icmp_type_arg = '%s' % icmp_type - if not icmp_code == -1: - icmp_type_arg += '/%s' % icmp_code - - if icmp_type_arg: - if version == 4: - args += ['-m', 'icmp', '--icmp-type', - icmp_type_arg] - elif version == 6: - args += ['-m', 'icmp6', '--icmpv6-type', - icmp_type_arg] - - args += ['-j DROP'] - fw_rules += [' '.join(args)] - security_groups = db.security_group_get_by_instance(ctxt, instance['id']) @@ -2118,7 +2065,7 @@ class IptablesFirewallDriver(FirewallDriver): security_group['id']) for rule in rules: - LOG.debug('%r', rule) + LOG.debug(_('Adding security group rule: %r'), rule) if not rule.cidr: # Eventually, a mechanism to grant access for security @@ -2185,7 +2132,86 @@ class IptablesFirewallDriver(FirewallDriver): self.add_filters_for_instance(instance) def refresh_provider_fw_rules(self): - self.apply_ruleset() + """See class:FirewallDriver: docs.""" + self._do_refresh_provider_fw_rules() + self.iptables.apply() + + @utils.synchronized('iptables', external=True) + def _do_refresh_provider_fw_rules(self): + """Internal, synchronized version of refresh_provider_fw_rules.""" + self.purge_provider_fw_rules(self) + self.build_provider_fw_rules(self) + + def _purge_provider_fw_rules(self): + """Remove all rules from the $provider chains.""" + self.iptables.ipv4['filter'].empty_chain('$provider') + if FLAGS.use_ipv6: + self.iptables.ipv6['filter'].empty_chain('$provider') + + def _build_provider_fw_rules(self): + """Create all rules for the provider IP DROPs.""" + ipv4_rules, ipv6_rules = self._provider_rules() + for rule in ipv4_rules: + self.iptables.ipv4['filter'].add_rule('$provider', rule) + + if FLAGS.use_ipv6: + for rule in ipv6_rules: + self.iptables.ipv6['filter'].add_rule('$provider', rule) + + def _provider_rules(self): + """Generate a list of rules from provider for IP4 & IP6.""" + ctxt = context.get_admin_context() + ipv4_rules = [] + ipv6_rules = [] + rules = db.provider_fw_rule_get_all(ctxt) + for rule in rules: + LOG.debug(_('Adding prvider rule: %r'), rule) + + if not rule.cidr: + # Eventually, a mechanism to grant access for security + # groups will turn up here. It'll use ipsets. + continue + + version = _get_ip_version(rule.cidr) + if version == 4: + fw_rules = ipv4_rules + else: + fw_rules = ipv6_rules + + protocol = rule.protocol + if version == 6 and rule.protocol == 'icmp': + protocol = 'icmpv6' + + args = ['-A', '$provider', '-p', protocol, '-s', rule.cidr] + + if rule.protocol in ['udp', 'tcp']: + if rule.from_port == rule.to_port: + args += ['--dport', '%s' % (rule.from_port,)] + else: + args += ['-m', 'multiport', + '--dports', '%s:%s' % (rule.from_port, + rule.to_port)] + elif rule.protocol == 'icmp': + icmp_type = rule.from_port + icmp_code = rule.to_port + + if icmp_type == -1: + icmp_type_arg = None + else: + icmp_type_arg = '%s' % icmp_type + if not icmp_code == -1: + icmp_type_arg += '/%s' % icmp_code + + if icmp_type_arg: + if version == 4: + args += ['-m', 'icmp', '--icmp-type', + icmp_type_arg] + elif version == 6: + args += ['-m', 'icmp6', '--icmpv6-type', + icmp_type_arg] + args += ['-j DROP'] + fw_rules += [' '.join(args)] + return ipv4_rules, ipv6_rules def _security_group_chain_name(self, security_group_id): return 'nova-sg-%s' % (security_group_id,) From 2b7da3f2e9fa45f9bfca03bb6bcb713dcb6c58fe Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 5 Apr 2011 20:03:29 -0400 Subject: [PATCH 17/53] Testing for iptables manager changes. --- ...cactus.py => 014_add_provider_fw_rules.py} | 0 nova/tests/test_network.py | 30 +++++++++++++++++++ 2 files changed, 30 insertions(+) rename nova/db/sqlalchemy/migrate_repo/versions/{003_cactus.py => 014_add_provider_fw_rules.py} (100%) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py similarity index 100% rename from nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py rename to nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 77f6aaff35c1..a67c846dd6c0 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -164,3 +164,33 @@ class IptablesManagerTestCase(test.TestCase): self.assertTrue('-A %s -j run_tests.py-%s' \ % (chain, chain) in new_lines, "Built-in chain %s not wrapped" % (chain,)) + + def test_will_empty_chain(self): + self.manager.ipv4['filter'].add_chain('test-chain') + self.manager.ipv4['filter'].add_rule('test-chain', '-j DROP') + old_count = len(self.manager.ipv4['filter'].rules) + self.manager.ipv4['filter'].empty_chain('test-chain') + self.assertEqual(old_count - 1, len(self.manager.ipv4['filter'].rules)) + + def test_will_empty_unwrapped_chain(self): + self.manager.ipv4['filter'].add_chain('test-chain', wrap=False) + self.manager.ipv4['filter'].add_rule('test-chain', '-j DROP', + wrap=False) + old_count = len(self.manager.ipv4['filter'].rules) + self.manager.ipv4['filter'].empty_chain('test-chain', wrap=False) + self.assertEqual(old_count - 1, len(self.manager.ipv4['filter'].rules)) + + def test_will_not_empty_wrapped_when_unwrapped(self): + self.manager.ipv4['filter'].add_chain('test-chain') + self.manager.ipv4['filter'].add_rule('test-chain', '-j DROP') + old_count = len(self.manager.ipv4['filter'].rules) + self.manager.ipv4['filter'].empty_chain('test-chain', wrap=False) + self.assertEqual(old_count, len(self.manager.ipv4['filter'].rules)) + + def test_will_not_empty_unwrapped_when_wrapped(self): + self.manager.ipv4['filter'].add_chain('test-chain', wrap=False) + self.manager.ipv4['filter'].add_rule('test-chain', '-j DROP', + wrap=False) + old_count = len(self.manager.ipv4['filter'].rules) + self.manager.ipv4['filter'].empty_chain('test-chain') + self.assertEqual(old_count, len(self.manager.ipv4['filter'].rules)) From 26d2a6ca8939156e8957e31dd17906070283ff24 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 5 Apr 2011 20:07:46 -0400 Subject: [PATCH 18/53] Undo use of $ in chain name where not needed. --- nova/virt/libvirt_conn.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9bc7ca05a19f..0d92e2e7002d 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -2143,20 +2143,20 @@ class IptablesFirewallDriver(FirewallDriver): self.build_provider_fw_rules(self) def _purge_provider_fw_rules(self): - """Remove all rules from the $provider chains.""" - self.iptables.ipv4['filter'].empty_chain('$provider') + """Remove all rules from the provider chains.""" + self.iptables.ipv4['filter'].empty_chain('provider') if FLAGS.use_ipv6: - self.iptables.ipv6['filter'].empty_chain('$provider') + self.iptables.ipv6['filter'].empty_chain('provider') def _build_provider_fw_rules(self): """Create all rules for the provider IP DROPs.""" ipv4_rules, ipv6_rules = self._provider_rules() for rule in ipv4_rules: - self.iptables.ipv4['filter'].add_rule('$provider', rule) + self.iptables.ipv4['filter'].add_rule('provider', rule) if FLAGS.use_ipv6: for rule in ipv6_rules: - self.iptables.ipv6['filter'].add_rule('$provider', rule) + self.iptables.ipv6['filter'].add_rule('provider', rule) def _provider_rules(self): """Generate a list of rules from provider for IP4 & IP6.""" @@ -2182,7 +2182,7 @@ class IptablesFirewallDriver(FirewallDriver): if version == 6 and rule.protocol == 'icmp': protocol = 'icmpv6' - args = ['-A', '$provider', '-p', protocol, '-s', rule.cidr] + args = ['-p', protocol, '-s', rule.cidr] if rule.protocol in ['udp', 'tcp']: if rule.from_port == rule.to_port: From 2b79fa82872c55368167fc7433cb28a2369f5191 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 7 Apr 2011 01:42:49 -0400 Subject: [PATCH 19/53] test provider fw rules at the virt/ipteables layer. lowercase protocol names in admin api to match what the firewall driver expects. add provider fw rule chain in iptables6 as well. fix a couple of small typos and copy-paste errors. --- nova/api/ec2/admin.py | 6 ++-- nova/tests/test_virt.py | 65 ++++++++++++++++++++++++++++++++++++++- nova/virt/libvirt_conn.py | 23 ++++++++------ 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 0b27854ef22d..c0c2bcd0df0b 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -356,11 +356,11 @@ class AdminController(object): IPy.IP(cidr) rule = {'cidr': cidr} tcp_rule = rule.copy() - tcp_rule.update({"protocol": "TCP", "from_port": 1, "to_port": 65535}) + tcp_rule.update({"protocol": "tcp", "from_port": 1, "to_port": 65535}) udp_rule = rule.copy() - udp_rule.update({"protocol": "UDP", "from_port": 1, "to_port": 65535}) + udp_rule.update({"protocol": "udp", "from_port": 1, "to_port": 65535}) icmp_rule = rule.copy() - icmp_rule.update({"protocol": "ICMP", "from_port": -1, + icmp_rule.update({"protocol": "icmp", "from_port": -1, "to_port": None}) rules_added = 0 if not self._provider_fw_rule_exists(context, tcp_rule): diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 958c8e3e2299..34ecd09c6001 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -566,7 +566,9 @@ class IptablesFirewallTestCase(test.TestCase): self.network = utils.import_object(FLAGS.network_manager) class FakeLibvirtConnection(object): - pass + def nwfilterDefineXML(*args, **kwargs): + """setup_basic_rules in nwfilter calls this.""" + pass self.fake_libvirt_connection = FakeLibvirtConnection() self.fw = libvirt_conn.IptablesFirewallDriver( get_connection=lambda: self.fake_libvirt_connection) @@ -728,6 +730,67 @@ class IptablesFirewallTestCase(test.TestCase): "TCP port 80/81 acceptance rule wasn't added") db.instance_destroy(admin_ctxt, instance_ref['id']) + def test_provider_firewall_rules(self): + # keep from changing state of actual firewall + #def fake_function(*args, **kwargs): + # pass + #self.fw.iptables.apply = fake_function + #self.fw.nwfilter.setup_basic_filtering = fake_function + + # setup basic instance data + instance_ref = db.instance_create(self.context, + {'user_id': 'fake', + 'project_id': 'fake', + 'mac_address': '56:12:12:12:12:12'}) + ip = '10.11.12.13' + network_ref = db.project_get_network(self.context, 'fake') + admin_ctxt = context.get_admin_context() + fixed_ip = {'address': ip, 'network_id': network_ref['id']} + db.fixed_ip_create(admin_ctxt, fixed_ip) + db.fixed_ip_update(admin_ctxt, ip, {'allocated': True, + 'instance_id': instance_ref['id']}) + # FRAGILE: peeks at how the firewall names chains + chain_name = 'inst-%s' % instance_ref['id'] + + # create a firewall via setup_basic_filtering like libvirt_conn.spawn + # should have a chain with 0 rules + self.fw.setup_basic_filtering(instance_ref, network_info=None) + self.assertTrue('provider' in self.fw.iptables.ipv4['filter'].chains) + rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules + if rule.chain == 'provider'] + self.assertEqual(0, len(rules)) + + # add a rule and send the update message, check for 1 rule + provider_fw0 = db.provider_fw_rule_create(admin_ctxt, + {'protocol': 'tcp', + 'cidr': '10.99.99.99/32', + 'from_port': 1, + 'to_port': 65535}) + self.fw.refresh_provider_fw_rules() + rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules + if rule.chain == 'provider'] + self.assertEqual(1, len(rules)) + + # Add another, refresh, and make sure number of rules goes to two + provider_fw1 = db.provider_fw_rule_create(admin_ctxt, + {'protocol': 'udp', + 'cidr': '10.99.99.99/32', + 'from_port': 1, + 'to_port': 65535}) + self.fw.refresh_provider_fw_rules() + rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules + if rule.chain == 'provider'] + self.assertEqual(2, len(rules)) + + # create the instance filter and make sure it has a jump rule + self.fw.prepare_instance_filter(instance_ref, network_info=None) + self.fw.apply_instance_filter(instance_ref) + inst_rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules + if rule.chain == chain_name] + jump_rules = [rule for rule in inst_rules if '-j' in rule.rule] + prov_rules = [rule for rule in jump_rules if 'provider' in rule.rule] + self.assertEqual(1, len(prov_rules)) + class NWFilterTestCase(test.TestCase): def setUp(self): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 0d92e2e7002d..38ba21521be6 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1939,6 +1939,7 @@ class IptablesFirewallDriver(FirewallDriver): network_info = _get_network_info(instance) self.nwfilter.setup_basic_filtering(instance, network_info) if not self.basicly_filtered: + LOG.debug("Setup Basic Filtering") self.refresh_provider_fw_rules() self.basicly_filtered = True @@ -1967,6 +1968,7 @@ class IptablesFirewallDriver(FirewallDriver): chain_name = self._instance_chain_name(instance) self.iptables.ipv4['filter'].add_chain(chain_name) + self.iptables.ipv4['filter'].empty_chain(chain_name) ips_v4 = [ip['ip'] for (_, mapping) in network_info for ip in mapping['ips']] @@ -1978,6 +1980,7 @@ class IptablesFirewallDriver(FirewallDriver): if FLAGS.use_ipv6: self.iptables.ipv6['filter'].add_chain(chain_name) + self.iptables.ipv6['filter'].empty_chain(chain_name) ips_v6 = [ip['ip'] for (_, mapping) in network_info for ip in mapping['ip6s']] @@ -1991,9 +1994,6 @@ class IptablesFirewallDriver(FirewallDriver): for rule in ipv4_rules: self.iptables.ipv4['filter'].add_rule(chain_name, rule) - for rule in ipv4_rules: - self.iptables.ipv4['filter'].add_rule(chain_name, rule) - if FLAGS.use_ipv6: for rule in ipv6_rules: self.iptables.ipv6['filter'].add_rule(chain_name, rule) @@ -2042,7 +2042,7 @@ class IptablesFirewallDriver(FirewallDriver): # they're not worth the clutter. if FLAGS.use_ipv6: # Allow RA responses - gateways_v6 = [network['gateway_v6'] for (network, _) in + gateways_v6 = [network['gateway_v6'] for (network, _m) in network_info] for gateway_v6 in gateways_v6: ipv6_rules.append( @@ -2065,7 +2065,7 @@ class IptablesFirewallDriver(FirewallDriver): security_group['id']) for rule in rules: - LOG.debug(_('Adding security group rule: %r'), rule) + LOG.debug(_("Adding security group rule: %r"), rule) if not rule.cidr: # Eventually, a mechanism to grant access for security @@ -2139,8 +2139,8 @@ class IptablesFirewallDriver(FirewallDriver): @utils.synchronized('iptables', external=True) def _do_refresh_provider_fw_rules(self): """Internal, synchronized version of refresh_provider_fw_rules.""" - self.purge_provider_fw_rules(self) - self.build_provider_fw_rules(self) + self._purge_provider_fw_rules() + self._build_provider_fw_rules() def _purge_provider_fw_rules(self): """Remove all rules from the provider chains.""" @@ -2150,6 +2150,9 @@ class IptablesFirewallDriver(FirewallDriver): def _build_provider_fw_rules(self): """Create all rules for the provider IP DROPs.""" + self.iptables.ipv4['filter'].add_chain('provider') + if FLAGS.use_ipv6: + self.iptables.ipv6['filter'].add_chain('provider') ipv4_rules, ipv6_rules = self._provider_rules() for rule in ipv4_rules: self.iptables.ipv4['filter'].add_rule('provider', rule) @@ -2179,19 +2182,19 @@ class IptablesFirewallDriver(FirewallDriver): fw_rules = ipv6_rules protocol = rule.protocol - if version == 6 and rule.protocol == 'icmp': + if version == 6 and protocol == 'icmp': protocol = 'icmpv6' args = ['-p', protocol, '-s', rule.cidr] - if rule.protocol in ['udp', 'tcp']: + if protocol in ['udp', 'tcp']: if rule.from_port == rule.to_port: args += ['--dport', '%s' % (rule.from_port,)] else: args += ['-m', 'multiport', '--dports', '%s:%s' % (rule.from_port, rule.to_port)] - elif rule.protocol == 'icmp': + elif protocol == 'icmp': icmp_type = rule.from_port icmp_code = rule.to_port From 2278f2886d369af285f914a7b5883d3a7a5727cc Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 7 Apr 2011 13:22:03 -0400 Subject: [PATCH 20/53] remove unused code. --- nova/tests/test_virt.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 34ecd09c6001..7c3dbf654c02 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -731,12 +731,6 @@ class IptablesFirewallTestCase(test.TestCase): db.instance_destroy(admin_ctxt, instance_ref['id']) def test_provider_firewall_rules(self): - # keep from changing state of actual firewall - #def fake_function(*args, **kwargs): - # pass - #self.fw.iptables.apply = fake_function - #self.fw.nwfilter.setup_basic_filtering = fake_function - # setup basic instance data instance_ref = db.instance_create(self.context, {'user_id': 'fake', From 0be9e06c09c1d08802a8963e34090b5fcedb19be Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:35:58 -0400 Subject: [PATCH 21/53] Double quotes are ugly. --- nova/api/ec2/admin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 0d08aba7ae10..3c7a60277852 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -349,19 +349,19 @@ class AdminController(object): def block_external_addresses(self, context, cidr): """Add provider-level firewall rules to block incoming traffic.""" - LOG.audit(_("Blocking traffic to all projects incoming from %s"), + LOG.audit(_('Blocking traffic to all projects incoming from %s'), cidr, context=context) cidr = urllib.unquote(cidr).decode() # raise if invalid IPy.IP(cidr) rule = {'cidr': cidr} tcp_rule = rule.copy() - tcp_rule.update({"protocol": "tcp", "from_port": 1, "to_port": 65535}) + tcp_rule.update({'protocol': 'tcp', 'from_port': 1, 'to_port': 65535}) udp_rule = rule.copy() - udp_rule.update({"protocol": "udp", "from_port": 1, "to_port": 65535}) + udp_rule.update({'protocol': 'udp', 'from_port': 1, 'to_port': 65535}) icmp_rule = rule.copy() - icmp_rule.update({"protocol": "icmp", "from_port": -1, - "to_port": None}) + icmp_rule.update({'protocol': 'icmp', 'from_port': -1, + 'to_port': None}) rules_added = 0 if not self._provider_fw_rule_exists(context, tcp_rule): db.provider_fw_rule_create(context, tcp_rule) @@ -375,4 +375,4 @@ class AdminController(object): if rules_added == 0: raise exception.ApiError(_('Duplicate rule')) self.compute_api.trigger_provider_fw_rules_refresh(context) - return {'status': 'OK', 'message': 'Disabled (number) IPs'} + return {'status': 'OK', 'message': 'Added %s rules' % rules_added} From ace6c5f82810c9984fc3e0bb24a9c37c00e8ac39 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:36:45 -0400 Subject: [PATCH 22/53] Double quotes are ugly #2. --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index e9e8e10e777c..660c7666fab2 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -372,7 +372,7 @@ class API(base.Base): for host in hosts: rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "refresh_provider_fw_rules", "args": {}}) + {'method': 'refresh_provider_fw_rules', 'args': {}}) def update(self, context, instance_id, **kwargs): """Updates the instance in the datastore. From b114b618e4ce112589773cbe63daf0ee70b900be Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:39:03 -0400 Subject: [PATCH 23/53] remove dead/duplicate code. --- nova/db/sqlalchemy/api.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 939e02a84ca7..5f5d46b863d6 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2011,10 +2011,6 @@ def provider_fw_rule_get_all(context): return session.query(models.ProviderFirewallRule).\ filter_by(deleted=can_read_deleted(context)).\ all() - fw_rule_ref = models.ProviderFirewallRule() - fw_rule_ref.update(rule) - fw_rule_ref.save() - return fw_rule_ref ################### From a8f2a6444f4198db5fd5f05f7d2ae94e953a0fa2 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:40:08 -0400 Subject: [PATCH 24/53] Move migration to fix ordering. --- ...{014_add_provider_fw_rules.py => 019_add_provider_fw_rules.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename nova/db/sqlalchemy/migrate_repo/versions/{014_add_provider_fw_rules.py => 019_add_provider_fw_rules.py} (100%) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py similarity index 100% rename from nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py rename to nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py From dfd6e6e3a46c2fbbb4e771d38396348c9659a0bd Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:44:18 -0400 Subject: [PATCH 25/53] Remove spurious newline at end of file. --- nova/virt/libvirt/connection.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index dc2dd1219d87..dfeda38148ee 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1532,4 +1532,3 @@ class LibvirtConnection(driver.ComputeDriver): def get_host_stats(self, refresh=False): """See xenapi_conn.py implementation.""" pass - From 459864dc0a05e6a0db642e9cb80ceade7b000ce8 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:45:46 -0400 Subject: [PATCH 26/53] fix typo introduced during merge conflict resolution. --- nova/virt/libvirt/firewall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py index fd4c120a6230..4e0df323d9a7 100644 --- a/nova/virt/libvirt/firewall.py +++ b/nova/virt/libvirt/firewall.py @@ -376,7 +376,7 @@ class NWFilterFirewall(FirewallDriver): return result def _define_filters(self, filter_name, filter_children): - self._define_fitler(self._filter_container(filter_name, + self._define_filter(self._filter_container(filter_name, filter_children)) def refresh_security_group_rules(self, From a5c9f44295df4054e9afb135aaa76c5e34cc3624 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:53:25 -0400 Subject: [PATCH 27/53] Double quotes are ugly #3. --- nova/virt/libvirt/firewall.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py index 4e0df323d9a7..5bdc0c1c6937 100644 --- a/nova/virt/libvirt/firewall.py +++ b/nova/virt/libvirt/firewall.py @@ -513,7 +513,7 @@ class IptablesFirewallDriver(FirewallDriver): network_info = netutils.get_network_info(instance) self.nwfilter.setup_basic_filtering(instance, network_info) if not self.basicly_filtered: - LOG.debug(_("iptables firewall: Setup Basic Filtering")) + LOG.debug(_('iptables firewall: Setup Basic Filtering')) self.refresh_provider_fw_rules() self.basicly_filtered = True @@ -638,7 +638,7 @@ class IptablesFirewallDriver(FirewallDriver): security_group['id']) for rule in rules: - LOG.debug(_("Adding security group rule: %r"), rule) + LOG.debug(_('Adding security group rule: %r'), rule) if not rule.cidr: # Eventually, a mechanism to grant access for security From 93bfea42bdd594030c8ae046f87291ff184ef3f6 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 12:09:04 -0400 Subject: [PATCH 28/53] Make a cleaner log message and use [] instead of . to get database fields. --- nova/tests/test_libvirt.py | 5 +---- nova/virt/libvirt/firewall.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 1743b09a2c2c..0eaf069fbf66 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -884,10 +884,7 @@ class IptablesFirewallTestCase(test.TestCase): def test_provider_firewall_rules(self): # setup basic instance data - instance_ref = db.instance_create(self.context, - {'user_id': 'fake', - 'project_id': 'fake', - 'mac_address': '56:12:12:12:12:12'}) + instance_ref = self._create_instance_ref() ip = '10.11.12.13' network_ref = db.project_get_network(self.context, 'fake') admin_ctxt = context.get_admin_context() diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py index 5bdc0c1c6937..c4192fac0a13 100644 --- a/nova/virt/libvirt/firewall.py +++ b/nova/virt/libvirt/firewall.py @@ -749,29 +749,29 @@ class IptablesFirewallDriver(FirewallDriver): ipv6_rules = [] rules = db.provider_fw_rule_get_all(ctxt) for rule in rules: - LOG.debug(_('Adding prvider rule: %r'), rule) - version = netutils.get_ip_version(rule.cidr) + LOG.debug(_('Adding provider rule: %s'), rule['cidr']) + version = netutils.get_ip_version(rule['cidr']) if version == 4: fw_rules = ipv4_rules else: fw_rules = ipv6_rules - protocol = rule.protocol + protocol = rule['protocol'] if version == 6 and protocol == 'icmp': protocol = 'icmpv6' - args = ['-p', protocol, '-s', rule.cidr] + args = ['-p', protocol, '-s', rule['cidr']] if protocol in ['udp', 'tcp']: - if rule.from_port == rule.to_port: - args += ['--dport', '%s' % (rule.from_port,)] + if rule['from_port'] == rule['to_port']: + args += ['--dport', '%s' % (rule['from_port'],)] else: args += ['-m', 'multiport', - '--dports', '%s:%s' % (rule.from_port, - rule.to_port)] + '--dports', '%s:%s' % (rule['from_port'], + rule['to_port'])] elif protocol == 'icmp': - icmp_type = rule.from_port - icmp_code = rule.to_port + icmp_type = rule['from_port'] + icmp_code = rule['to_port'] if icmp_type == -1: icmp_type_arg = None From 168d4556365fafcb34e9536f7f932d4da24b30a6 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 12:48:07 -0400 Subject: [PATCH 29/53] Test tweaks. --- nova/tests/test_libvirt.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 0eaf069fbf66..bb7c8cf33ac9 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -885,6 +885,7 @@ class IptablesFirewallTestCase(test.TestCase): def test_provider_firewall_rules(self): # setup basic instance data instance_ref = self._create_instance_ref() + nw_info = _create_network_info(1) ip = '10.11.12.13' network_ref = db.project_get_network(self.context, 'fake') admin_ctxt = context.get_admin_context() @@ -897,7 +898,7 @@ class IptablesFirewallTestCase(test.TestCase): # create a firewall via setup_basic_filtering like libvirt_conn.spawn # should have a chain with 0 rules - self.fw.setup_basic_filtering(instance_ref, network_info=None) + self.fw.setup_basic_filtering(instance_ref, network_info=nw_info) self.assertTrue('provider' in self.fw.iptables.ipv4['filter'].chains) rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules if rule.chain == 'provider'] @@ -926,13 +927,17 @@ class IptablesFirewallTestCase(test.TestCase): self.assertEqual(2, len(rules)) # create the instance filter and make sure it has a jump rule - self.fw.prepare_instance_filter(instance_ref, network_info=None) + self.fw.prepare_instance_filter(instance_ref, network_info=nw_info) self.fw.apply_instance_filter(instance_ref) inst_rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules if rule.chain == chain_name] jump_rules = [rule for rule in inst_rules if '-j' in rule.rule] - prov_rules = [rule for rule in jump_rules if 'provider' in rule.rule] - self.assertEqual(1, len(prov_rules)) + provjump_rules = [] + # IptablesTable doesn't make rules unique internally + for rule in jump_rules: + if 'provider' in rule.rule and rule not in provjump_rules: + provjump_rules.append(rule) + self.assertEqual(1, len(provjump_rules)) class NWFilterTestCase(test.TestCase): From 1fd3f5a6edf911dc84e11130bc2c590567d780c3 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 17:05:55 -0400 Subject: [PATCH 30/53] Fixes from Ed Leafe's review suggestions. --- nova/api/ec2/admin.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 3c7a60277852..1d4cffff358b 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -339,12 +339,9 @@ class AdminController(object): def _provider_fw_rule_exists(self, context, rule): # TODO(todd): we call this repeatedly, can we filter by protocol? for old_rule in db.provider_fw_rule_get_all(context): - dupe = True - for key in ('cidr', 'from_port', 'to_port', 'protocol'): - if rule[key] != old_rule[key]: - dupe = False - if dupe: - return dupe + if all([rule[k] == old_rule[k] for k in ('cidr', 'from_port', + 'to_port', 'protocol')]): + return True return False def block_external_addresses(self, context, cidr): @@ -372,7 +369,7 @@ class AdminController(object): if not self._provider_fw_rule_exists(context, icmp_rule): db.provider_fw_rule_create(context, icmp_rule) rules_added += 1 - if rules_added == 0: + if not rules_added: raise exception.ApiError(_('Duplicate rule')) self.compute_api.trigger_provider_fw_rules_refresh(context) return {'status': 'OK', 'message': 'Added %s rules' % rules_added} From add164c45db31baf8f12c3e5dede140c51a2e498 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 31 May 2011 14:10:29 -0400 Subject: [PATCH 31/53] Add refresh_provider_fw_rules to virt/driver.py#ComputeDriver so virtualization drivers other than libvirt will raise NotImplemented. --- nova/virt/driver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index eb9626d0879e..23581ab490bd 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -191,6 +191,10 @@ class ComputeDriver(object): def refresh_security_group_members(self, security_group_id): raise NotImplementedError() + def refresh_provider_fw_rules(self, security_group_id): + """See: nova/virt/fake.py for docs.""" + raise NotImplementedError() + def reset_network(self, instance): """reset networking for specified instance""" raise NotImplementedError() From 4d7dbdc96e30afbd19ab525e9667f6e3aaaafbe9 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 31 May 2011 16:20:07 -0400 Subject: [PATCH 32/53] Change version number of migration. --- ...{019_add_provider_fw_rules.py => 021_add_provider_fw_rules.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename nova/db/sqlalchemy/migrate_repo/versions/{019_add_provider_fw_rules.py => 021_add_provider_fw_rules.py} (100%) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py similarity index 100% rename from nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py rename to nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py From 2c1dd72060fccbe7f32a6aa08c1ce67476806680 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 31 May 2011 16:28:46 -0400 Subject: [PATCH 33/53] Whitespace cleanups. --- nova/virt/libvirt/firewall.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py index c4192fac0a13..28cd9fe9c1c0 100644 --- a/nova/virt/libvirt/firewall.py +++ b/nova/virt/libvirt/firewall.py @@ -81,7 +81,7 @@ class FirewallDriver(object): Gets called when a rule has been added to or removed from the list of rules (via admin api). - + """ raise NotImplementedError() @@ -306,7 +306,7 @@ class NWFilterFirewall(FirewallDriver): def prepare_instance_filter(self, instance, network_info=None): """Creates an NWFilter for the given instance. - + In the process, it makes sure the filters for the provider blocks, security groups, and base filter are all in place. From 781f3f07ebc3236404e33189e0a76cbb877dff18 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 10 Jun 2011 16:35:35 -0400 Subject: [PATCH 34/53] adding xml support to /images//meta resource; moving show/update entities into meta container --- nova/api/openstack/image_metadata.py | 58 +++++++++- nova/api/openstack/wsgi.py | 8 +- .../api/openstack/test_image_metadata.py | 103 +++++++++++++++++- 3 files changed, 155 insertions(+), 14 deletions(-) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index ebfe2bde9d6c..77028be28341 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -16,6 +16,7 @@ # under the License. from webob import exc +from xml.dom import minidom from nova import flags from nova import image @@ -59,7 +60,7 @@ class Controller(object): context = req.environ['nova.context'] metadata = self._get_metadata(context, image_id) if id in metadata: - return {id: metadata[id]} + return {'meta': {id: metadata[id]}} else: return faults.Fault(exc.HTTPNotFound()) @@ -77,15 +78,15 @@ class Controller(object): def update(self, req, image_id, id, body): context = req.environ['nova.context'] - if not id in body: + if not id in body['meta']: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) - if len(body) > 1: + if len(body['meta']) > 1: expl = _('Request body contains too many items') raise exc.HTTPBadRequest(explanation=expl) img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id, img) - metadata[id] = body[id] + metadata[id] = body['meta'][id] self._check_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) @@ -103,9 +104,56 @@ class Controller(object): self.image_service.update(context, image_id, img, None) +class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): + def __init__(self): + xmlns = wsgi.XMLNS_V11 + super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns) + + def _meta_item_to_xml(self, doc, key, value): + node = doc.createElement('meta') + node.setAttribute('key', key) + text = doc.createTextNode(value) + node.appendChild(text) + return node + + def _meta_list_to_xml(self, xml_doc, meta_items): + container_node = xml_doc.createElement('metadata') + for (key, value) in meta_items: + item_node = self._meta_item_to_xml(xml_doc, key, value) + container_node.appendChild(item_node) + return container_node + + def _meta_list_to_xml_string(self, metadata_dict): + xml_doc = minidom.Document() + items = metadata_dict['metadata'].items() + container_node = self._meta_list_to_xml(xml_doc, items) + self._add_xmlns(container_node) + return container_node.toprettyxml(indent=' ') + + def index(self, metadata_dict): + return self._meta_list_to_xml_string(metadata_dict) + + def create(self, metadata_dict): + return self._meta_list_to_xml_string(metadata_dict) + + def _meta_item_to_xml_string(self, meta_item_dict): + xml_doc = minidom.Document() + item_key, item_value = meta_item_dict.items()[0] + item_node = self._meta_item_to_xml(xml_doc, item_key, item_value) + self._add_xmlns(item_node) + return item_node.toprettyxml(indent=' ') + + def show(self, meta_item_dict): + return self._meta_item_to_xml_string(meta_item_dict['meta']) + + def update(self, meta_item_dict): + return self._meta_item_to_xml_string(meta_item_dict['meta']) + + + def create_resource(): serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), + 'application/xml': ImageMetadataXMLSerializer(), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index ddf4e6fa9c45..d1402b31403c 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -221,12 +221,14 @@ class XMLDictSerializer(DictSerializer): doc = minidom.Document() node = self._to_xml_node(doc, self.metadata, root_key, data[root_key]) - xmlns = node.getAttribute('xmlns') - if not xmlns and self.xmlns: - node.setAttribute('xmlns', self.xmlns) + self._add_xmlns(node) return node.toprettyxml(indent=' ') + def _add_xmlns(self, node): + if self.xmlns is not None: + node.setAttribute('xmlns', self.xmlns) + def _to_xml_node(self, doc, metadata, nodename, data): """Recursive method to convert data members to XML nodes.""" result = doc.createElement(nodename) diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 56be0f1cc917..bc1903ca5d36 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -19,6 +19,7 @@ import json import stubout import unittest import webob +import xml.dom.minidom as minidom from nova import flags @@ -105,13 +106,55 @@ class ImageMetaDataTest(unittest.TestCase): self.assertEqual(200, res.status_int) self.assertEqual('value1', res_dict['metadata']['key1']) + def test_index_xml(self): + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() + fixture = { + 'metadata': { + 'one': 'two', + 'three': 'four', + }, + } + output = serializer.index(fixture) + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + + four + + + two + + + """.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + def test_show(self): req = webob.Request.blank('/v1.1/images/1/meta/key1') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) - self.assertEqual('value1', res_dict['key1']) + self.assertEqual('value1', res_dict['meta']['key1']) + + def test_show_xml(self): + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() + fixture = { + 'meta': { + 'one': 'two', + }, + } + output = serializer.show(fixture) + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + two + + """.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) def test_show_not_found(self): req = webob.Request.blank('/v1.1/images/1/meta/key9') @@ -135,22 +178,70 @@ class ImageMetaDataTest(unittest.TestCase): self.assertEqual('value2', res_dict['metadata']['key2']) self.assertEqual(1, len(res_dict)) + def test_create_xml(self): + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() + fixture = { + 'metadata': { + 'key9': 'value9', + 'key2': 'value2', + 'key1': 'value1', + }, + } + output = serializer.create(fixture) + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + + value2 + + + value9 + + + value1 + + + """.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + def test_update_item(self): req = webob.Request.blank('/v1.1/images/1/meta/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"key1": "zz"}' + req.body = '{"meta": {"key1": "zz"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) + print res.body self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) - self.assertEqual('zz', res_dict['key1']) + self.assertEqual('zz', res_dict['meta']['key1']) + + def test_update_item_xml(self): + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() + fixture = { + 'meta': { + 'one': 'two', + }, + } + output = serializer.update(fixture) + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + two + + """.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + def test_update_item_too_many_keys(self): req = webob.Request.blank('/v1.1/images/1/meta/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"key1": "value1", "key2": "value2"}' + req.body = '{"meta": {"key1": "value1", "key2": "value2"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -159,7 +250,7 @@ class ImageMetaDataTest(unittest.TestCase): req = webob.Request.blank('/v1.1/images/1/meta/bad') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"key1": "value1"}' + req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -195,7 +286,7 @@ class ImageMetaDataTest(unittest.TestCase): req = webob.Request.blank('/v1.1/images/3/meta/blah') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"blah": "blah"}' + req.body = '{"meta": {"blah": "blah"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) From 0bcb15317fede5c17c77c187e1cd9a68a0c8030c Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 10 Jun 2011 22:32:33 -0400 Subject: [PATCH 35/53] Reorder firewall rules so the common path is shorter. --- nova/virt/libvirt/firewall.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py index 28cd9fe9c1c0..331c73b476b6 100644 --- a/nova/virt/libvirt/firewall.py +++ b/nova/virt/libvirt/firewall.py @@ -590,14 +590,14 @@ class IptablesFirewallDriver(FirewallDriver): ipv4_rules += ['-m state --state ' 'INVALID -j DROP'] ipv6_rules += ['-m state --state ' 'INVALID -j DROP'] - # Pass through provider-wide drops - ipv4_rules += ['-j $provider'] - ipv6_rules += ['-j $provider'] - # Allow established connections ipv4_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT'] ipv6_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT'] + # Pass through provider-wide drops + ipv4_rules += ['-j $provider'] + ipv6_rules += ['-j $provider'] + dhcp_servers = [network['gateway'] for (network, _m) in network_info] for dhcp_server in dhcp_servers: From 90b3f8eb676681afa491c8bac85a45fdc87b099b Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sat, 11 Jun 2011 14:50:21 -0400 Subject: [PATCH 36/53] Move migration. --- ...dd_provider_fw_rules.py => 023_add_provider_firewall_rules.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename nova/db/sqlalchemy/migrate_repo/versions/{021_add_provider_fw_rules.py => 023_add_provider_firewall_rules.py} (100%) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py similarity index 100% rename from nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py rename to nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py From 27fb2cf245ae87282f3aefdf2ae4740866529101 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sat, 11 Jun 2011 15:14:46 -0400 Subject: [PATCH 37/53] pep 8 whitespace fix. --- nova/tests/test_libvirt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 07c8e81f11b3..ee94d3c171b0 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -1058,7 +1058,6 @@ class IptablesFirewallTestCase(test.TestCase): db.instance_destroy(admin_ctxt, instance_ref['id']) - def test_provider_firewall_rules(self): # setup basic instance data instance_ref = self._create_instance_ref() From 7a2712ebf74e5565663a6723a992151f71255eff Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 14 Jun 2011 11:34:03 -0700 Subject: [PATCH 38/53] Move ipy commands to netaddr. --- nova/api/ec2/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index fcf7f674c652..343bc61c4d19 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -22,7 +22,7 @@ Admin API controller, exposed through http via the api worker. import base64 import datetime -import IPy +import netaddr import urllib from nova import compute @@ -346,7 +346,7 @@ class AdminController(object): cidr, context=context) cidr = urllib.unquote(cidr).decode() # raise if invalid - IPy.IP(cidr) + netaddr.IPNetwork(cidr) rule = {'cidr': cidr} tcp_rule = rule.copy() tcp_rule.update({'protocol': 'tcp', 'from_port': 1, 'to_port': 65535}) From bfbb2b8e04d1cd4b761c67973b173d2ca6f84859 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 17 Jun 2011 13:39:34 -0400 Subject: [PATCH 39/53] adding extra image service properties to compute api snapshot; adding instance_ref property --- nova/api/openstack/images.py | 31 ++++++++++++++++++++++--- nova/api/openstack/views/images.py | 30 +++++++++++------------- nova/compute/api.py | 3 ++- nova/tests/api/openstack/fakes.py | 7 +++--- nova/tests/api/openstack/test_images.py | 30 ++++++++++++++++-------- 5 files changed, 67 insertions(+), 34 deletions(-) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 5ffd8e96afd3..4a09060c9d89 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os.path + import webob.exc from nova import compute @@ -104,7 +106,10 @@ class Controller(object): except KeyError: raise webob.exc.HTTPBadRequest() - image = self._compute_service.snapshot(context, server_id, image_name) + props = self._get_extra_properties(req, body) + + image = self._compute_service.snapshot(context, server_id, + image_name, props) return dict(image=self.get_builder(req).build(image, detail=True)) def get_builder(self, request): @@ -114,6 +119,9 @@ class Controller(object): def _server_id_from_req_data(self, data): raise NotImplementedError() + def _get_extra_properties(self, req, data): + return {} + class ControllerV10(Controller): """Version 1.0 specific controller logic.""" @@ -150,7 +158,11 @@ class ControllerV10(Controller): return dict(images=[builder(image, detail=True) for image in images]) def _server_id_from_req_data(self, data): - return data['image']['serverId'] + try: + return data['image']['serverId'] + except KeyError: + msg = _("Expected serverId attribute on server entity.") + raise webob.exc.HTTPBadRequest(explanation=msg) class ControllerV11(Controller): @@ -190,7 +202,20 @@ class ControllerV11(Controller): return dict(images=[builder(image, detail=True) for image in images]) def _server_id_from_req_data(self, data): - return data['image']['serverRef'] + try: + server_ref = data['image']['serverRef'] + except KeyError: + msg = _("Expected serverRef attribute on server entity.") + raise webob.exc.HTTPBadRequest(explanation=msg) + + return os.path.split(server_ref)[1] + + def _get_extra_properties(self, req, data): + server_ref = data['image']['serverRef'] + if not server_ref.startswith('http'): + server_ref = os.path.join(req.application_url, 'servers', + server_ref) + return {'instance_ref': server_ref} def create_resource(version='1.0'): diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 2773c9c135fb..d6a0541020f9 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -46,13 +46,9 @@ class ViewBuilder(object): except KeyError: image['status'] = image['status'].upper() - def _build_server(self, image, instance_id): + def _build_server(self, image, image_obj): """Indicates that you must use a ViewBuilder subclass.""" - raise NotImplementedError - - def generate_server_ref(self, server_id): - """Return an href string pointing to this server.""" - return os.path.join(self._url, "servers", str(server_id)) + raise NotImplementedError() def generate_href(self, image_id): """Return an href string pointing to this object.""" @@ -60,8 +56,6 @@ class ViewBuilder(object): def build(self, image_obj, detail=False): """Return a standardized image structure for display by the API.""" - properties = image_obj.get("properties", {}) - self._format_dates(image_obj) if "status" in image_obj: @@ -72,11 +66,7 @@ class ViewBuilder(object): "name": image_obj.get("name"), } - if "instance_id" in properties: - try: - self._build_server(image, int(properties["instance_id"])) - except ValueError: - pass + self._build_server(image, image_obj) if detail: image.update({ @@ -94,15 +84,21 @@ class ViewBuilder(object): class ViewBuilderV10(ViewBuilder): """OpenStack API v1.0 Image Builder""" - def _build_server(self, image, instance_id): - image["serverId"] = instance_id + def _build_server(self, image, image_obj): + try: + image['serverId'] = int(image_obj['properties']['instance_id']) + except (KeyError, ValueError): + pass class ViewBuilderV11(ViewBuilder): """OpenStack API v1.1 Image Builder""" - def _build_server(self, image, instance_id): - image["serverRef"] = self.generate_server_ref(instance_id) + def _build_server(self, image, image_obj): + try: + image['serverRef'] = image_obj['properties']['instance_ref'] + except KeyError: + return def build(self, image_obj, detail=False): """Return a standardized image structure for display by the API.""" diff --git a/nova/compute/api.py b/nova/compute/api.py index e2c4cf8d7348..86e2b116ecf1 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -619,7 +619,7 @@ class API(base.Base): raise exception.Error(_("Unable to find host for Instance %s") % instance_id) - def snapshot(self, context, instance_id, name): + def snapshot(self, context, instance_id, name, extra_properties=None): """Snapshot the given instance. :returns: A dict containing image metadata @@ -627,6 +627,7 @@ class API(base.Base): properties = {'instance_id': str(instance_id), 'user_id': str(context.user_id), 'image_state': 'creating'} + properties.update(extra_properties or {}) sent_meta = {'name': name, 'is_public': False, 'status': 'creating', 'properties': properties} recv_meta = self.image_service.create(context, sent_meta) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index a10fb7433eda..d46608c9263e 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -141,9 +141,10 @@ def stub_out_networking(stubs): def stub_out_compute_api_snapshot(stubs): - def snapshot(self, context, instance_id, name): - return dict(id='123', status='ACTIVE', - properties=dict(instance_id='123')) + def snapshot(self, context, instance_id, name, extra_properties=None): + props = dict(instance_id=instance_id, instance_ref=instance_id) + props.update(extra_properties or {}) + return dict(id='123', status='ACTIVE', name=name, properties=props) stubs.Set(nova.compute.API, 'snapshot', snapshot) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index be777df9b7c7..06983893a10f 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -648,7 +648,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 124, 'name': 'queued backup', - 'serverId': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'QUEUED', @@ -656,7 +655,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 125, 'name': 'saving backup', - 'serverId': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'SAVING', @@ -665,7 +663,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 126, 'name': 'active backup', - 'serverId': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE' @@ -673,7 +670,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 127, 'name': 'killed backup', - 'serverId': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', @@ -719,7 +715,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 124, 'name': 'queued backup', - 'serverRef': "http://localhost/v1.1/servers/42", + 'serverRef': "http://localhost:8774/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'QUEUED', @@ -741,7 +737,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 125, 'name': 'saving backup', - 'serverRef': "http://localhost/v1.1/servers/42", + 'serverRef': "http://localhost:8774/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'SAVING', @@ -764,7 +760,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 126, 'name': 'active backup', - 'serverRef': "http://localhost/v1.1/servers/42", + 'serverRef': "http://localhost:8774/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', @@ -786,7 +782,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 127, 'name': 'killed backup', - 'serverRef': "http://localhost/v1.1/servers/42", + 'serverRef': "http://localhost:8774/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', @@ -1032,6 +1028,19 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): response = req.get_response(fakes.wsgi_app()) self.assertEqual(200, response.status_int) + def test_create_image_v1_1_actual_serverRef(self): + + serverRef = 'http://localhost:8774/v1.1/servers/1' + body = dict(image=dict(serverRef=serverRef, name='Backup 1')) + req = webob.Request.blank('/v1.1/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, response.status_int) + result = json.loads(response.body) + self.assertEqual(result['image']['serverRef'], serverRef) + def test_create_image_v1_1_xml_serialization(self): body = dict(image=dict(serverRef='123', name='Backup 1')) @@ -1048,7 +1057,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): Date: Fri, 17 Jun 2011 14:35:10 -0400 Subject: [PATCH 40/53] adding check for serverRef hostname matching app url --- nova/api/openstack/images.py | 16 +++++++++++----- nova/tests/api/openstack/test_images.py | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 4a09060c9d89..d43340e104de 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -101,7 +101,7 @@ class Controller(object): raise webob.exc.HTTPBadRequest() try: - server_id = self._server_id_from_req_data(body) + server_id = self._server_id_from_req(req, body) image_name = body["image"]["name"] except KeyError: raise webob.exc.HTTPBadRequest() @@ -116,7 +116,7 @@ class Controller(object): """Indicates that you must use a Controller subclass.""" raise NotImplementedError - def _server_id_from_req_data(self, data): + def _server_id_from_req(self, req, data): raise NotImplementedError() def _get_extra_properties(self, req, data): @@ -157,7 +157,7 @@ class ControllerV10(Controller): builder = self.get_builder(req).build return dict(images=[builder(image, detail=True) for image in images]) - def _server_id_from_req_data(self, data): + def _server_id_from_req(self, req, data): try: return data['image']['serverId'] except KeyError: @@ -201,14 +201,20 @@ class ControllerV11(Controller): builder = self.get_builder(req).build return dict(images=[builder(image, detail=True) for image in images]) - def _server_id_from_req_data(self, data): + def _server_id_from_req(self, req, data): try: server_ref = data['image']['serverRef'] except KeyError: msg = _("Expected serverRef attribute on server entity.") raise webob.exc.HTTPBadRequest(explanation=msg) - return os.path.split(server_ref)[1] + head, tail = os.path.split(server_ref) + + if head and head != os.path.join(req.application_url, 'servers'): + msg = _("serverRef must match request url") + raise webob.exc.HTTPBadRequest(explanation=msg) + + return tail def _get_extra_properties(self, req, data): server_ref = data['image']['serverRef'] diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 06983893a10f..deef5d235079 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -1028,9 +1028,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): response = req.get_response(fakes.wsgi_app()) self.assertEqual(200, response.status_int) - def test_create_image_v1_1_actual_serverRef(self): + def test_create_image_v1_1_actual_server_ref(self): - serverRef = 'http://localhost:8774/v1.1/servers/1' + serverRef = 'http://localhost/v1.1/servers/1' body = dict(image=dict(serverRef=serverRef, name='Backup 1')) req = webob.Request.blank('/v1.1/images') req.method = 'POST' @@ -1041,6 +1041,17 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): result = json.loads(response.body) self.assertEqual(result['image']['serverRef'], serverRef) + def test_create_image_v1_1_server_ref_bad_hostname(self): + + serverRef = 'http://asdf/v1.1/servers/1' + body = dict(image=dict(serverRef=serverRef, name='Backup 1')) + req = webob.Request.blank('/v1.1/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + def test_create_image_v1_1_xml_serialization(self): body = dict(image=dict(serverRef='123', name='Backup 1')) From dca372d68ab99126f22c7467af12de30bb4488e4 Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 20 Jun 2011 15:28:34 -0700 Subject: [PATCH 41/53] nova-manage checks if user is member of proj, prior to adding role for that project --- bin/nova-manage | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/nova-manage b/bin/nova-manage index dbdb798a7cb3..7226fcfa104a 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -257,6 +257,11 @@ class RoleCommands(object): """adds role to user if project is specified, adds project specific role arguments: user, role [project]""" + if project: + projobj = self.manager.get_project(project) + if not projobj.has_member(user): + print "%s not a member of %s" % (user, project) + return self.manager.add_role(user, role, project) def has(self, user, role, project=None): From 90bfee5c963416c1f807fde4701f0b2755d5021c Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 21 Jun 2011 10:46:29 +0200 Subject: [PATCH 42/53] Stop trying to set a body for HTTP methods that do not allow it. It renders the unit tests useless (since they're testing a situation that can never arise) and webob 1.0.8 fails if you do this. --- nova/tests/api/openstack/test_limits.py | 3 +-- nova/tests/api/openstack/test_servers.py | 2 +- nova/tests/api/openstack/test_wsgi.py | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 01613d1d8fdf..38c959fae615 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -672,8 +672,7 @@ class WsgiLimiterTest(BaseLimitTestSuite): """Only POSTs should work.""" requests = [] for method in ["GET", "PUT", "DELETE", "HEAD", "OPTIONS"]: - request = webob.Request.blank("/") - request.body = self._request_data("GET", "/something") + request = webob.Request.blank("/", method=method) response = request.get_response(self.app) self.assertEqual(response.status_int, 405) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 28ad4a4178d9..8851b4a7303d 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1314,7 +1314,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 400) def test_resized_server_has_correct_status(self): - req = self.webreq('/1', 'GET', dict(resize=dict(flavorId=3))) + req = self.webreq('/1', 'GET') def fake_migration_get(*args): return {} diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index 2fa50ac9bac5..73a26a087a19 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -10,13 +10,13 @@ from nova.api.openstack import wsgi class RequestTest(test.TestCase): def test_content_type_missing(self): - request = wsgi.Request.blank('/tests/123') + request = wsgi.Request.blank('/tests/123', method='POST') request.body = "" self.assertRaises(exception.InvalidContentType, request.get_content_type) def test_content_type_unsupported(self): - request = wsgi.Request.blank('/tests/123') + request = wsgi.Request.blank('/tests/123', method='POST') request.headers["Content-Type"] = "text/html" request.body = "asdf
" self.assertRaises(exception.InvalidContentType, From 652ccbd3d255c5c95337d874b9cba10f0ce40ebb Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 21 Jun 2011 16:59:36 +0200 Subject: [PATCH 43/53] Bump WebOb requirement to 1.0.8 in pip-requires. --- tools/pip-requires | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pip-requires b/tools/pip-requires index e81ef944a01a..c27efc305e27 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -15,7 +15,7 @@ python-daemon==1.5.5 python-gflags==1.3 redis==2.0.0 routes==1.12.3 -WebOb==0.9.8 +WebOb==1.0.8 wsgiref==0.1.2 mox==0.5.3 greenlet==0.3.1 From 6faecbb9617dfc2da283c7b46be36f512db14287 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 21 Jun 2011 12:31:33 -0400 Subject: [PATCH 44/53] if we get InstanceNotFound error on create, ignore (means it has been deleted before we got the create message) --- nova/compute/manager.py | 86 ++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8ab744855948..e53ff75fcf2c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -291,50 +291,58 @@ class ComputeManager(manager.SchedulerDependentManager): 'networking') is_vpn = instance_ref['image_ref'] == str(FLAGS.vpn_image_id) - # NOTE(vish): This could be a cast because we don't do anything - # with the address currently, but I'm leaving it as - # a call to ensure that network setup completes. We - # will eventually also need to save the address here. - if not FLAGS.stub_network: - address = rpc.call(context, - self.get_network_topic(context), - {"method": "allocate_fixed_ip", - "args": {"instance_id": instance_id, - "vpn": is_vpn}}) - - self.network_manager.setup_compute_network(context, - instance_id) - - block_device_mapping = self._setup_block_device_mapping(context, - instance_id) - - # TODO(vish) check to make sure the availability zone matches - self._update_state(context, instance_id, power_state.BUILDING) - try: - self.driver.spawn(instance_ref, - block_device_mapping=block_device_mapping) - except Exception as ex: # pylint: disable=W0702 - msg = _("Instance '%(instance_id)s' failed to spawn. Is " - "virtualization enabled in the BIOS? Details: " - "%(ex)s") % locals() - LOG.exception(msg) + # NOTE(vish): This could be a cast because we don't do anything + # with the address currently, but I'm leaving it as + # a call to ensure that network setup completes. We + # will eventually also need to save the address here. + if not FLAGS.stub_network: + address = rpc.call(context, + self.get_network_topic(context), + {"method": "allocate_fixed_ip", + "args": {"instance_id": instance_id, + "vpn": is_vpn}}) - if not FLAGS.stub_network and FLAGS.auto_assign_floating_ip: - public_ip = self.network_api.allocate_floating_ip(context) + self.network_manager.setup_compute_network(context, + instance_id) - self.db.floating_ip_set_auto_assigned(context, public_ip) - fixed_ip = self.db.fixed_ip_get_by_address(context, address) - floating_ip = self.db.floating_ip_get_by_address(context, - public_ip) + block_device_mapping = self._setup_block_device_mapping(context, + instance_id) - self.network_api.associate_floating_ip(context, - floating_ip, - fixed_ip, - affect_auto_assigned=True) + # TODO(vish) check to make sure the availability zone matches + self._update_state(context, instance_id, power_state.BUILDING) + + try: + self.driver.spawn(instance_ref, + block_device_mapping=block_device_mapping) + except Exception as ex: # pylint: disable=W0702 + msg = _("Instance '%(instance_id)s' failed to spawn. Is " + "virtualization enabled in the BIOS? Details: " + "%(ex)s") % locals() + LOG.exception(msg) + + if not FLAGS.stub_network and FLAGS.auto_assign_floating_ip: + public_ip = self.network_api.allocate_floating_ip(context) + + self.db.floating_ip_set_auto_assigned(context, public_ip) + fixed_ip = self.db.fixed_ip_get_by_address(context, address) + floating_ip = self.db.floating_ip_get_by_address(context, + public_ip) + + self.network_api.associate_floating_ip(context, + floating_ip, + fixed_ip, + affect_auto_assigned=True) + + self._update_launched_at(context, instance_id) + self._update_state(context, instance_id) + except exception.InstanceNotFound: + # FIXME(wwolf): We are just ignoring InstanceNotFound + # exceptions here in case the image was immediately deleted + # before it actually got created. This should be fixed once we have + # no-db-messaging + pass - self._update_launched_at(context, instance_id) - self._update_state(context, instance_id) @exception.wrap_exception def run_instance(self, context, instance_id, **kwargs): From 979a9d2587584f60bfcad9a65da70df5ba3169be Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 22 Jun 2011 15:22:25 -0400 Subject: [PATCH 45/53] fixed incorrect exception --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 48d5769318ca..000e834f0d4d 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -873,7 +873,7 @@ class InstanceTypeCommands(object): try: instance_types.create(name, memory, vcpus, local_gb, flavorid, swap, rxtx_quota, rxtx_cap) - except exception.InvalidInputException: + except exception.InvalidInput: print "Must supply valid parameters to create instance_type" print e sys.exit(1) From 7398819cc00a078a486b4d2f11846ff32db19a88 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 22 Jun 2011 17:07:26 -0400 Subject: [PATCH 46/53] moving image show/update into 'meta' container --- nova/api/openstack/image_metadata.py | 15 ++++++++--- .../api/openstack/test_image_metadata.py | 26 ++++++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index ebfe2bde9d6c..07f0fb4fbc7e 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -59,7 +59,7 @@ class Controller(object): context = req.environ['nova.context'] metadata = self._get_metadata(context, image_id) if id in metadata: - return {id: metadata[id]} + return {'meta': {id: metadata[id]}} else: return faults.Fault(exc.HTTPNotFound()) @@ -77,15 +77,22 @@ class Controller(object): def update(self, req, image_id, id, body): context = req.environ['nova.context'] - if not id in body: + + try: + meta = body['meta'] + except KeyError: + expl = _('Incorrect request body format') + raise exc.HTTPBadRequest(explanation=expl) + + if not id in meta: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) - if len(body) > 1: + if len(meta) > 1: expl = _('Request body contains too many items') raise exc.HTTPBadRequest(explanation=expl) img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id, img) - metadata[id] = body[id] + metadata[id] = meta[id] self._check_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 56be0f1cc917..0faf9a26efbc 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -111,13 +111,14 @@ class ImageMetaDataTest(unittest.TestCase): res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) - self.assertEqual('value1', res_dict['key1']) + self.assertTrue('meta' in res_dict) + self.assertEqual(len(res_dict['meta']), 1) + self.assertEqual('value1', res_dict['meta']['key1']) def test_show_not_found(self): req = webob.Request.blank('/v1.1/images/1/meta/key9') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) - res_dict = json.loads(res.body) self.assertEqual(404, res.status_int) def test_create(self): @@ -139,18 +140,29 @@ class ImageMetaDataTest(unittest.TestCase): req = webob.Request.blank('/v1.1/images/1/meta/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"key1": "zz"}' + req.body = '{"meta": {"key1": "zz"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) - self.assertEqual('zz', res_dict['key1']) + self.assertTrue('meta' in res_dict) + self.assertEqual(len(res_dict['meta']), 1) + self.assertEqual('zz', res_dict['meta']['key1']) + + def test_update_item_bad_body(self): + req = webob.Request.blank('/v1.1/images/1/meta/key1') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"key1": "zz"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) def test_update_item_too_many_keys(self): req = webob.Request.blank('/v1.1/images/1/meta/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"key1": "value1", "key2": "value2"}' + req.body = '{"meta": {"key1": "value1", "key2": "value2"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -159,7 +171,7 @@ class ImageMetaDataTest(unittest.TestCase): req = webob.Request.blank('/v1.1/images/1/meta/bad') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"key1": "value1"}' + req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -195,7 +207,7 @@ class ImageMetaDataTest(unittest.TestCase): req = webob.Request.blank('/v1.1/images/3/meta/blah') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"blah": "blah"}' + req.body = '{"meta": {"blah": "blah"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) From 1f9cd3e7c97034408b5afe3fc3720c48040dea97 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 22 Jun 2011 17:14:58 -0400 Subject: [PATCH 47/53] reverting non-xml changes --- nova/api/openstack/image_metadata.py | 8 ++++---- nova/tests/api/openstack/test_image_metadata.py | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 77028be28341..399cd2637e5b 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -60,7 +60,7 @@ class Controller(object): context = req.environ['nova.context'] metadata = self._get_metadata(context, image_id) if id in metadata: - return {'meta': {id: metadata[id]}} + return {id: metadata[id]} else: return faults.Fault(exc.HTTPNotFound()) @@ -78,15 +78,15 @@ class Controller(object): def update(self, req, image_id, id, body): context = req.environ['nova.context'] - if not id in body['meta']: + if not id in body: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) - if len(body['meta']) > 1: + if len(body) > 1: expl = _('Request body contains too many items') raise exc.HTTPBadRequest(explanation=expl) img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id, img) - metadata[id] = body['meta'][id] + metadata[id] = body[id] self._check_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index bc1903ca5d36..e77ff79b945e 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -136,7 +136,7 @@ class ImageMetaDataTest(unittest.TestCase): res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) - self.assertEqual('value1', res_dict['meta']['key1']) + self.assertEqual('value1', res_dict['key1']) def test_show_xml(self): serializer = openstack.image_metadata.ImageMetadataXMLSerializer() @@ -210,13 +210,12 @@ class ImageMetaDataTest(unittest.TestCase): req = webob.Request.blank('/v1.1/images/1/meta/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"meta": {"key1": "zz"}}' + req.body = '{"key1": "zz"}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) - print res.body self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) - self.assertEqual('zz', res_dict['meta']['key1']) + self.assertEqual('zz', res_dict['key1']) def test_update_item_xml(self): serializer = openstack.image_metadata.ImageMetadataXMLSerializer() @@ -241,7 +240,7 @@ class ImageMetaDataTest(unittest.TestCase): req = webob.Request.blank('/v1.1/images/1/meta/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"meta": {"key1": "value1", "key2": "value2"}}' + req.body = '{"key1": "value1", "key2": "value2"}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -250,7 +249,7 @@ class ImageMetaDataTest(unittest.TestCase): req = webob.Request.blank('/v1.1/images/1/meta/bad') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"meta": {"key1": "value1"}}' + req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -286,7 +285,7 @@ class ImageMetaDataTest(unittest.TestCase): req = webob.Request.blank('/v1.1/images/3/meta/blah') req.environ['api.version'] = '1.1' req.method = 'PUT' - req.body = '{"meta": {"blah": "blah"}}' + req.body = '{"blah": "blah"}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) From 548ac151cd1c7de5249fdeb651895917e83df488 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 22 Jun 2011 18:22:57 -0400 Subject: [PATCH 48/53] pep8 fixes --- nova/api/openstack/image_metadata.py | 1 - nova/tests/api/openstack/test_image_metadata.py | 1 - 2 files changed, 2 deletions(-) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 399cd2637e5b..6912645533a6 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -150,7 +150,6 @@ class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): return self._meta_item_to_xml_string(meta_item_dict['meta']) - def create_resource(): serializers = { 'application/xml': ImageMetadataXMLSerializer(), diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index e77ff79b945e..9495eadec715 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -235,7 +235,6 @@ class ImageMetaDataTest(unittest.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) - def test_update_item_too_many_keys(self): req = webob.Request.blank('/v1.1/images/1/meta/key1') req.environ['api.version'] = '1.1' From 3b8ac87afbc1b2bb9371486697e1dd3ff22a4bc5 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 22 Jun 2011 20:03:35 -0400 Subject: [PATCH 49/53] image -> instance in comment --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index adf8b1c78947..3b98b9067861 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -338,7 +338,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._update_state(context, instance_id) except exception.InstanceNotFound: # FIXME(wwolf): We are just ignoring InstanceNotFound - # exceptions here in case the image was immediately deleted + # exceptions here in case the instance was immediately deleted # before it actually got created. This should be fixed once we have # no-db-messaging pass From b0e24d4dd2f4918ed1cbf85af4b31fdd09def1f6 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 22 Jun 2011 20:27:17 -0400 Subject: [PATCH 50/53] fixed pep8 issues --- nova/compute/manager.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 3b98b9067861..54eaf2082fa8 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -306,8 +306,9 @@ class ComputeManager(manager.SchedulerDependentManager): self.network_manager.setup_compute_network(context, instance_id) - block_device_mapping = self._setup_block_device_mapping(context, - instance_id) + block_device_mapping = self._setup_block_device_mapping( + context, + instance_id) # TODO(vish) check to make sure the availability zone matches self._update_state(context, instance_id, power_state.BUILDING) @@ -329,21 +330,21 @@ class ComputeManager(manager.SchedulerDependentManager): floating_ip = self.db.floating_ip_get_by_address(context, public_ip) - self.network_api.associate_floating_ip(context, - floating_ip, - fixed_ip, - affect_auto_assigned=True) + self.network_api.associate_floating_ip( + context, + floating_ip, + fixed_ip, + affect_auto_assigned=True) self._update_launched_at(context, instance_id) self._update_state(context, instance_id) except exception.InstanceNotFound: # FIXME(wwolf): We are just ignoring InstanceNotFound - # exceptions here in case the instance was immediately deleted - # before it actually got created. This should be fixed once we have - # no-db-messaging + # exceptions here in case the instance was immediately + # deleted before it actually got created. This should + # be fixed once we have no-db-messaging pass - @exception.wrap_exception def run_instance(self, context, instance_id, **kwargs): self._run_instance(context, instance_id, **kwargs) From 02085b96528b66b322f5c1ce5281d0284f9cbe40 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 23 Jun 2011 13:28:13 -0400 Subject: [PATCH 51/53] Move migration to newer version. --- ...vider_firewall_rules.py => 027_add_provider_firewall_rules.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename nova/db/sqlalchemy/migrate_repo/versions/{023_add_provider_firewall_rules.py => 027_add_provider_firewall_rules.py} (100%) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/027_add_provider_firewall_rules.py similarity index 100% rename from nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py rename to nova/db/sqlalchemy/migrate_repo/versions/027_add_provider_firewall_rules.py From 1a3fb4332401e5fb3b5b090034ecf4fdf47246cf Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 23 Jun 2011 13:54:45 -0400 Subject: [PATCH 52/53] Add admin api test case (like cloud test case) with a test for fw rules. --- nova/tests/test_adminapi.py | 90 +++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 nova/tests/test_adminapi.py diff --git a/nova/tests/test_adminapi.py b/nova/tests/test_adminapi.py new file mode 100644 index 000000000000..70a00f999a2f --- /dev/null +++ b/nova/tests/test_adminapi.py @@ -0,0 +1,90 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from eventlet import greenthread + +from nova import context +from nova import db +from nova import flags +from nova import log as logging +from nova import rpc +from nova import test +from nova import utils +from nova.auth import manager +from nova.api.ec2 import admin +from nova.image import fake + + +FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.tests.adminapi') + + +class AdminApiTestCase(test.TestCase): + def setUp(self): + super(AdminApiTestCase, self).setUp() + self.flags(connection_type='fake') + + self.conn = rpc.Connection.instance() + + # set up our cloud + self.api = admin.AdminController() + + # set up services + self.compute = self.start_service('compute') + self.scheduter = self.start_service('scheduler') + self.network = self.start_service('network') + self.volume = self.start_service('volume') + self.image_service = utils.import_object(FLAGS.image_service) + + self.manager = manager.AuthManager() + self.user = self.manager.create_user('admin', 'admin', 'admin', True) + self.project = self.manager.create_project('proj', 'admin', 'proj') + self.context = context.RequestContext(user=self.user, + project=self.project) + host = self.network.get_network_host(self.context.elevated()) + + def fake_show(meh, context, id): + return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type': 'machine', 'image_state': 'available'}} + + self.stubs.Set(fake._FakeImageService, 'show', fake_show) + self.stubs.Set(fake._FakeImageService, 'show_by_name', fake_show) + + # NOTE(vish): set up a manual wait so rpc.cast has a chance to finish + rpc_cast = rpc.cast + + def finish_cast(*args, **kwargs): + rpc_cast(*args, **kwargs) + greenthread.sleep(0.2) + + self.stubs.Set(rpc, 'cast', finish_cast) + + def tearDown(self): + network_ref = db.project_get_network(self.context, + self.project.id) + db.network_disassociate(self.context, network_ref['id']) + self.manager.delete_project(self.project) + self.manager.delete_user(self.user) + super(AdminApiTestCase, self).tearDown() + + def test_block_external_ips(self): + """Make sure provider firewall rules are created.""" + result = self.api.block_external_addresses(self.context, '1.1.1.1/32') + self.assertEqual('OK', result['status']) + self.assertEqual('Added 3 rules', result['message']) + From 203f3f85b6d66735f52013cbe5a736ef82d7a083 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 23 Jun 2011 13:59:26 -0400 Subject: [PATCH 53/53] pep8: remove newline at end of file. --- nova/tests/test_adminapi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/test_adminapi.py b/nova/tests/test_adminapi.py index 70a00f999a2f..7ecaf1c095ce 100644 --- a/nova/tests/test_adminapi.py +++ b/nova/tests/test_adminapi.py @@ -87,4 +87,3 @@ class AdminApiTestCase(test.TestCase): result = self.api.block_external_addresses(self.context, '1.1.1.1/32') self.assertEqual('OK', result['status']) self.assertEqual('Added 3 rules', result['message']) -