From 3403e773f5a38c5d415e4ab66799c6e239223a0d Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 17 Jan 2011 19:40:35 -0500 Subject: 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 b2609c8c4..90ad4d9dd 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 758b612e8..b784c5a00 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'} -- cgit From d4e7eb818c9f4ec51fd3a88a0e92d557867511d4 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 17 Jan 2011 23:18:46 -0500 Subject: 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 b784c5a00..1ae5f7094 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 a6b99c1cb..607a9ef25 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 6f09ce674..9c4a23d08 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 f9d561587..a05c8159e 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 b63b84bed..4223aa0f7 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 bf5e48b04..1d82cff18 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 13181b730..f5a978997 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 f38af5ed8..fa5dc502e 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): """ -- cgit From 46c1c554e7d98959a2b20597d6b0f2b0f648cdc9 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 19 Jan 2011 15:16:12 -0500 Subject: 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 90ad4d9dd..0cdb8c6fb 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 1ae5f7094..3a8ed39eb 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"), -- cgit From c58a8edb5c282f661d5be361ce68131516c741ba Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 19 Jan 2011 15:17:06 -0500 Subject: 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 a42e06e3b..f55bde908 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 95ed2d59e..7a3a6e89a 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 b189a5b31..68503ef68 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: 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 9c4a23d08..a9734f13b 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 dc5a9fc06..4f3107c88 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() -- cgit From f02c9e781bdddd609601da81b97a438b6d5b9781 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 21 Jan 2011 12:30:26 -0500 Subject: 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 3a8ed39eb..c83f84b15 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 4f3107c88..38eddf748 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,) -- cgit From 4e3524c57f6fa0f917bdb30ec15c8d4633a307e5 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 25 Jan 2011 12:52:00 -0800 Subject: 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 4a34476d3..0dabf2092 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 cb1a57a44..a8eed7aa5 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), -- cgit From bbea3a093f3e9be5052a2e64b6d5d0b909ae33ee Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 25 Jan 2011 13:05:47 -0800 Subject: Migration for provider firewall rules. --- .../sqlalchemy/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 000000000..7e5c55811 --- /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 + -- cgit From d6c6d8115b9dda07716d85fb1201cde0e907a9bd Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 26 Jan 2011 22:54:39 -0800 Subject: 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 9b43995fe..9652dcb9c 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 478032ce9..b019e8e8b 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 453824d82..5b5b329ed 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): -- cgit From ece7d2fa493e901c2a826e42a86ca93bb0afaed4 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 26 Jan 2011 22:56:34 -0800 Subject: 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 0b9b847a0..1008f32ae 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 5b5b329ed..cac6a4440 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""" -- cgit From d47886e16504cc92d0f9b33e02417229970d3efb Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 31 Jan 2011 16:02:29 -0500 Subject: 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 2f99a0bb1..ec6572d3f 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)) -- cgit From d552158b19bf1652da795e1681c9dc904bdc425b Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 1 Feb 2011 12:32:58 -0500 Subject: 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 161445b86..b16d53634 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): -- cgit From 5da75737ddfb876fd397b71986af42a5f8d0d04c Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 16 Feb 2011 12:49:04 -0500 Subject: 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 2a13c5466..f11f9f67e 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 -- cgit From dc887ab39641039817cdddce062bd398e69d07e5 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 5 Apr 2011 15:17:17 -0400 Subject: 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 c96d6e1fe..000000000 --- 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) -- cgit From 29b7a087efc64965e079733fe62e552fac70d13a Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 5 Apr 2011 15:17:37 -0400 Subject: 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 b0c301925..916c1db5e 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 f3d73103b..c5ada729a 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'] -- cgit From cd4748abfdc5014aac1d867c2ede261060375e2e Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 5 Apr 2011 19:31:12 -0400 Subject: 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 d11d21dad..322524a41 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 875d558a0..99a8849f1 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 c5ada729a..9bc7ca05a 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,) -- cgit From 2b7da3f2e9fa45f9bfca03bb6bcb713dcb6c58fe Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 5 Apr 2011 20:03:29 -0400 Subject: Testing for iptables manager changes. --- .../sqlalchemy/migrate_repo/versions/003_cactus.py | 75 ---------------------- .../versions/014_add_provider_fw_rules.py | 75 ++++++++++++++++++++++ nova/tests/test_network.py | 30 +++++++++ 3 files changed, 105 insertions(+), 75 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py b/nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py deleted file mode 100644 index 5aa30f7a8..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py +++ /dev/null @@ -1,75 +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. - -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 diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py new file mode 100644 index 000000000..5aa30f7a8 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py @@ -0,0 +1,75 @@ +# 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 diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 77f6aaff3..a67c846dd 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)) -- cgit From 26d2a6ca8939156e8957e31dd17906070283ff24 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 5 Apr 2011 20:07:46 -0400 Subject: 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 9bc7ca05a..0d92e2e70 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: -- cgit From 2b79fa82872c55368167fc7433cb28a2369f5191 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 7 Apr 2011 01:42:49 -0400 Subject: 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 0b27854ef..c0c2bcd0d 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 958c8e3e2..34ecd09c6 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 0d92e2e70..38ba21521 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 -- cgit From 2278f2886d369af285f914a7b5883d3a7a5727cc Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 7 Apr 2011 13:22:03 -0400 Subject: 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 34ecd09c6..7c3dbf654 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', -- cgit From 6365f4141715c6806d40698add59c294613bc063 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 25 May 2011 09:35:35 -0400 Subject: Added model for InstanceTypeMetadata --- nova/db/sqlalchemy/models.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 1215448f8..0c0a5a479 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -635,6 +635,19 @@ class InstanceMetadata(BASE, NovaBase): 'InstanceMetadata.instance_id == Instance.id,' 'InstanceMetadata.deleted == False)') +class InstanceTypeMetadata(BASE, NovaBase): + """Represents a metadata key/value pair for an instance_type""" + __tablename__ = 'instance_type_metadata' + id = Column(Integer, primary_key=True) + key = Column(String(255)) + value = Column(String(255)) + instance_type_id = Column(Integer, ForeignKey('instance_types.id'), nullable=False) + instance = relationship(InstanceType, backref="metadata", + foreign_keys=instance_type_id, + primaryjoin='and_(' + 'InstanceTypeMetadata.instance_type_id == InstanceType.id,' + 'InstanceTypeMetadata.deleted == False)') + class Zone(BASE, NovaBase): """Represents a child zone of this zone.""" -- cgit From f694bc15b921cd4affa5b5b63ed0eb9073516b44 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 25 May 2011 12:07:40 -0400 Subject: Adding the migrate code to add the new table --- .../versions/019_add_instance_type_metadata.py | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py b/nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py new file mode 100644 index 000000000..4ceb8c36d --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py @@ -0,0 +1,68 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 University of Southern California +# +# 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 Boolean, Column, DateTime, ForeignKey, Integer +from sqlalchemy import MetaData, String, Table +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. +instance_types = Table('instance_types', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + + + +# +# New Tables +# + +instance_type_metadata_table = Table('instance_type_metadata', 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('instance_type_id', + Integer(), + ForeignKey('instance_types.id'), + nullable=False), + Column('key', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False)), + Column('value', + 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 (instance_type_metadata_table, ): + try: + table.create() + except Exception: + logging.info(repr(table)) + logging.exception('Exception while creating table') + raise + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + for table in (instance_type_metadata_table, ): + table.drop() + -- cgit From 354b2303e684e50cccb28f7b8af13b19a27e0415 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 25 May 2011 12:15:58 -0400 Subject: Fixing the InstanceTypesMetadata table definition --- nova/db/sqlalchemy/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 0c0a5a479..a32b22b39 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -642,10 +642,10 @@ class InstanceTypeMetadata(BASE, NovaBase): key = Column(String(255)) value = Column(String(255)) instance_type_id = Column(Integer, ForeignKey('instance_types.id'), nullable=False) - instance = relationship(InstanceType, backref="metadata", + instance_type = relationship(InstanceTypes, backref="metadata", foreign_keys=instance_type_id, primaryjoin='and_(' - 'InstanceTypeMetadata.instance_type_id == InstanceType.id,' + 'InstanceTypeMetadata.instance_type_id == InstanceTypes.id,' 'InstanceTypeMetadata.deleted == False)') -- cgit From 7aa62856ebff4242b123f5e0888276237176d066 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 25 May 2011 12:19:52 -0400 Subject: InstanceTypesMetadata is now registered --- nova/db/sqlalchemy/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index a32b22b39..e20feb71a 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -666,12 +666,12 @@ def register_models(): connection is lost and needs to be reestablished. """ from sqlalchemy import create_engine - models = (Service, Instance, InstanceActions, InstanceTypes, + models = (Service, Instance, InstanceActions, InstanceTypes, Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp, Network, SecurityGroup, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, Project, Certificate, ConsolePool, Console, Zone, - InstanceMetadata, Migration) + InstanceMetadata, InstanceTypesMetadata, Migration) engine = create_engine(FLAGS.sql_connection, echo=False) for model in models: model.metadata.create_all(engine) -- cgit From 6141221d01026ce277d34ae329767139178b1ea0 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 25 May 2011 12:29:23 -0400 Subject: pep8 fixes --- .../versions/019_add_instance_type_metadata.py | 5 ++--- nova/db/sqlalchemy/models.py | 14 ++++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py b/nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py index 4ceb8c36d..22b741614 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py @@ -26,8 +26,6 @@ instance_types = Table('instance_types', meta, Column('id', Integer(), primary_key=True, nullable=False), ) - - # # New Tables # @@ -49,6 +47,7 @@ instance_type_metadata_table = Table('instance_type_metadata', meta, 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 @@ -61,8 +60,8 @@ def upgrade(migrate_engine): logging.exception('Exception while creating table') raise + def downgrade(migrate_engine): # Operations to reverse the above upgrade go here. for table in (instance_type_metadata_table, ): table.drop() - diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index e20feb71a..f806becb5 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -635,18 +635,20 @@ class InstanceMetadata(BASE, NovaBase): 'InstanceMetadata.instance_id == Instance.id,' 'InstanceMetadata.deleted == False)') + class InstanceTypeMetadata(BASE, NovaBase): """Represents a metadata key/value pair for an instance_type""" __tablename__ = 'instance_type_metadata' id = Column(Integer, primary_key=True) key = Column(String(255)) value = Column(String(255)) - instance_type_id = Column(Integer, ForeignKey('instance_types.id'), nullable=False) + instance_type_id = Column(Integer, ForeignKey('instance_types.id'), + nullable=False) instance_type = relationship(InstanceTypes, backref="metadata", - foreign_keys=instance_type_id, - primaryjoin='and_(' - 'InstanceTypeMetadata.instance_type_id == InstanceTypes.id,' - 'InstanceTypeMetadata.deleted == False)') + foreign_keys=instance_type_id, + primaryjoin='and_(' + 'InstanceTypeMetadata.instance_type_id == InstanceTypes.id,' + 'InstanceTypeMetadata.deleted == False)') class Zone(BASE, NovaBase): @@ -666,7 +668,7 @@ def register_models(): connection is lost and needs to be reestablished. """ from sqlalchemy import create_engine - models = (Service, Instance, InstanceActions, InstanceTypes, + models = (Service, Instance, InstanceActions, InstanceTypes, Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp, Network, SecurityGroup, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, -- cgit From 5a95b6923a7ca37d292edc0aceb5e4b34a1ccbaf Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 25 May 2011 15:48:03 -0400 Subject: Initial tests --- nova/tests/test_instance_types_metadata.py | 62 ++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 nova/tests/test_instance_types_metadata.py diff --git a/nova/tests/test_instance_types_metadata.py b/nova/tests/test_instance_types_metadata.py new file mode 100644 index 000000000..a22bf2b77 --- /dev/null +++ b/nova/tests/test_instance_types_metadata.py @@ -0,0 +1,62 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 University of Southern California +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Unit Tests for instance types metadata code +""" + + +from nova import test +from nova.db.sqlalchemy.session import get_session +from nova.db.sqlalchemy import models + + +class InstanceTypeMetadataTestCase(test.TestCase): + + def setUp(self): + super(InstanceTypeMetadataTestCase, self).setUp() + values = dict(memory_mb=22000, + vcpus=8, + local_gb=1690, + flavorid=105) + metadata = dict(cpu_arch="x86_64", + cpu_info=dict(model="Nehalem"), + xpu_arch="fermi", + xpus=2, + xpu_info=dict(model="Tesla 2050", gcores="448"), + net_arch="ethernet", + net_mbps=10000) + + metadata_refs = [] + for k,v in metadata.iteritems(): + metadata_ref = models.InstanceTypeMetadata() + metadata_ref['key'] = k + metadata_ref['value'] = v + metadata_refs.append(metadata_ref) + values['metadata'] = metadata_refs + + instance_type_ref = models.InstanceTypes() + instance_type_ref.update(values) + + session = get_session() + with session.begin(): + instance_type_ref.save(session=session) + # Add cg1.4xlarge + + + def test_foo(self): + # Add a new instance type cg1 + # Add the metadata + # Retrieve the metadata + pass -- cgit From 775566067a1f764baec5036357ad47a57316da03 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 25 May 2011 16:58:12 -0400 Subject: Fixed a typo --- nova/db/sqlalchemy/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f806becb5..d57feddd5 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -673,7 +673,7 @@ def register_models(): Network, SecurityGroup, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, Project, Certificate, ConsolePool, Console, Zone, - InstanceMetadata, InstanceTypesMetadata, Migration) + InstanceMetadata, InstanceTypeMetadata, Migration) engine = create_engine(FLAGS.sql_connection, echo=False) for model in models: model.metadata.create_all(engine) -- cgit From 0be9e06c09c1d08802a8963e34090b5fcedb19be Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:35:58 -0400 Subject: 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 0d08aba7a..3c7a60277 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} -- cgit From ace6c5f82810c9984fc3e0bb24a9c37c00e8ac39 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:36:45 -0400 Subject: 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 e9e8e10e7..660c7666f 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. -- cgit From b114b618e4ce112589773cbe63daf0ee70b900be Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:39:03 -0400 Subject: 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 939e02a84..5f5d46b86 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 ################### -- cgit From a8f2a6444f4198db5fd5f05f7d2ae94e953a0fa2 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:40:08 -0400 Subject: Move migration to fix ordering. --- .../versions/014_add_provider_fw_rules.py | 75 ---------------------- .../versions/019_add_provider_fw_rules.py | 75 ++++++++++++++++++++++ 2 files changed, 75 insertions(+), 75 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py deleted file mode 100644 index 5aa30f7a8..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/014_add_provider_fw_rules.py +++ /dev/null @@ -1,75 +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. - -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 diff --git a/nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py new file mode 100644 index 000000000..5aa30f7a8 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py @@ -0,0 +1,75 @@ +# 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 -- cgit From dfd6e6e3a46c2fbbb4e771d38396348c9659a0bd Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:44:18 -0400 Subject: 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 dc2dd1219..dfeda3814 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 - -- cgit From 459864dc0a05e6a0db642e9cb80ceade7b000ce8 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:45:46 -0400 Subject: 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 fd4c120a6..4e0df323d 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, -- cgit From a5c9f44295df4054e9afb135aaa76c5e34cc3624 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 11:53:25 -0400 Subject: 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 4e0df323d..5bdc0c1c6 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 -- cgit From 93bfea42bdd594030c8ae046f87291ff184ef3f6 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 12:09:04 -0400 Subject: 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 1743b09a2..0eaf069fb 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 5bdc0c1c6..c4192fac0 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 -- cgit From 168d4556365fafcb34e9536f7f932d4da24b30a6 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 12:48:07 -0400 Subject: 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 0eaf069fb..bb7c8cf33 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): -- cgit From 1fd3f5a6edf911dc84e11130bc2c590567d780c3 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 26 May 2011 17:05:55 -0400 Subject: 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 3c7a60277..1d4cffff3 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} -- cgit From 65c26759fa8607f89614a6c90a55172805359538 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 27 May 2011 13:03:36 -0400 Subject: Adding accessor methods for instance type metadata --- nova/db/api.py | 17 +++++++++++++ nova/db/sqlalchemy/api.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++ nova/exception.py | 5 ++++ 3 files changed, 85 insertions(+) diff --git a/nova/db/api.py b/nova/db/api.py index ef8aa1143..4700d7f30 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1208,3 +1208,20 @@ def instance_metadata_delete(context, instance_id, key): def instance_metadata_update_or_create(context, instance_id, metadata): """Create or update instance metadata.""" IMPL.instance_metadata_update_or_create(context, instance_id, metadata) + +#################### + + +def instance_type_metadata_get(context, instance_type_id): + """Get all metadata for an instance type.""" + return IMPL.instance_type_metadata_get(context, instance_type_id) + + +def instance_type_metadata_delete(context, instance_type_id, key): + """Delete the given metadata item.""" + IMPL.instance_type_metadata_delete(context, instance_type_id, key) + + +def instance_type_metadata_update_or_create(context, instance_type_id, metadata): + """Create or update instance type metadata.""" + IMPL.instance_type_metadata_update_or_create(context, instance_type_id, metadata) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b53e81053..9d33ff61f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2579,3 +2579,66 @@ def instance_metadata_update_or_create(context, instance_id, metadata): "deleted": 0}) meta_ref.save(session=session) return metadata + +#################### + +@require_context +def instance_type_metadata_get(context, instance_type_id): + session = get_session() + + meta_results = session.query(models.InstanceTypeMetadata).\ + filter_by(instance_type_id=instance_type_id).\ + filter_by(deleted=False).\ + all() + + meta_dict = {} + for i in meta_results: + meta_dict[i['key']] = i['value'] + return meta_dict + + +@require_context +def instance_type_metadata_delete(context, instance_type_id, key): + session = get_session() + session.query(models.InstanceTypeMetadata).\ + filter_by(instance_type_id=instance_type_id).\ + filter_by(key=key).\ + filter_by(deleted=False).\ + update({'deleted': True, + 'deleted_at': datetime.datetime.utcnow(), + 'updated_at': literal_column('updated_at')}) + + +@require_context +def instance_type_metadata_get_item(context, instance_type_id, key): + session = get_session() + + meta_result = session.query(models.InstanceMetadata).\ + filter_by(instance_type_id=instance_type_id).\ + filter_by(key=key).\ + filter_by(deleted=False).\ + first() + + if not meta_result: + raise exception.InstanceTypeMetadataNotFound(metadata_key=key, + instance_type_id=instance_type_id) + return meta_result + + +@require_context +def instance_type_metadata_update_or_create(context, instance_type_id, + metadata): + session = get_session() + meta_ref = None + for key, value in metadata.iteritems(): + try: + meta_ref = instance_type_metadata_get_item(context, + instance_type_id, key, + session) + except: + meta_ref = models.InstanceTypeMetadata() + meta_ref.update({"key": key, "value": value, + "instance_type_id": instance_id, + "deleted": 0}) + meta_ref.save(session=session) + return metadata diff --git a/nova/exception.py b/nova/exception.py index 56c20d111..4e5f750c1 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -474,6 +474,11 @@ class InstanceMetadataNotFound(NotFound): message = _("Instance %(instance_id)s has no metadata with " "key %(metadata_key)s.") +class InstanceTypeMetadataNotFound(NotFound): + message = _("Instance Type %(instance_type_id)s has no metadata with " + "key %(metadata_key)s.") + + class LDAPObjectNotFound(NotFound): message = _("LDAP object could not be found") -- cgit From 556ccd9b0d9c7809395b7720e5dcfb6af514f69f Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 27 May 2011 13:03:57 -0400 Subject: Changed metadata to meta to avoid sqlalchemy collision --- nova/db/sqlalchemy/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index d57feddd5..b3e87e77c 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -644,7 +644,7 @@ class InstanceTypeMetadata(BASE, NovaBase): value = Column(String(255)) instance_type_id = Column(Integer, ForeignKey('instance_types.id'), nullable=False) - instance_type = relationship(InstanceTypes, backref="metadata", + instance_type = relationship(InstanceTypes, backref="meta", foreign_keys=instance_type_id, primaryjoin='and_(' 'InstanceTypeMetadata.instance_type_id == InstanceTypes.id,' -- cgit From de0122eaae70c92db47f9457b162cc48c5d5f755 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 27 May 2011 13:10:19 -0400 Subject: Adding test code --- nova/tests/test_instance_types_metadata.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/nova/tests/test_instance_types_metadata.py b/nova/tests/test_instance_types_metadata.py index a22bf2b77..06c11a7cf 100644 --- a/nova/tests/test_instance_types_metadata.py +++ b/nova/tests/test_instance_types_metadata.py @@ -16,7 +16,9 @@ Unit Tests for instance types metadata code """ +import nova.db.api +from nova import context from nova import test from nova.db.sqlalchemy.session import get_session from nova.db.sqlalchemy import models @@ -26,15 +28,16 @@ class InstanceTypeMetadataTestCase(test.TestCase): def setUp(self): super(InstanceTypeMetadataTestCase, self).setUp() - values = dict(memory_mb=22000, + values = dict(name="cg1.4xlarge", + memory_mb=22000, vcpus=8, local_gb=1690, flavorid=105) metadata = dict(cpu_arch="x86_64", - cpu_info=dict(model="Nehalem"), + cpu_model="Nehalem", xpu_arch="fermi", xpus=2, - xpu_info=dict(model="Tesla 2050", gcores="448"), + xpu_model="Tesla 2050", net_arch="ethernet", net_mbps=10000) @@ -44,19 +47,22 @@ class InstanceTypeMetadataTestCase(test.TestCase): metadata_ref['key'] = k metadata_ref['value'] = v metadata_refs.append(metadata_ref) - values['metadata'] = metadata_refs + values['meta'] = metadata_refs instance_type_ref = models.InstanceTypes() instance_type_ref.update(values) + session = get_session() with session.begin(): instance_type_ref.save(session=session) - # Add cg1.4xlarge + self.instance_type_id = instance_type_ref.id + def test_instance_type_metadata_get(self): + self.assertEquals( \ + nova.db.api.instance_type_metadata_get(context.get_admin_context(), + self.instance_type_id), + {'foo' : 'bar'}) - def test_foo(self): - # Add a new instance type cg1 - # Add the metadata - # Retrieve the metadata - pass + + \ No newline at end of file -- cgit From a28590d77474f7a43d704385cc3815f2c879f397 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 27 May 2011 13:29:51 -0400 Subject: Added a unit test --- nova/tests/test_instance_types_metadata.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/nova/tests/test_instance_types_metadata.py b/nova/tests/test_instance_types_metadata.py index 06c11a7cf..5d8364c3a 100644 --- a/nova/tests/test_instance_types_metadata.py +++ b/nova/tests/test_instance_types_metadata.py @@ -16,9 +16,8 @@ Unit Tests for instance types metadata code """ -import nova.db.api - from nova import context +from nova import db from nova import test from nova.db.sqlalchemy.session import get_session from nova.db.sqlalchemy import models @@ -59,10 +58,17 @@ class InstanceTypeMetadataTestCase(test.TestCase): self.instance_type_id = instance_type_ref.id def test_instance_type_metadata_get(self): - self.assertEquals( \ - nova.db.api.instance_type_metadata_get(context.get_admin_context(), - self.instance_type_id), - {'foo' : 'bar'}) + expected_metadata = dict(cpu_arch="x86_64", + cpu_model="Nehalem", + xpu_arch="fermi", + xpus="2", + xpu_model="Tesla 2050", + net_arch="ethernet", + net_mbps="10000") + retrieved_metadata = db.api.instance_type_metadata_get( + context.get_admin_context(), + self.instance_type_id) + self.assertEquals(expected_metadata, retrieved_metadata) \ No newline at end of file -- cgit From 842bb180f04d8b1fbacbca77171f11bfe3d68cdd Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 27 May 2011 13:39:37 -0400 Subject: Added delete instance metadata unit test --- nova/tests/test_instance_types_metadata.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/nova/tests/test_instance_types_metadata.py b/nova/tests/test_instance_types_metadata.py index 5d8364c3a..c83c5bfca 100644 --- a/nova/tests/test_instance_types_metadata.py +++ b/nova/tests/test_instance_types_metadata.py @@ -47,11 +47,8 @@ class InstanceTypeMetadataTestCase(test.TestCase): metadata_ref['value'] = v metadata_refs.append(metadata_ref) values['meta'] = metadata_refs - instance_type_ref = models.InstanceTypes() instance_type_ref.update(values) - - session = get_session() with session.begin(): instance_type_ref.save(session=session) @@ -65,10 +62,22 @@ class InstanceTypeMetadataTestCase(test.TestCase): xpu_model="Tesla 2050", net_arch="ethernet", net_mbps="10000") - retrieved_metadata = db.api.instance_type_metadata_get( + actual_metadata = db.api.instance_type_metadata_get( context.get_admin_context(), self.instance_type_id) - self.assertEquals(expected_metadata, retrieved_metadata) - - - \ No newline at end of file + self.assertEquals(expected_metadata, actual_metadata) + + def test_instance_type_metadata_delete(self): + expected_metadata = dict(cpu_arch="x86_64", + cpu_model="Nehalem", + xpu_arch="fermi", + xpus="2", + net_arch="ethernet", + net_mbps="10000") + db.api.instance_type_metadata_delete(context.get_admin_context(), + self.instance_type_id, + "xpu_model") + actual_metadata = db.api.instance_type_metadata_get( + context.get_admin_context(), + self.instance_type_id) + self.assertEquals(expected_metadata, actual_metadata) \ No newline at end of file -- cgit From aa18d32cf20c0bfbbc81ddf234ac59ecf310ccb0 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 27 May 2011 13:44:40 -0400 Subject: Added test for instance type metadata update --- nova/db/sqlalchemy/api.py | 2 +- nova/tests/test_instance_types_metadata.py | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9d33ff61f..14d53d9ed 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2638,7 +2638,7 @@ def instance_type_metadata_update_or_create(context, instance_type_id, except: meta_ref = models.InstanceTypeMetadata() meta_ref.update({"key": key, "value": value, - "instance_type_id": instance_id, + "instance_type_id": instance_type_id, "deleted": 0}) meta_ref.save(session=session) return metadata diff --git a/nova/tests/test_instance_types_metadata.py b/nova/tests/test_instance_types_metadata.py index c83c5bfca..d72a72e0d 100644 --- a/nova/tests/test_instance_types_metadata.py +++ b/nova/tests/test_instance_types_metadata.py @@ -80,4 +80,24 @@ class InstanceTypeMetadataTestCase(test.TestCase): actual_metadata = db.api.instance_type_metadata_get( context.get_admin_context(), self.instance_type_id) - self.assertEquals(expected_metadata, actual_metadata) \ No newline at end of file + self.assertEquals(expected_metadata, actual_metadata) + + def test_instance_type_metadata_update(self): + expected_metadata = dict(cpu_arch="x86_64", + cpu_model="Sandy Bridge", + xpu_arch="fermi", + xpus="2", + xpu_model="Tesla 2050", + net_arch="ethernet", + net_mbps="10000") + db.api.instance_type_metadata_update_or_create( + context.get_admin_context(), + self.instance_type_id, + dict(cpu_model="Sandy Bridge")) + actual_metadata = db.api.instance_type_metadata_get( + context.get_admin_context(), + self.instance_type_id) + self.assertEquals(expected_metadata, actual_metadata) + + + \ No newline at end of file -- cgit From bbdb8ed7148d08b790e0adf0d291fc3fbe0ae361 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 27 May 2011 13:54:19 -0400 Subject: Added test for instance type metadata create --- nova/tests/test_instance_types_metadata.py | 40 +++++++++++++++++++----------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/nova/tests/test_instance_types_metadata.py b/nova/tests/test_instance_types_metadata.py index d72a72e0d..085a951a1 100644 --- a/nova/tests/test_instance_types_metadata.py +++ b/nova/tests/test_instance_types_metadata.py @@ -36,10 +36,7 @@ class InstanceTypeMetadataTestCase(test.TestCase): cpu_model="Nehalem", xpu_arch="fermi", xpus=2, - xpu_model="Tesla 2050", - net_arch="ethernet", - net_mbps=10000) - + xpu_model="Tesla 2050") metadata_refs = [] for k,v in metadata.iteritems(): metadata_ref = models.InstanceTypeMetadata() @@ -54,14 +51,18 @@ class InstanceTypeMetadataTestCase(test.TestCase): instance_type_ref.save(session=session) self.instance_type_id = instance_type_ref.id + def tearDown(self): + # Remove the instance from the database + db.api.instance_type_purge(context.get_admin_context(), "cg1.4xlarge") + super(InstanceTypeMetadataTestCase, self).tearDown() + + def test_instance_type_metadata_get(self): expected_metadata = dict(cpu_arch="x86_64", cpu_model="Nehalem", xpu_arch="fermi", xpus="2", - xpu_model="Tesla 2050", - net_arch="ethernet", - net_mbps="10000") + xpu_model="Tesla 2050") actual_metadata = db.api.instance_type_metadata_get( context.get_admin_context(), self.instance_type_id) @@ -71,9 +72,7 @@ class InstanceTypeMetadataTestCase(test.TestCase): expected_metadata = dict(cpu_arch="x86_64", cpu_model="Nehalem", xpu_arch="fermi", - xpus="2", - net_arch="ethernet", - net_mbps="10000") + xpus="2") db.api.instance_type_metadata_delete(context.get_admin_context(), self.instance_type_id, "xpu_model") @@ -87,17 +86,30 @@ class InstanceTypeMetadataTestCase(test.TestCase): cpu_model="Sandy Bridge", xpu_arch="fermi", xpus="2", + xpu_model="Tesla 2050") + db.api.instance_type_metadata_update_or_create( + context.get_admin_context(), + self.instance_type_id, + dict(cpu_model="Sandy Bridge")) + actual_metadata = db.api.instance_type_metadata_get( + context.get_admin_context(), + self.instance_type_id) + self.assertEquals(expected_metadata, actual_metadata) + + def test_instance_type_metadata_create(self): + expected_metadata = dict(cpu_arch="x86_64", + cpu_model="Nehalem", + xpu_arch="fermi", + xpus="2", xpu_model="Tesla 2050", net_arch="ethernet", net_mbps="10000") db.api.instance_type_metadata_update_or_create( context.get_admin_context(), self.instance_type_id, - dict(cpu_model="Sandy Bridge")) + dict(net_arch="ethernet", + net_mbps=10000)) actual_metadata = db.api.instance_type_metadata_get( context.get_admin_context(), self.instance_type_id) self.assertEquals(expected_metadata, actual_metadata) - - - \ No newline at end of file -- cgit From 19e4a081509217ec04d92ae092917d590cbd8f30 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 27 May 2011 14:20:08 -0400 Subject: Modified instance_type_create to take metadata --- nova/db/sqlalchemy/api.py | 15 +++++++++++++++ nova/tests/test_instance_types_metadata.py | 23 +++++++---------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 14d53d9ed..523bc2a56 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2373,7 +2373,22 @@ def console_get(context, console_id, instance_id=None): @require_admin_context def instance_type_create(_context, values): + """Create a new instance type. In order to pass in metadata, + the values dict should contain a 'meta' key/value pair: + + {'meta' : {'k1': 'v1', 'k2': 'v2', ...}} + + """ try: + metadata = values.get('meta') + metadata_refs = [] + if metadata: + for k, v in metadata.iteritems(): + metadata_ref = models.InstanceTypeMetadata() + metadata_ref['key'] = k + metadata_ref['value'] = v + metadata_refs.append(metadata_ref) + values['meta'] = metadata_refs instance_type_ref = models.InstanceTypes() instance_type_ref.update(values) instance_type_ref.save() diff --git a/nova/tests/test_instance_types_metadata.py b/nova/tests/test_instance_types_metadata.py index 085a951a1..5263b1cba 100644 --- a/nova/tests/test_instance_types_metadata.py +++ b/nova/tests/test_instance_types_metadata.py @@ -27,6 +27,7 @@ class InstanceTypeMetadataTestCase(test.TestCase): def setUp(self): super(InstanceTypeMetadataTestCase, self).setUp() + self.context = context.get_admin_context() values = dict(name="cg1.4xlarge", memory_mb=22000, vcpus=8, @@ -36,27 +37,17 @@ class InstanceTypeMetadataTestCase(test.TestCase): cpu_model="Nehalem", xpu_arch="fermi", xpus=2, - xpu_model="Tesla 2050") - metadata_refs = [] - for k,v in metadata.iteritems(): - metadata_ref = models.InstanceTypeMetadata() - metadata_ref['key'] = k - metadata_ref['value'] = v - metadata_refs.append(metadata_ref) - values['meta'] = metadata_refs - instance_type_ref = models.InstanceTypes() - instance_type_ref.update(values) - session = get_session() - with session.begin(): - instance_type_ref.save(session=session) - self.instance_type_id = instance_type_ref.id + xpu_model="Tesla 2050") + values['meta'] = metadata + ref = db.api.instance_type_create(self.context, + values) + self.instance_type_id = ref.id def tearDown(self): - # Remove the instance from the database + # Remove the instance type from the database db.api.instance_type_purge(context.get_admin_context(), "cg1.4xlarge") super(InstanceTypeMetadataTestCase, self).tearDown() - def test_instance_type_metadata_get(self): expected_metadata = dict(cpu_arch="x86_64", cpu_model="Nehalem", -- cgit From 69a49743d733459e532a47e6b588045fe65a6145 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 27 May 2011 14:29:23 -0400 Subject: Fixing pep8 problems --- nova/db/api.py | 9 ++++++--- nova/db/sqlalchemy/api.py | 18 ++++++++++-------- nova/exception.py | 2 +- nova/tests/test_instance_types_metadata.py | 16 ++++++++-------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index 4700d7f30..3582b7a44 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1222,6 +1222,9 @@ def instance_type_metadata_delete(context, instance_type_id, key): IMPL.instance_type_metadata_delete(context, instance_type_id, key) -def instance_type_metadata_update_or_create(context, instance_type_id, metadata): - """Create or update instance type metadata.""" - IMPL.instance_type_metadata_update_or_create(context, instance_type_id, metadata) +def instance_type_metadata_update_or_create(context, instance_type_id, + metadata): + """Create or update instance type metadata. This adds or modifies the + key/value pairs specified in the metadata dict argument""" + IMPL.instance_type_metadata_update_or_create(context, instance_type_id, + metadata) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 523bc2a56..3e783f534 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2373,11 +2373,11 @@ def console_get(context, console_id, instance_id=None): @require_admin_context def instance_type_create(_context, values): - """Create a new instance type. In order to pass in metadata, - the values dict should contain a 'meta' key/value pair: - + """Create a new instance type. In order to pass in metadata, + the values dict should contain a 'meta' key/value pair: + {'meta' : {'k1': 'v1', 'k2': 'v2', ...}} - + """ try: metadata = values.get('meta') @@ -2597,6 +2597,7 @@ def instance_metadata_update_or_create(context, instance_id, metadata): #################### + @require_context def instance_type_metadata_get(context, instance_type_id): session = get_session() @@ -2635,19 +2636,20 @@ def instance_type_metadata_get_item(context, instance_type_id, key): first() if not meta_result: - raise exception.InstanceTypeMetadataNotFound(metadata_key=key, - instance_type_id=instance_type_id) + raise exception.\ + InstanceTypeMetadataNotFound(metadata_key=key, + instance_type_id=instance_type_id) return meta_result @require_context -def instance_type_metadata_update_or_create(context, instance_type_id, +def instance_type_metadata_update_or_create(context, instance_type_id, metadata): session = get_session() meta_ref = None for key, value in metadata.iteritems(): try: - meta_ref = instance_type_metadata_get_item(context, + meta_ref = instance_type_metadata_get_item(context, instance_type_id, key, session) except: diff --git a/nova/exception.py b/nova/exception.py index 4e5f750c1..a08311e19 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -474,12 +474,12 @@ class InstanceMetadataNotFound(NotFound): message = _("Instance %(instance_id)s has no metadata with " "key %(metadata_key)s.") + class InstanceTypeMetadataNotFound(NotFound): message = _("Instance Type %(instance_type_id)s has no metadata with " "key %(metadata_key)s.") - class LDAPObjectNotFound(NotFound): message = _("LDAP object could not be found") diff --git a/nova/tests/test_instance_types_metadata.py b/nova/tests/test_instance_types_metadata.py index 5263b1cba..1c4185888 100644 --- a/nova/tests/test_instance_types_metadata.py +++ b/nova/tests/test_instance_types_metadata.py @@ -24,7 +24,7 @@ from nova.db.sqlalchemy import models class InstanceTypeMetadataTestCase(test.TestCase): - + def setUp(self): super(InstanceTypeMetadataTestCase, self).setUp() self.context = context.get_admin_context() @@ -42,12 +42,12 @@ class InstanceTypeMetadataTestCase(test.TestCase): ref = db.api.instance_type_create(self.context, values) self.instance_type_id = ref.id - + def tearDown(self): # Remove the instance type from the database db.api.instance_type_purge(context.get_admin_context(), "cg1.4xlarge") super(InstanceTypeMetadataTestCase, self).tearDown() - + def test_instance_type_metadata_get(self): expected_metadata = dict(cpu_arch="x86_64", cpu_model="Nehalem", @@ -58,20 +58,20 @@ class InstanceTypeMetadataTestCase(test.TestCase): context.get_admin_context(), self.instance_type_id) self.assertEquals(expected_metadata, actual_metadata) - + def test_instance_type_metadata_delete(self): expected_metadata = dict(cpu_arch="x86_64", cpu_model="Nehalem", xpu_arch="fermi", xpus="2") - db.api.instance_type_metadata_delete(context.get_admin_context(), + db.api.instance_type_metadata_delete(context.get_admin_context(), self.instance_type_id, "xpu_model") actual_metadata = db.api.instance_type_metadata_get( context.get_admin_context(), self.instance_type_id) self.assertEquals(expected_metadata, actual_metadata) - + def test_instance_type_metadata_update(self): expected_metadata = dict(cpu_arch="x86_64", cpu_model="Sandy Bridge", @@ -86,13 +86,13 @@ class InstanceTypeMetadataTestCase(test.TestCase): context.get_admin_context(), self.instance_type_id) self.assertEquals(expected_metadata, actual_metadata) - + def test_instance_type_metadata_create(self): expected_metadata = dict(cpu_arch="x86_64", cpu_model="Nehalem", xpu_arch="fermi", xpus="2", - xpu_model="Tesla 2050", + xpu_model="Tesla 2050", net_arch="ethernet", net_mbps="10000") db.api.instance_type_metadata_update_or_create( -- cgit From add164c45db31baf8f12c3e5dede140c51a2e498 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 31 May 2011 14:10:29 -0400 Subject: 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 eb9626d08..23581ab49 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() -- cgit From 4d7dbdc96e30afbd19ab525e9667f6e3aaaafbe9 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 31 May 2011 16:20:07 -0400 Subject: Change version number of migration. --- .../versions/019_add_provider_fw_rules.py | 75 ---------------------- .../versions/021_add_provider_fw_rules.py | 75 ++++++++++++++++++++++ 2 files changed, 75 insertions(+), 75 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py deleted file mode 100644 index 5aa30f7a8..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/019_add_provider_fw_rules.py +++ /dev/null @@ -1,75 +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. - -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 diff --git a/nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py new file mode 100644 index 000000000..5aa30f7a8 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py @@ -0,0 +1,75 @@ +# 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 -- cgit From 2c1dd72060fccbe7f32a6aa08c1ce67476806680 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 31 May 2011 16:28:46 -0400 Subject: 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 c4192fac0..28cd9fe9c 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. -- cgit From 1a62d0a546e15b1e4e9dbc06b0bc422734594fdb Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 1 Jun 2011 11:38:10 -0400 Subject: Bumped migration number --- .../versions/019_add_instance_type_metadata.py | 67 ---------------------- .../versions/021_add_instance_type_metadata.py | 67 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 67 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_metadata.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py b/nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py deleted file mode 100644 index 22b741614..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/019_add_instance_type_metadata.py +++ /dev/null @@ -1,67 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 University of Southern California -# -# 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 Boolean, Column, DateTime, ForeignKey, Integer -from sqlalchemy import MetaData, String, Table -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. -instance_types = Table('instance_types', meta, - Column('id', Integer(), primary_key=True, nullable=False), - ) - -# -# New Tables -# - -instance_type_metadata_table = Table('instance_type_metadata', 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('instance_type_id', - Integer(), - ForeignKey('instance_types.id'), - nullable=False), - Column('key', - String(length=255, convert_unicode=False, assert_unicode=None, - unicode_error=None, _warn_on_bytestring=False)), - Column('value', - 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 (instance_type_metadata_table, ): - try: - table.create() - except Exception: - logging.info(repr(table)) - logging.exception('Exception while creating table') - raise - - -def downgrade(migrate_engine): - # Operations to reverse the above upgrade go here. - for table in (instance_type_metadata_table, ): - table.drop() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_metadata.py b/nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_metadata.py new file mode 100644 index 000000000..22b741614 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_metadata.py @@ -0,0 +1,67 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 University of Southern California +# +# 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 Boolean, Column, DateTime, ForeignKey, Integer +from sqlalchemy import MetaData, String, Table +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. +instance_types = Table('instance_types', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + +# +# New Tables +# + +instance_type_metadata_table = Table('instance_type_metadata', 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('instance_type_id', + Integer(), + ForeignKey('instance_types.id'), + nullable=False), + Column('key', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False)), + Column('value', + 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 (instance_type_metadata_table, ): + try: + table.create() + except Exception: + logging.info(repr(table)) + logging.exception('Exception while creating table') + raise + + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + for table in (instance_type_metadata_table, ): + table.drop() -- cgit From e52069015aa3ed0ba130f529ebfb93d53ea6053c Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 6 Jun 2011 15:32:48 -0400 Subject: Remove ipy from nova-manage and use netaddr --- bin/nova-manage | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index b0cd343f5..5ac0b8a0c 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -56,11 +56,11 @@ import gettext import glob import json +import netaddr import os import sys import time -import IPy # If ../nova/__init__.py exists, add ../ to Python search path, so that # it will override what happens to be installed in /usr/(local/)lib/python... @@ -513,7 +513,7 @@ class FloatingIpCommands(object): def create(self, host, range): """Creates floating ips for host by range arguments: host ip_range""" - for address in IPy.IP(range): + for address in netaddr.IPRange(range): db.floating_ip_create(context.get_admin_context(), {'address': str(address), 'host': host}) @@ -521,7 +521,7 @@ class FloatingIpCommands(object): def delete(self, ip_range): """Deletes floating ips by range arguments: range""" - for address in IPy.IP(ip_range): + for address in netaddr.IPRange(ip_range): db.floating_ip_destroy(context.get_admin_context(), str(address)) -- cgit From 93f61c18134e6b4091114a7126f52d74e0d20df8 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 6 Jun 2011 15:33:35 -0400 Subject: Remove ipy from nova/api/ec2/cloud.py and use netaddr --- nova/api/ec2/cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b7a9a8633..ceeaa3e48 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -23,7 +23,7 @@ datastore. """ import base64 -import IPy +import netaddr import os import urllib import tempfile @@ -444,7 +444,7 @@ class CloudController(object): elif cidr_ip: # If this fails, it throws an exception. This is what we want. cidr_ip = urllib.unquote(cidr_ip).decode() - IPy.IP(cidr_ip) + netaddr.IPNetwork(cidr_ip) values['cidr'] = cidr_ip else: values['cidr'] = '0.0.0.0/0' -- cgit From b7390c78054f839ac3340d771b9aae0109f5a98e Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 6 Jun 2011 15:34:51 -0400 Subject: Remove ipy from network code and replace with netaddr --- nova/network/linux_net.py | 4 ++-- nova/network/manager.py | 43 ++++++++++++++++++++--------------------- nova/tests/test_flat_network.py | 6 +++--- nova/tests/test_vlan_network.py | 6 +++--- 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 815cd29c3..7b6f4497d 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -20,6 +20,7 @@ import calendar import inspect +import netaddr import os from nova import db @@ -27,7 +28,6 @@ from nova import exception from nova import flags from nova import log as logging from nova import utils -from IPy import IP LOG = logging.getLogger("nova.linux_net") @@ -700,7 +700,7 @@ def _dnsmasq_cmd(net): '--listen-address=%s' % net['gateway'], '--except-interface=lo', '--dhcp-range=%s,static,120s' % net['dhcp_start'], - '--dhcp-lease-max=%s' % IP(net['cidr']).len(), + '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(net['cidr'])), '--dhcp-hostsfile=%s' % _dhcp_file(net['bridge'], 'conf'), '--dhcp-script=%s' % FLAGS.dhcpbridge, '--leasefile-ro'] diff --git a/nova/network/manager.py b/nova/network/manager.py index f726c4b26..f037312db 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -45,10 +45,9 @@ topologies. All of the network commands are issued to a subclass of import datetime import math +import netaddr import socket -import IPy - from nova import context from nova import db from nova import exception @@ -294,8 +293,8 @@ class NetworkManager(manager.SchedulerDependentManager): def create_networks(self, context, cidr, num_networks, network_size, cidr_v6, label, *args, **kwargs): """Create networks based on parameters.""" - fixed_net = IPy.IP(cidr) - fixed_net_v6 = IPy.IP(cidr_v6) + fixed_net = netaddr.IPNetwork(cidr) + fixed_net_v6 = netaddr.IPNetwork(cidr_v6) significant_bits_v6 = 64 network_size_v6 = 1 << 64 count = 1 @@ -304,15 +303,15 @@ class NetworkManager(manager.SchedulerDependentManager): start_v6 = index * network_size_v6 significant_bits = 32 - int(math.log(network_size, 2)) cidr = '%s/%s' % (fixed_net[start], significant_bits) - project_net = IPy.IP(cidr) + project_net = netaddr.IPNetwork(cidr) net = {} net['bridge'] = FLAGS.flat_network_bridge net['dns'] = FLAGS.flat_network_dns net['cidr'] = cidr - net['netmask'] = str(project_net.netmask()) - net['gateway'] = str(project_net[1]) - net['broadcast'] = str(project_net.broadcast()) - net['dhcp_start'] = str(project_net[2]) + net['netmask'] = str(project_net.netmask) + net['gateway'] = str(list(project_net)[1]) + net['broadcast'] = str(project_net.broadcast) + net['dhcp_start'] = str(list(project_net)[2]) if num_networks > 1: net['label'] = '%s_%d' % (label, count) else: @@ -323,9 +322,9 @@ class NetworkManager(manager.SchedulerDependentManager): cidr_v6 = '%s/%s' % (fixed_net_v6[start_v6], significant_bits_v6) net['cidr_v6'] = cidr_v6 - project_net_v6 = IPy.IP(cidr_v6) - net['gateway_v6'] = str(project_net_v6[1]) - net['netmask_v6'] = str(project_net_v6.prefixlen()) + project_net_v6 = netaddr.IPNetwork(cidr_v6) + net['gateway_v6'] = str(list(project_net_v6)[1]) + net['netmask_v6'] = str(project_net_v6._prefixlen) network_ref = self.db.network_create_safe(context, net) @@ -349,7 +348,7 @@ class NetworkManager(manager.SchedulerDependentManager): # to properties of the manager class? bottom_reserved = self._bottom_reserved_ips top_reserved = self._top_reserved_ips - project_net = IPy.IP(network_ref['cidr']) + project_net = netaddr.IPNetwork(network_ref['cidr']) num_ips = len(project_net) for index in range(num_ips): address = str(project_net[index]) @@ -539,13 +538,13 @@ class VlanManager(NetworkManager): ' the vlan start cannot be greater' ' than 4094')) - fixed_net = IPy.IP(cidr) - if fixed_net.len() < num_networks * network_size: + fixed_net = netaddr.IPNetwork(cidr) + if len(fixed_net) < num_networks * network_size: raise ValueError(_('The network range is not big enough to fit ' '%(num_networks)s. Network size is %(network_size)s' % locals())) - fixed_net_v6 = IPy.IP(cidr_v6) + fixed_net_v6 = netaddr.IPNetwork(cidr_v6) network_size_v6 = 1 << 64 significant_bits_v6 = 64 for index in range(num_networks): @@ -554,14 +553,14 @@ class VlanManager(NetworkManager): start_v6 = index * network_size_v6 significant_bits = 32 - int(math.log(network_size, 2)) cidr = "%s/%s" % (fixed_net[start], significant_bits) - project_net = IPy.IP(cidr) + project_net = netaddr.IPNetwork(cidr) net = {} net['cidr'] = cidr - net['netmask'] = str(project_net.netmask()) - net['gateway'] = str(project_net[1]) - net['broadcast'] = str(project_net.broadcast()) - net['vpn_private_address'] = str(project_net[2]) - net['dhcp_start'] = str(project_net[3]) + net['netmask'] = str(project_net.netmask) + net['gateway'] = str(list(project_net)[1]) + net['broadcast'] = str(project_net.broadcast) + net['vpn_private_address'] = str(list(project_net)[2]) + net['dhcp_start'] = str(list(project_net)[3]) net['vlan'] = vlan net['bridge'] = 'br%s' % vlan if(FLAGS.use_ipv6): diff --git a/nova/tests/test_flat_network.py b/nova/tests/test_flat_network.py index dcc617e25..8544019c0 100644 --- a/nova/tests/test_flat_network.py +++ b/nova/tests/test_flat_network.py @@ -18,7 +18,7 @@ """ Unit Tests for flat network code """ -import IPy +import netaddr import os import unittest @@ -45,8 +45,8 @@ class FlatNetworkTestCase(base.NetworkTestCase): self.context._project = self.projects[0] self.context.project_id = self.projects[0].id - pubnet = IPy.IP(flags.FLAGS.floating_range) - address = str(pubnet[0]) + pubnet = netaddr.IPRange(flags.FLAGS.floating_range) + address = str(list(pubnet)[0]) try: db.floating_ip_get_by_address(context.get_admin_context(), address) except exception.NotFound: diff --git a/nova/tests/test_vlan_network.py b/nova/tests/test_vlan_network.py index 063b81832..a1c8ab11c 100644 --- a/nova/tests/test_vlan_network.py +++ b/nova/tests/test_vlan_network.py @@ -18,7 +18,7 @@ """ Unit Tests for vlan network code """ -import IPy +import netaddr import os from nova import context @@ -44,8 +44,8 @@ class VlanNetworkTestCase(base.NetworkTestCase): # TODO(vish): better way of adding floating ips self.context._project = self.projects[0] self.context.project_id = self.projects[0].id - pubnet = IPy.IP(flags.FLAGS.floating_range) - address = str(pubnet[0]) + pubnet = netaddr.IPNetwork(flags.FLAGS.floating_range) + address = str(list(pubnet)[0]) try: db.floating_ip_get_by_address(context.get_admin_context(), address) except exception.NotFound: -- cgit From 46bd8cbd1358a44534a620408b828ad08eef9cec Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 6 Jun 2011 15:35:33 -0400 Subject: Remove ipy from virt code and replace with netaddr --- nova/virt/libvirt/connection.py | 3 +-- nova/virt/libvirt/netutils.py | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index c491418ae..da5911a1a 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -38,6 +38,7 @@ Supports KVM, LXC, QEMU, UML, and XEN. import hashlib import multiprocessing +import netaddr import os import random import shutil @@ -52,8 +53,6 @@ from xml.etree import ElementTree from eventlet import greenthread from eventlet import tpool -import IPy - from nova import context from nova import db from nova import exception diff --git a/nova/virt/libvirt/netutils.py b/nova/virt/libvirt/netutils.py index 4d596078a..0bad84f7c 100644 --- a/nova/virt/libvirt/netutils.py +++ b/nova/virt/libvirt/netutils.py @@ -21,7 +21,7 @@ """Network-releated utilities for supporting libvirt connection code.""" -import IPy +import netaddr from nova import context from nova import db @@ -34,18 +34,18 @@ FLAGS = flags.FLAGS def get_net_and_mask(cidr): - net = IPy.IP(cidr) - return str(net.net()), str(net.netmask()) + net = netaddr.IPNetwork(cidr) + return str(net.ip), str(net.netmask) def get_net_and_prefixlen(cidr): - net = IPy.IP(cidr) - return str(net.net()), str(net.prefixlen()) + net = netaddr.IPNetwork(cidr) + return str(net.ip), str(net._prefixlen) def get_ip_version(cidr): - net = IPy.IP(cidr) - return int(net.version()) + net = netaddr.IPNetwork(cidr) + return int(net.version) def get_network_info(instance): -- cgit From de42ebea7a8d055caeddd40edcdcaa6d64f1548e Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 6 Jun 2011 17:03:50 -0400 Subject: Convert stray import IPy --- nova/tests/test_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 77f6aaff3..da92f2066 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -18,7 +18,7 @@ """ Unit Tests for network code """ -import IPy +import netaddr import os from nova import test -- cgit From 9effe34506c5a23f21b9132ff97a2217b0c99735 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 6 Jun 2011 17:04:12 -0400 Subject: Dropped requirement for IPy --- tools/pip-requires | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/pip-requires b/tools/pip-requires index e81ef944a..af8ce80cd 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,7 +1,6 @@ SQLAlchemy==0.6.3 pep8==0.5.0 pylint==0.19 -IPy==0.70 Cheetah==2.4.4 M2Crypto==0.20.2 amqplib==0.6.1 -- cgit From 4b0b0361ed8d231844344d014412f7b647baae0b Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 6 Jun 2011 17:05:28 -0400 Subject: Remove more stray import IPy --- nova/tests/network/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/network/base.py b/nova/tests/network/base.py index b06271c99..f65416824 100644 --- a/nova/tests/network/base.py +++ b/nova/tests/network/base.py @@ -18,7 +18,7 @@ """ Base class of Unit Tests for all network models """ -import IPy +import netaddr import os from nova import context -- cgit From 0c3c0ef6e0604e24ab3f2ec25554a867fe64bd45 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Tue, 7 Jun 2011 10:39:30 -0400 Subject: Use IPNetwork rather than IPRange --- bin/nova-manage | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 5ac0b8a0c..1f8ccf268 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -513,7 +513,7 @@ class FloatingIpCommands(object): def create(self, host, range): """Creates floating ips for host by range arguments: host ip_range""" - for address in netaddr.IPRange(range): + for address in netaddr.IPNetwork(range): db.floating_ip_create(context.get_admin_context(), {'address': str(address), 'host': host}) @@ -521,7 +521,7 @@ class FloatingIpCommands(object): def delete(self, ip_range): """Deletes floating ips by range arguments: range""" - for address in netaddr.IPRange(ip_range): + for address in netaddr.IPNetwork(ip_range): db.floating_ip_destroy(context.get_admin_context(), str(address)) -- cgit From 781f3f07ebc3236404e33189e0a76cbb877dff18 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 10 Jun 2011 16:35:35 -0400 Subject: 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 +- nova/tests/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 ebfe2bde9..77028be28 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 ddf4e6fa9..d1402b314 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 56be0f1cc..bc1903ca5 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) -- cgit From 0bcb15317fede5c17c77c187e1cd9a68a0c8030c Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 10 Jun 2011 22:32:33 -0400 Subject: 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 28cd9fe9c..331c73b47 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: -- cgit From 90b3f8eb676681afa491c8bac85a45fdc87b099b Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sat, 11 Jun 2011 14:50:21 -0400 Subject: Move migration. --- .../versions/021_add_provider_fw_rules.py | 75 ---------------------- .../versions/023_add_provider_firewall_rules.py | 75 ++++++++++++++++++++++ 2 files changed, 75 insertions(+), 75 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py deleted file mode 100644 index 5aa30f7a8..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/021_add_provider_fw_rules.py +++ /dev/null @@ -1,75 +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. - -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 diff --git a/nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py new file mode 100644 index 000000000..5aa30f7a8 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py @@ -0,0 +1,75 @@ +# 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 -- cgit From 27fb2cf245ae87282f3aefdf2ae4740866529101 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sat, 11 Jun 2011 15:14:46 -0400 Subject: 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 07c8e81f1..ee94d3c17 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() -- cgit From d1b6ebb4009e13ac2cf2309275a66a634e4f9171 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sat, 11 Jun 2011 16:42:58 -0400 Subject: Add ability to list ip blocks. --- nova/api/ec2/admin.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index fcf7f674c..37ae2e648 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -369,3 +369,11 @@ class AdminController(object): raise exception.ApiError(_('Duplicate rule')) self.compute_api.trigger_provider_fw_rules_refresh(context) return {'status': 'OK', 'message': 'Added %s rules' % rules_added} + + def describe_external_address_blocks(self, context): + blocks = db.provider_fw_rule_get_all(context) + # NOTE(todd): use a set since we have icmp/udp/tcp rules with same cidr + blocks = set([b.cidr for b in blocks]) + blocks = [{'cidr': b} for b in blocks] + return {'externalIpBlockInfo': + list(sorted(blocks, key=lambda k: k['cidr']))} -- cgit From ed3914eafa7d076fdcc03ee958f77528bcf20603 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sat, 11 Jun 2011 18:03:45 -0400 Subject: Add a method to delete provider firewall rules. --- nova/api/ec2/admin.py | 12 ++++++++++++ nova/db/api.py | 10 ++++++++++ nova/db/sqlalchemy/api.py | 21 +++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 37ae2e648..da982d8dc 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -377,3 +377,15 @@ class AdminController(object): blocks = [{'cidr': b} for b in blocks] return {'externalIpBlockInfo': list(sorted(blocks, key=lambda k: k['cidr']))} + + def remove_external_address_block(self, context, cidr): + LOG.audit(_('Removing ip block from %s'), cidr, context=context) + cidr = urllib.unquote(cidr).decode() + # raise if invalid + IPy.IP(cidr) + rules = db.provider_fw_rule_get_all_by_cidr(context, cidr) + for rule in rules: + db.provider_fw_rule_destroy(context, rule['id']) + if rules: + self.compute_api.trigger_provider_fw_rules_refresh(context) + return {'status': 'OK', 'message': 'Deleted %s rules' % len(rules)} diff --git a/nova/db/api.py b/nova/db/api.py index 798b2881c..62368c438 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1004,6 +1004,16 @@ def provider_fw_rule_get_all(context): return IMPL.provider_fw_rule_get_all(context) +def provider_fw_rule_get_all_by_cidr(context, cidr): + """Get all provider-level firewall rules.""" + return IMPL.provider_fw_rule_get_all_by_cidr(context) + + +def provider_fw_rule_destroy(context, rule_id): + """Delete a provider firewall rule from the database.""" + return IMPL.provider_fw_rule_destroy(context) + + ################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9612e456b..c8802e354 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2093,6 +2093,7 @@ def provider_fw_rule_create(context, rule): return fw_rule_ref +@require_admin_context def provider_fw_rule_get_all(context): session = get_session() return session.query(models.ProviderFirewallRule).\ @@ -2100,6 +2101,26 @@ def provider_fw_rule_get_all(context): all() +@require_admin_context +def provider_fw_rule_get_all_by_cidr(context, cidr): + session = get_session() + return session.query(models.ProviderFirewallRule).\ + filter_by(deleted=can_read_deleted(context)).\ + filter_by(cidr=cidr).\ + all() + + +@require_admin_context +def provider_fw_rule_destroy(context, rule_id): + session = get_session() + with session.begin(): + session.query(models.ProviderFirewallRule).\ + filter_by(id=rule_id).\ + update({'deleted': True, + 'deleted_at': utils.utcnow(), + 'updated_at': literal_column('updated_at')}) + + ################### -- cgit From 0a6aeacfedfd5e666e109b54c5c03908eeb47c31 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Sat, 11 Jun 2011 18:38:44 -0400 Subject: fix method chaining in database layer to pass right parameters. --- nova/db/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index 62368c438..6ff6ec2f3 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1006,12 +1006,12 @@ def provider_fw_rule_get_all(context): def provider_fw_rule_get_all_by_cidr(context, cidr): """Get all provider-level firewall rules.""" - return IMPL.provider_fw_rule_get_all_by_cidr(context) + return IMPL.provider_fw_rule_get_all_by_cidr(context, cidr) def provider_fw_rule_destroy(context, rule_id): """Delete a provider firewall rule from the database.""" - return IMPL.provider_fw_rule_destroy(context) + return IMPL.provider_fw_rule_destroy(context, rule_id) ################### -- cgit From 7a2712ebf74e5565663a6723a992151f71255eff Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 14 Jun 2011 11:34:03 -0700 Subject: 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 fcf7f674c..343bc61c4 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}) -- cgit From b10621f5f85cccde3d159afddb78398544d4c32e Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 16 Jun 2011 17:30:36 +0400 Subject: First implementation of FloatingIpController --- nova/db/api.py | 3 +++ nova/db/sqlalchemy/api.py | 23 +++++++++++++++++++++++ nova/network/api.py | 4 ++++ 3 files changed, 30 insertions(+) diff --git a/nova/db/api.py b/nova/db/api.py index 4e0aa60a2..8348d90ae 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -223,6 +223,9 @@ def certificate_update(context, certificate_id, values): ################### +def floating_ip_get(context, floating_ip_id): + return IMPL.floating_ip_get(context, floating_ip_id) + def floating_ip_allocate_address(context, host, project_id): """Allocate free floating ip and return the address. diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 73870d2f3..c3b517492 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -428,6 +428,29 @@ def certificate_update(context, certificate_id, values): ################### +@require_context +def floating_ip_get(context, ip_id): + session = get_session() + result = None + if is_admin_context(context): + result = session.query(models.FloatingIp).\ + options(joinedload('fixed_ip')).\ + options(joinedload_all('fixed_ip.instance')).\ + filter_by(id=ip_id).\ + filter_by(deleted=can_read_deleted(context)).\ + first() + elif is_user_context(context): + result = session.query(models.FloatingIp).\ + options(joinedload('fixed_ip')).\ + options(joinedload_all('fixed_ip.instance')).\ + filter_by(project_id=context.project_id).\ + filter_by(id=ip_id).\ + filter_by(deleted=False).\ + first() + if not result: + raise exception.FloatingIpNotFound() + + return result @require_context diff --git a/nova/network/api.py b/nova/network/api.py index e2eacdf42..e5c4d6718 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -34,6 +34,10 @@ LOG = logging.getLogger('nova.network') class API(base.Base): """API for interacting with the network manager.""" + def get(self, context, id): + rv = self.db.floating_ip_get(context) + return dict(rv.iteritems()) + def allocate_floating_ip(self, context): if quota.allowed_floating_ips(context, 1) < 1: LOG.warn(_('Quota exceeeded for %s, tried to allocate ' -- cgit From 25009974df913c3e5c071b53a6004ae35e37d26b Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 16 Jun 2011 17:37:45 +0400 Subject: First implementation of FloatingIpController --- nova/api/openstack/contrib/floating_ips.py | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 nova/api/openstack/contrib/floating_ips.py diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py new file mode 100644 index 000000000..b67b8b4ec --- /dev/null +++ b/nova/api/openstack/contrib/floating_ips.py @@ -0,0 +1,54 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# Copyright 2011 Grid Dynamics +# +# 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 webob import exc + +from nova import exception +from nova import network +from nova.api.openstack import faults + +def _translate_floating_ip_detail_view(context, floating_ip): + #TODO(enugaev) implement view + return None + +class FloatingIPController(object): + """The Volumes API controller for the OpenStack API.""" + + _serialization_metadata = { + 'application/xml': { + "attributes": { + "floating_ip": [ + "id", + "ip", + "instance_id", + "fixed_ip", + ]}}} + + def __init__(self): + self.network_api = network.API() + super(FloatingIPController, self).__init__() + + def show(self, req, id): + """Return data about the given volume.""" + context = req.environ['nova.context'] + + try: + floating_ip = self.network_api.get(context, id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + return {'volume': _translate_floating_ip_detail_view(context, + floating_ip)} \ No newline at end of file -- cgit From a1e310aaa9f0ef829e2857c524be140541f3a13d Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Thu, 16 Jun 2011 20:31:09 +0400 Subject: floating_ips extension is loading to api now --- nova/api/openstack/contrib/floating_ips.py | 45 ++++++++++++++++++++++++++++-- nova/network/api.py | 5 ++++ tools/pip-requires | 2 +- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index b67b8b4ec..6c08f52e5 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -19,11 +19,16 @@ from webob import exc from nova import exception from nova import network from nova.api.openstack import faults +from nova.api.openstack import extensions def _translate_floating_ip_detail_view(context, floating_ip): #TODO(enugaev) implement view return None +def _translate_floating_ips_view(context, floating_ips): + #TODO(adiantum) implement view + return [] + class FloatingIPController(object): """The Volumes API controller for the OpenStack API.""" @@ -50,5 +55,41 @@ class FloatingIPController(object): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return {'volume': _translate_floating_ip_detail_view(context, - floating_ip)} \ No newline at end of file + return {'floating_ips': _translate_floating_ip_detail_view(context, + floating_ip)} + + def index(self, req): + context = req.environ['nova.context'] + + floating_ips = self.network_api.list(context) + + return {'floating_ips' : _translate_floating_ips_view(context, + floating_ips)} + + +class Floating_ips(extensions.ExtensionDescriptor): + def get_name(self): + return "Floating_ips" + + def get_alias(self): + return "FLOATING_IPS" + + def get_description(self): + return "Floating IPs support" + + def get_namespace(self): + return "http://docs.openstack.org/ext/floating_ips/api/v1.1" + + def get_updated(self): + return "2011-06-16T00:00:00+00:00" + + def get_resources(self): + resources = [] + + res = extensions.ResourceExtension('floating_ips', + FloatingIPController(), + collection_actions={}) + resources.append(res) + + return resources + diff --git a/nova/network/api.py b/nova/network/api.py index e5c4d6718..001c5a6f6 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -38,6 +38,11 @@ class API(base.Base): rv = self.db.floating_ip_get(context) return dict(rv.iteritems()) + def list(self, context): + ips = self.db.floating_ip_get_all_by_project(context, + context.project_id) + return ips + def allocate_floating_ip(self, context): if quota.allowed_floating_ips(context, 1) < 1: LOG.warn(_('Quota exceeeded for %s, tried to allocate ' diff --git a/tools/pip-requires b/tools/pip-requires index 168dacd40..f43e015f8 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -10,7 +10,7 @@ boto==1.9b carrot==0.10.5 eventlet==0.9.12 lockfile==0.8 -python-novaclient==2.5 +python-novaclient>=2.5 python-daemon==1.5.5 python-gflags==1.3 redis==2.0.0 -- cgit From d68f6de8d8275ec6dd9f231b9b52971f2ad15263 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Thu, 16 Jun 2011 16:33:29 -0400 Subject: Rename: intance_type_metadata -> instance_type_extra_specs --- nova/db/api.py | 24 ++--- nova/db/sqlalchemy/api.py | 77 +++++++-------- .../versions/021_add_instance_type_extra_specs.py | 67 +++++++++++++ .../versions/021_add_instance_type_metadata.py | 67 ------------- nova/db/sqlalchemy/models.py | 12 +-- nova/exception.py | 6 +- nova/tests/test_instance_types_extra_specs.py | 106 +++++++++++++++++++++ nova/tests/test_instance_types_metadata.py | 106 --------------------- 8 files changed, 233 insertions(+), 232 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_extra_specs.py delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_metadata.py create mode 100644 nova/tests/test_instance_types_extra_specs.py delete mode 100644 nova/tests/test_instance_types_metadata.py diff --git a/nova/db/api.py b/nova/db/api.py index 5ade51f02..a1a434d3d 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1251,19 +1251,19 @@ def instance_metadata_update_or_create(context, instance_id, metadata): #################### -def instance_type_metadata_get(context, instance_type_id): - """Get all metadata for an instance type.""" - return IMPL.instance_type_metadata_get(context, instance_type_id) +def instance_type_extra_specs_get(context, instance_type_id): + """Get all extra specs for an instance type.""" + return IMPL.instance_type_extra_specs_get(context, instance_type_id) -def instance_type_metadata_delete(context, instance_type_id, key): - """Delete the given metadata item.""" - IMPL.instance_type_metadata_delete(context, instance_type_id, key) +def instance_type_extra_specs_delete(context, instance_type_id, key): + """Delete the given extra specs item.""" + IMPL.instance_type_extra_specs_delete(context, instance_type_id, key) -def instance_type_metadata_update_or_create(context, instance_type_id, - metadata): - """Create or update instance type metadata. This adds or modifies the - key/value pairs specified in the metadata dict argument""" - IMPL.instance_type_metadata_update_or_create(context, instance_type_id, - metadata) +def instance_type_extra_specs_update_or_create(context, instance_type_id, + extra_specs): + """Create or update instance type extra specs. This adds or modifies the + key/value pairs specified in the extra specs dict argument""" + IMPL.instance_type_extra_specs_update_or_create(context, instance_type_id, + extra_specs) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 0c7f31366..e586bbc64 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2456,22 +2456,22 @@ def console_get(context, console_id, instance_id=None): @require_admin_context def instance_type_create(_context, values): - """Create a new instance type. In order to pass in metadata, - the values dict should contain a 'meta' key/value pair: + """Create a new instance type. In order to pass in extra specs, + the values dict should contain a 'extra_specs' key/value pair: - {'meta' : {'k1': 'v1', 'k2': 'v2', ...}} + {'extra_specs' : {'k1': 'v1', 'k2': 'v2', ...}} """ try: - metadata = values.get('meta') - metadata_refs = [] - if metadata: - for k, v in metadata.iteritems(): - metadata_ref = models.InstanceTypeMetadata() - metadata_ref['key'] = k - metadata_ref['value'] = v - metadata_refs.append(metadata_ref) - values['meta'] = metadata_refs + specs = values.get('extra_specs') + specs_refs = [] + if specs: + for k, v in specs.iteritems(): + specs_ref = models.InstanceTypeExtraSpecs() + specs_ref['key'] = k + specs_ref['value'] = v + specs_refs.append(specs_ref) + values['extra_specs'] = specs_refs instance_type_ref = models.InstanceTypes() instance_type_ref.update(values) instance_type_ref.save() @@ -2697,24 +2697,24 @@ def instance_metadata_update_or_create(context, instance_id, metadata): @require_context -def instance_type_metadata_get(context, instance_type_id): +def instance_type_extra_specs_get(context, instance_type_id): session = get_session() - meta_results = session.query(models.InstanceTypeMetadata).\ + spec_results = session.query(models.InstanceTypeExtraSpecs).\ filter_by(instance_type_id=instance_type_id).\ filter_by(deleted=False).\ all() - meta_dict = {} - for i in meta_results: - meta_dict[i['key']] = i['value'] - return meta_dict + spec_dict = {} + for i in spec_results: + spec_dict[i['key']] = i['value'] + return spec_dict @require_context -def instance_type_metadata_delete(context, instance_type_id, key): +def instance_type_extra_specs_delete(context, instance_type_id, key): session = get_session() - session.query(models.InstanceTypeMetadata).\ + session.query(models.InstanceTypeExtraSpecs).\ filter_by(instance_type_id=instance_type_id).\ filter_by(key=key).\ filter_by(deleted=False).\ @@ -2724,36 +2724,37 @@ def instance_type_metadata_delete(context, instance_type_id, key): @require_context -def instance_type_metadata_get_item(context, instance_type_id, key): +def instance_type_extra_specs_get_item(context, instance_type_id, key): session = get_session() - meta_result = session.query(models.InstanceMetadata).\ + sppec_result = session.query(models.InstanceTypeExtraSpecs).\ filter_by(instance_type_id=instance_type_id).\ filter_by(key=key).\ filter_by(deleted=False).\ first() - if not meta_result: + if not spec_result: raise exception.\ - InstanceTypeMetadataNotFound(metadata_key=key, + InstanceTypeExtraSpecsNotFound(extra_specs_key=key, instance_type_id=instance_type_id) - return meta_result + return spec_result @require_context -def instance_type_metadata_update_or_create(context, instance_type_id, - metadata): +def instance_type_extra_specs_update_or_create(context, instance_type_id, + specs): session = get_session() - meta_ref = None - for key, value in metadata.iteritems(): + spec_ref = None + for key, value in specs.iteritems(): try: - meta_ref = instance_type_metadata_get_item(context, - instance_type_id, key, - session) + spec_ref = instance_type_extra_specs_get_item(context, + instance_type_id, + key, + session) except: - meta_ref = models.InstanceTypeMetadata() - meta_ref.update({"key": key, "value": value, - "instance_type_id": instance_type_id, - "deleted": 0}) - meta_ref.save(session=session) - return metadata + spec_ref = models.InstanceTypeExtraSpecs() + spec_ref.update({"key": key, "value": value, + "instance_type_id": instance_type_id, + "deleted": 0}) + spec_ref.save(session=session) + return specs diff --git a/nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_extra_specs.py b/nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_extra_specs.py new file mode 100644 index 000000000..f26ad6d2c --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_extra_specs.py @@ -0,0 +1,67 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 University of Southern California +# +# 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 Boolean, Column, DateTime, ForeignKey, Integer +from sqlalchemy import MetaData, String, Table +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. +instance_types = Table('instance_types', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + +# +# New Tables +# + +instance_type_extra_specs_table = Table('instance_type_extra_specs', 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('instance_type_id', + Integer(), + ForeignKey('instance_types.id'), + nullable=False), + Column('key', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False)), + Column('value', + 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 (instance_type_extra_specs_table, ): + try: + table.create() + except Exception: + logging.info(repr(table)) + logging.exception('Exception while creating table') + raise + + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + for table in (instance_type_extra_specs_table, ): + table.drop() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_metadata.py b/nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_metadata.py deleted file mode 100644 index 22b741614..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/021_add_instance_type_metadata.py +++ /dev/null @@ -1,67 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 University of Southern California -# -# 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 Boolean, Column, DateTime, ForeignKey, Integer -from sqlalchemy import MetaData, String, Table -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. -instance_types = Table('instance_types', meta, - Column('id', Integer(), primary_key=True, nullable=False), - ) - -# -# New Tables -# - -instance_type_metadata_table = Table('instance_type_metadata', 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('instance_type_id', - Integer(), - ForeignKey('instance_types.id'), - nullable=False), - Column('key', - String(length=255, convert_unicode=False, assert_unicode=None, - unicode_error=None, _warn_on_bytestring=False)), - Column('value', - 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 (instance_type_metadata_table, ): - try: - table.create() - except Exception: - logging.info(repr(table)) - logging.exception('Exception while creating table') - raise - - -def downgrade(migrate_engine): - # Operations to reverse the above upgrade go here. - for table in (instance_type_metadata_table, ): - table.drop() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index dbe448ef8..d4c217450 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -663,19 +663,19 @@ class InstanceMetadata(BASE, NovaBase): 'InstanceMetadata.deleted == False)') -class InstanceTypeMetadata(BASE, NovaBase): - """Represents a metadata key/value pair for an instance_type""" - __tablename__ = 'instance_type_metadata' +class InstanceTypeExtraSpecs(BASE, NovaBase): + """Represents additional specs as key/value pairs for an instance_type""" + __tablename__ = 'instance_type_extra_specs' id = Column(Integer, primary_key=True) key = Column(String(255)) value = Column(String(255)) instance_type_id = Column(Integer, ForeignKey('instance_types.id'), nullable=False) - instance_type = relationship(InstanceTypes, backref="meta", + instance_type = relationship(InstanceTypes, backref="extra_specs", foreign_keys=instance_type_id, primaryjoin='and_(' - 'InstanceTypeMetadata.instance_type_id == InstanceTypes.id,' - 'InstanceTypeMetadata.deleted == False)') + 'InstanceTypeExtraSpecs.instance_type_id == InstanceTypes.id,' + 'InstanceTypeExtraSpecs.deleted == False)') class Zone(BASE, NovaBase): diff --git a/nova/exception.py b/nova/exception.py index ce287b7d6..394004fe6 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -482,9 +482,9 @@ class InstanceMetadataNotFound(NotFound): "key %(metadata_key)s.") -class InstanceTypeMetadataNotFound(NotFound): - message = _("Instance Type %(instance_type_id)s has no metadata with " - "key %(metadata_key)s.") +class InstanceTypeExtraSpecsNotFound(NotFound): + message = _("Instance Type %(instance_type_id)s has no extra specs with " + "key %(extra_specs_key)s.") class LDAPObjectNotFound(NotFound): diff --git a/nova/tests/test_instance_types_extra_specs.py b/nova/tests/test_instance_types_extra_specs.py new file mode 100644 index 000000000..e739225fc --- /dev/null +++ b/nova/tests/test_instance_types_extra_specs.py @@ -0,0 +1,106 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 University of Southern California +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Unit Tests for instance types extra specs code +""" + +from nova import context +from nova import db +from nova import test +from nova.db.sqlalchemy.session import get_session +from nova.db.sqlalchemy import models + + +class InstanceTypeExtraSpecsTestCase(test.TestCase): + + def setUp(self): + super(InstanceTypeExtraSpecsTestCase, self).setUp() + self.context = context.get_admin_context() + values = dict(name="cg1.4xlarge", + memory_mb=22000, + vcpus=8, + local_gb=1690, + flavorid=105) + specs = dict(cpu_arch="x86_64", + cpu_model="Nehalem", + xpu_arch="fermi", + xpus=2, + xpu_model="Tesla 2050") + values['extra_specs'] = specs + ref = db.api.instance_type_create(self.context, + values) + self.instance_type_id = ref.id + + def tearDown(self): + # Remove the instance type from the database + db.api.instance_type_purge(context.get_admin_context(), "cg1.4xlarge") + super(InstanceTypeExtraSpecsTestCase, self).tearDown() + + def test_instance_type_specs_get(self): + expected_specs = dict(cpu_arch="x86_64", + cpu_model="Nehalem", + xpu_arch="fermi", + xpus="2", + xpu_model="Tesla 2050") + actual_specs = db.api.instance_type_extra_specs_get( + context.get_admin_context(), + self.instance_type_id) + self.assertEquals(expected_specs, actual_specs) + + def test_instance_type_extra_specs_delete(self): + expected_specs = dict(cpu_arch="x86_64", + cpu_model="Nehalem", + xpu_arch="fermi", + xpus="2") + db.api.instance_type_extra_specs_delete(context.get_admin_context(), + self.instance_type_id, + "xpu_model") + actual_specs = db.api.instance_type_extra_specs_get( + context.get_admin_context(), + self.instance_type_id) + self.assertEquals(expected_specs, actual_specs) + + def test_instance_type_extra_specs_update(self): + expected_specs = dict(cpu_arch="x86_64", + cpu_model="Sandy Bridge", + xpu_arch="fermi", + xpus="2", + xpu_model="Tesla 2050") + db.api.instance_type_extra_specs_update_or_create( + context.get_admin_context(), + self.instance_type_id, + dict(cpu_model="Sandy Bridge")) + actual_specs = db.api.instance_type_extra_specs_get( + context.get_admin_context(), + self.instance_type_id) + self.assertEquals(expected_specs, actual_specs) + + def test_instance_type_extra_specs_create(self): + expected_specs = dict(cpu_arch="x86_64", + cpu_model="Nehalem", + xpu_arch="fermi", + xpus="2", + xpu_model="Tesla 2050", + net_arch="ethernet", + net_mbps="10000") + db.api.instance_type_extra_specs_update_or_create( + context.get_admin_context(), + self.instance_type_id, + dict(net_arch="ethernet", + net_mbps=10000)) + actual_specs = db.api.instance_type_extra_specs_get( + context.get_admin_context(), + self.instance_type_id) + self.assertEquals(expected_specs, actual_specs) diff --git a/nova/tests/test_instance_types_metadata.py b/nova/tests/test_instance_types_metadata.py deleted file mode 100644 index 1c4185888..000000000 --- a/nova/tests/test_instance_types_metadata.py +++ /dev/null @@ -1,106 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 University of Southern California -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Unit Tests for instance types metadata code -""" - -from nova import context -from nova import db -from nova import test -from nova.db.sqlalchemy.session import get_session -from nova.db.sqlalchemy import models - - -class InstanceTypeMetadataTestCase(test.TestCase): - - def setUp(self): - super(InstanceTypeMetadataTestCase, self).setUp() - self.context = context.get_admin_context() - values = dict(name="cg1.4xlarge", - memory_mb=22000, - vcpus=8, - local_gb=1690, - flavorid=105) - metadata = dict(cpu_arch="x86_64", - cpu_model="Nehalem", - xpu_arch="fermi", - xpus=2, - xpu_model="Tesla 2050") - values['meta'] = metadata - ref = db.api.instance_type_create(self.context, - values) - self.instance_type_id = ref.id - - def tearDown(self): - # Remove the instance type from the database - db.api.instance_type_purge(context.get_admin_context(), "cg1.4xlarge") - super(InstanceTypeMetadataTestCase, self).tearDown() - - def test_instance_type_metadata_get(self): - expected_metadata = dict(cpu_arch="x86_64", - cpu_model="Nehalem", - xpu_arch="fermi", - xpus="2", - xpu_model="Tesla 2050") - actual_metadata = db.api.instance_type_metadata_get( - context.get_admin_context(), - self.instance_type_id) - self.assertEquals(expected_metadata, actual_metadata) - - def test_instance_type_metadata_delete(self): - expected_metadata = dict(cpu_arch="x86_64", - cpu_model="Nehalem", - xpu_arch="fermi", - xpus="2") - db.api.instance_type_metadata_delete(context.get_admin_context(), - self.instance_type_id, - "xpu_model") - actual_metadata = db.api.instance_type_metadata_get( - context.get_admin_context(), - self.instance_type_id) - self.assertEquals(expected_metadata, actual_metadata) - - def test_instance_type_metadata_update(self): - expected_metadata = dict(cpu_arch="x86_64", - cpu_model="Sandy Bridge", - xpu_arch="fermi", - xpus="2", - xpu_model="Tesla 2050") - db.api.instance_type_metadata_update_or_create( - context.get_admin_context(), - self.instance_type_id, - dict(cpu_model="Sandy Bridge")) - actual_metadata = db.api.instance_type_metadata_get( - context.get_admin_context(), - self.instance_type_id) - self.assertEquals(expected_metadata, actual_metadata) - - def test_instance_type_metadata_create(self): - expected_metadata = dict(cpu_arch="x86_64", - cpu_model="Nehalem", - xpu_arch="fermi", - xpus="2", - xpu_model="Tesla 2050", - net_arch="ethernet", - net_mbps="10000") - db.api.instance_type_metadata_update_or_create( - context.get_admin_context(), - self.instance_type_id, - dict(net_arch="ethernet", - net_mbps=10000)) - actual_metadata = db.api.instance_type_metadata_get( - context.get_admin_context(), - self.instance_type_id) - self.assertEquals(expected_metadata, actual_metadata) -- cgit From 060fd3921e876dcbd594270871ddaeee749259be Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 17 Jun 2011 09:19:55 -0400 Subject: Missed a InstanceTypeMetadata -> InstanceTypeExtraSpecs rename in register_models --- nova/db/sqlalchemy/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 52de9298a..96f85b4e5 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -701,7 +701,7 @@ def register_models(): Network, SecurityGroup, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, Project, Certificate, ConsolePool, Console, Zone, - InstanceMetadata, InstanceTypeMetadata, Migration) + InstanceMetadata, InstanceTypeExtraSpecs, Migration) engine = create_engine(FLAGS.sql_connection, echo=False) for model in models: model.metadata.create_all(engine) -- cgit From 971efd1b5568c324c91e826fc347c49ceea3790c Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Fri, 17 Jun 2011 17:27:06 +0400 Subject: stub api methods --- nova/api/openstack/contrib/floating_ips.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 6c08f52e5..f7a939ddd 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -66,6 +66,25 @@ class FloatingIPController(object): return {'floating_ips' : _translate_floating_ips_view(context, floating_ips)} + def create(self, req, body): + context = req.environ['nova.context'] + + return {'allocate': None} + + def delete(self,req, id): + context = req.environ['nova.context'] + + return {'release': None } + + def associate(self, req, id, body): + context = req.environ['nova.context'] + + return {'associate': None} + + def disassociate(self, req, id, body): + context = req.environ['nova.context'] + + return {'disassociate': None} class Floating_ips(extensions.ExtensionDescriptor): def get_name(self): @@ -88,7 +107,9 @@ class Floating_ips(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('floating_ips', FloatingIPController(), - collection_actions={}) + member_actions={ + 'associate' : 'POST', + 'disassociate' : 'POST'}) resources.append(res) return resources -- cgit From bfbb2b8e04d1cd4b761c67973b173d2ca6f84859 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 17 Jun 2011 13:39:34 -0400 Subject: 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 5ffd8e96a..4a09060c9 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 2773c9c13..d6a054102 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 e2c4cf8d7..86e2b116e 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 a10fb7433..d46608c92 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 be777df9b..06983893a 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: 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 4a09060c9..d43340e10 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 06983893a..deef5d235 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')) -- cgit From 843644aed6477b4411ec3f07d1a5271df41c9798 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Sat, 18 Jun 2011 23:10:41 -0400 Subject: General cleanup and refactor of a lot of the API/WSGI service code. --- bin/nova-api | 18 +++++- nova/api/openstack/wsgi.py | 4 +- nova/log.py | 13 ++++ nova/service.py | 96 +++++++++++------------------ nova/test.py | 23 ------- nova/tests/integrated/integrated_helpers.py | 13 ++-- nova/wsgi.py | 63 +++++++++---------- 7 files changed, 104 insertions(+), 126 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index a1088c23d..6db68be9c 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -24,6 +24,8 @@ import gettext import os import sys +import eventlet.pool + # If ../nova/__init__.py exists, add ../ to Python search path, so that # it will override what happens to be installed in /usr/(local/)lib/python... possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), @@ -46,6 +48,13 @@ LOG = logging.getLogger('nova.api') FLAGS = flags.FLAGS + +def launch(service_name): + _service = service.WSGIService(service_name) + _service.start() + _service.wait() + + if __name__ == '__main__': utils.default_flagfile() FLAGS(sys.argv) @@ -57,5 +66,10 @@ if __name__ == '__main__': flag_get = FLAGS.get(flag, None) LOG.debug("%(flag)s : %(flag_get)s" % locals()) - service = service.serve_wsgi(service.ApiService) - service.wait() + + pool = eventlet.pool.Pool() + pool.execute(launch, "ec2") + pool.execute(launch, "osapi") + pool.wait_all() + + print >>sys.stderr, "Exiting..." diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index a57b7f72b..6033c1f5b 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -356,7 +356,7 @@ class Resource(wsgi.Application): def __call__(self, request): """WSGI method that controls (de)serialization and method dispatch.""" - LOG.debug("%(method)s %(url)s" % {"method": request.method, + LOG.info("%(method)s %(url)s" % {"method": request.method, "url": request.url}) try: @@ -384,7 +384,7 @@ class Resource(wsgi.Application): msg_dict = dict(url=request.url, e=e) msg = _("%(url)s returned a fault: %(e)s" % msg_dict) - LOG.debug(msg) + LOG.info(msg) return response diff --git a/nova/log.py b/nova/log.py index 6909916a1..d3bffdb21 100644 --- a/nova/log.py +++ b/nova/log.py @@ -314,3 +314,16 @@ logging.setLoggerClass(NovaLogger) def audit(msg, *args, **kwargs): """Shortcut for logging to root log with sevrity 'AUDIT'.""" logging.root.log(AUDIT, msg, *args, **kwargs) + + +class WritableLogger(object): + """A thin wrapper that responds to `write` and logs.""" + + def __init__(self, logger, level=logging.DEBUG): + self.logger = logger + self.level = level + + def write(self, msg): + self.logger.log(self.level, msg) + + diff --git a/nova/service.py b/nova/service.py index 74f9f04d8..768390414 100644 --- a/nova/service.py +++ b/nova/service.py @@ -232,45 +232,45 @@ class Service(object): logging.exception(_('model server went away')) -class WsgiService(object): - """Base class for WSGI based services. - - For each api you define, you must also define these flags: - :_listen: The address on which to listen - :_listen_port: The port on which to listen - - """ - - def __init__(self, conf, apis): - self.conf = conf - self.apis = apis - self.wsgi_app = None +class WSGIService(object): + """Provides ability to launch API from a 'paste' configuration.""" + + def __init__(self, name, config_name=None): + """Initialize, but do not start, an API service.""" + self.name = name + self._config_name = config_name or FLAGS.api_paste_config + self._config_location = self._find_config() + self._config = self._load_config() + self.application = self._load_application() + host = getattr(FLAGS, '%s_listen' % name, "0.0.0.0") + port = getattr(FLAGS, '%s_listen_port' % name, 0) + self.server = wsgi.Server(name, self.application, host, port) + + def _find_config(self): + """Attempt to find 'paste' configuration file.""" + location = wsgi.paste_config_file(self._config_name) + logging.debug(_("Using paste.deploy config at: %s"), location) + return location + + def _load_config(self): + """Read and return the 'paste' configuration file.""" + return wsgi.load_paste_configuration(self._config_location, self.name) + + def _load_application(self): + """Using the loaded configuration, return the WSGI application.""" + return wsgi.load_paste_app(self._config_location, self.name) def start(self): - self.wsgi_app = _run_wsgi(self.conf, self.apis) - - def wait(self): - self.wsgi_app.wait() - - def get_socket_info(self, api_name): - """Returns the (host, port) that an API was started on.""" - return self.wsgi_app.socket_info[api_name] + """Start serving this API using loaded configuration.""" + self.server.start() + def stop(self): + """Stop serving this API.""" + self.server.stop() -class ApiService(WsgiService): - """Class for our nova-api service.""" - - @classmethod - def create(cls, conf=None): - if not conf: - conf = wsgi.paste_config_file(FLAGS.api_paste_config) - if not conf: - message = (_('No paste configuration found for: %s'), - FLAGS.api_paste_config) - raise exception.Error(message) - api_endpoints = ['ec2', 'osapi'] - service = cls(conf, api_endpoints) - return service + def wait(self): + """Wait for the service to stop serving this API.""" + self.server.wait() def serve(*services): @@ -321,29 +321,3 @@ def serve_wsgi(cls, conf=None): service.start() return service - - -def _run_wsgi(paste_config_file, apis): - logging.debug(_('Using paste.deploy config at: %s'), paste_config_file) - apps = [] - for api in apis: - config = wsgi.load_paste_configuration(paste_config_file, api) - if config is None: - logging.debug(_('No paste configuration for app: %s'), api) - continue - logging.debug(_('App Config: %(api)s\n%(config)r') % locals()) - logging.info(_('Running %s API'), api) - app = wsgi.load_paste_app(paste_config_file, api) - apps.append((app, - getattr(FLAGS, '%s_listen_port' % api), - getattr(FLAGS, '%s_listen' % api), - api)) - if len(apps) == 0: - logging.error(_('No known API applications configured in %s.'), - paste_config_file) - return - - server = wsgi.Server() - for app in apps: - server.start(*app) - return server diff --git a/nova/test.py b/nova/test.py index 4a0a18fe7..ab1eaf5fd 100644 --- a/nova/test.py +++ b/nova/test.py @@ -38,7 +38,6 @@ from nova import flags from nova import rpc from nova import utils from nova import service -from nova import wsgi from nova.virt import fake @@ -81,7 +80,6 @@ class TestCase(unittest.TestCase): self.injected = [] self._services = [] self._monkey_patch_attach() - self._monkey_patch_wsgi() self._original_flags = FLAGS.FlagValuesDict() rpc.ConnectionPool = rpc.Pool(max_size=FLAGS.rpc_conn_pool_size) @@ -107,7 +105,6 @@ class TestCase(unittest.TestCase): # Reset our monkey-patches rpc.Consumer.attach_to_eventlet = self.original_attach - wsgi.Server.start = self.original_start # Stop any timers for x in self.injected: @@ -163,26 +160,6 @@ class TestCase(unittest.TestCase): _wrapped.func_name = self.original_attach.func_name rpc.Consumer.attach_to_eventlet = _wrapped - def _monkey_patch_wsgi(self): - """Allow us to kill servers spawned by wsgi.Server.""" - self.original_start = wsgi.Server.start - - @functools.wraps(self.original_start) - def _wrapped_start(inner_self, *args, **kwargs): - original_spawn_n = inner_self.pool.spawn_n - - @functools.wraps(original_spawn_n) - def _wrapped_spawn_n(*args, **kwargs): - rv = greenthread.spawn(*args, **kwargs) - self._services.append(rv) - - inner_self.pool.spawn_n = _wrapped_spawn_n - self.original_start(inner_self, *args, **kwargs) - inner_self.pool.spawn_n = original_spawn_n - - _wrapped_start.func_name = self.original_start.func_name - wsgi.Server.start = _wrapped_start - # Useful assertions def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001): """Assert two dicts are equivalent. diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 522c7cb0e..ba6bef62f 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -171,15 +171,14 @@ class _IntegratedTestBase(test.TestCase): self.api = self.user.openstack_api def _start_api_service(self): - api_service = service.ApiService.create() - api_service.start() + #ec2 = service.WSGIService("ec2") + #ec2.start() - if not api_service: - raise Exception("API Service was None") + osapi = service.WSGIService("osapi") + osapi.start() - self.api_service = api_service - - host, port = api_service.get_socket_info('osapi') + host = osapi.server.host + port = osapi.server.port self.auth_url = 'http://%s:%s/v1.1' % (host, port) def tearDown(self): diff --git a/nova/wsgi.py b/nova/wsgi.py index 33ba852bc..439ec6a12 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -43,46 +43,45 @@ FLAGS = flags.FLAGS LOG = logging.getLogger('nova.wsgi') -class WritableLogger(object): - """A thin wrapper that responds to `write` and logs.""" - - def __init__(self, logger, level=logging.DEBUG): - self.logger = logger - self.level = level - - def write(self, msg): - self.logger.log(self.level, msg) - - class Server(object): """Server class to manage multiple WSGI sockets and applications.""" - def __init__(self, threads=1000): - self.pool = eventlet.GreenPool(threads) - self.socket_info = {} - - def start(self, application, port, host='0.0.0.0', key=None, backlog=128): - """Run a WSGI server with the given application.""" - arg0 = sys.argv[0] - logging.audit(_('Starting %(arg0)s on %(host)s:%(port)s') % locals()) - socket = eventlet.listen((host, port), backlog=backlog) - self.pool.spawn_n(self._run, application, socket) - if key: - self.socket_info[key] = socket.getsockname() + default_pool_size = 1000 + logger_name = "eventlet.wsgi.server" + + def __init__(self, name, app, host, port, pool_size=None): + self.name = name + self.app = app + self.host = host + self.port = port + self._pool = eventlet.GreenPool(pool_size or self.default_pool_size) + self._log = logging.WritableLogger(logging.getLogger(self.logger_name)) + + def _start(self, socket): + """Blocking eventlet WSGI server launched from the real 'start'.""" + eventlet.wsgi.server(socket, + self.app, + custom_pool=self._pool, + log=self._log) + + def start(self, backlog=128): + """Serve given WSGI application using the given parameters.""" + socket = eventlet.listen((self.host, self.port), backlog=backlog) + self._server = eventlet.spawn(self._start, socket) + (self.host, self.port) = socket.getsockname() + LOG.info(_('Starting %(app)s on %(host)s:%(port)s') % self.__dict__) + + def stop(self): + """Stop this server by killing the greenthread running it.""" + self._server.kill() def wait(self): - """Wait until all servers have completed running.""" + """Wait until server has been stopped.""" try: - self.pool.waitall() + self._server.wait() except KeyboardInterrupt: pass - def _run(self, application, socket): - """Start a WSGI server in a new green thread.""" - logger = logging.getLogger('eventlet.wsgi.server') - eventlet.wsgi.server(socket, application, custom_pool=self.pool, - log=WritableLogger(logger)) - class Request(webob.Request): pass @@ -340,6 +339,8 @@ def paste_config_file(basename): if os.path.exists(configfile): return configfile + raise Exception(_("Unable to find paste.deploy config '%s'") % basename) + def load_paste_configuration(filename, appname): """Returns a paste configuration dict, or None.""" -- cgit From ea64f883b74fa3c702a3c47d4508a1e7a7f6b40d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Sun, 19 Jun 2011 14:09:09 -0400 Subject: Removed debugging, made objectstore tests pass again. --- bin/nova-api | 3 --- nova/log.py | 2 -- nova/tests/test_objectstore.py | 6 ++++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index 6db68be9c..90c8b69ad 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -66,10 +66,7 @@ if __name__ == '__main__': flag_get = FLAGS.get(flag, None) LOG.debug("%(flag)s : %(flag_get)s" % locals()) - pool = eventlet.pool.Pool() pool.execute(launch, "ec2") pool.execute(launch, "osapi") pool.wait_all() - - print >>sys.stderr, "Exiting..." diff --git a/nova/log.py b/nova/log.py index d3bffdb21..3080daee2 100644 --- a/nova/log.py +++ b/nova/log.py @@ -325,5 +325,3 @@ class WritableLogger(object): def write(self, msg): self.logger.log(self.level, msg) - - diff --git a/nova/tests/test_objectstore.py b/nova/tests/test_objectstore.py index c78772f27..c35955c9c 100644 --- a/nova/tests/test_objectstore.py +++ b/nova/tests/test_objectstore.py @@ -70,11 +70,12 @@ class S3APITestCase(test.TestCase): os.mkdir(FLAGS.buckets_path) router = s3server.S3Application(FLAGS.buckets_path) - server = wsgi.Server() - server.start(router, FLAGS.s3_port, host=FLAGS.s3_host) + self.server = wsgi.Server("s3api", router, FLAGS.s3_host, FLAGS.s3_port) + self.server.start() if not boto.config.has_section('Boto'): boto.config.add_section('Boto') + boto.config.set('Boto', 'num_retries', '0') conn = s3.S3Connection(aws_access_key_id=self.admin_user.access, aws_secret_access_key=self.admin_user.secret, @@ -145,4 +146,5 @@ class S3APITestCase(test.TestCase): """Tear down auth and test server.""" self.auth_manager.delete_user('admin') self.auth_manager.delete_project('admin') + self.server.stop() super(S3APITestCase, self).tearDown() -- cgit From 95213244fe341b7ec2723b92a5b793e89ee8403f Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Sun, 19 Jun 2011 14:41:42 -0400 Subject: Cleaned up nova-api binary and logging a bit. --- bin/nova-api | 32 +++++++++----------------------- nova/__init__.py | 12 ++++++++++++ nova/service.py | 19 ------------------- nova/utils.py | 2 ++ nova/wsgi.py | 2 +- setup.cfg | 3 +++ 6 files changed, 27 insertions(+), 43 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index 90c8b69ad..7d80b0b78 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -20,33 +20,20 @@ """Starter script for Nova API.""" -import gettext -import os import sys import eventlet.pool -# If ../nova/__init__.py exists, add ../ to Python search path, so that -# it will override what happens to be installed in /usr/(local/)lib/python... -possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), - os.pardir, - os.pardir)) -if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): - sys.path.insert(0, possible_topdir) - -gettext.install('nova', unicode=1) - from nova import flags from nova import log as logging from nova import service from nova import utils from nova import version -from nova import wsgi -LOG = logging.getLogger('nova.api') - +LOG = logging.getLogger("nova.api") FLAGS = flags.FLAGS +VERSION = version.version_string_with_vcs() def launch(service_name): @@ -55,18 +42,17 @@ def launch(service_name): _service.wait() -if __name__ == '__main__': +def main(): utils.default_flagfile() FLAGS(sys.argv) - logging.setup() - LOG.audit(_("Starting nova-api node (version %s)"), - version.version_string_with_vcs()) - LOG.debug(_("Full set of FLAGS:")) - for flag in FLAGS: - flag_get = FLAGS.get(flag, None) - LOG.debug("%(flag)s : %(flag_get)s" % locals()) +# logging.setup() + LOG.audit(_("Starting nova-api node (version %s)") % VERSION) pool = eventlet.pool.Pool() pool.execute(launch, "ec2") pool.execute(launch, "osapi") pool.wait_all() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/nova/__init__.py b/nova/__init__.py index 256db55a9..7b05611b9 100644 --- a/nova/__init__.py +++ b/nova/__init__.py @@ -30,3 +30,15 @@ .. moduleauthor:: Manish Singh .. moduleauthor:: Andy Smith """ + +import gettext + +import log as logging + + +def initialize(): + gettext.install("nova", unicode=1) + logging.setup() + + +initialize() diff --git a/nova/service.py b/nova/service.py index 768390414..ec2de9004 100644 --- a/nova/service.py +++ b/nova/service.py @@ -302,22 +302,3 @@ def serve(*services): def wait(): while True: greenthread.sleep(5) - - -def serve_wsgi(cls, conf=None): - try: - service = cls.create(conf) - except Exception: - logging.exception('in WsgiService.create()') - raise - finally: - # After we've loaded up all our dynamic bits, check - # whether we should print help - flags.DEFINE_flag(flags.HelpFlag()) - flags.DEFINE_flag(flags.HelpshortFlag()) - flags.DEFINE_flag(flags.HelpXMLFlag()) - FLAGS.ParseNewFlags() - - service.start() - - return service diff --git a/nova/utils.py b/nova/utils.py index 691134ada..22d0ce940 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -238,6 +238,8 @@ def default_flagfile(filename='nova.conf'): filename = "./nova.conf" if not os.path.exists(filename): filename = '/etc/nova/nova.conf' + if not os.path.exists(filename): + return flagfile = ['--flagfile=%s' % filename] sys.argv = sys.argv[:1] + flagfile + sys.argv[1:] diff --git a/nova/wsgi.py b/nova/wsgi.py index 439ec6a12..4da8eff82 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -69,7 +69,7 @@ class Server(object): socket = eventlet.listen((self.host, self.port), backlog=backlog) self._server = eventlet.spawn(self._start, socket) (self.host, self.port) = socket.getsockname() - LOG.info(_('Starting %(app)s on %(host)s:%(port)s') % self.__dict__) + LOG.info(_('Starting %(name)s on %(host)s:%(port)s') % self.__dict__) def stop(self): """Stop this server by killing the greenthread running it.""" diff --git a/setup.cfg b/setup.cfg index 9c0a331e3..7efc46f23 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,6 @@ +[develop] +user = True + [build_sphinx] all_files = 1 build-dir = doc/build -- cgit From 1e047dae71131a0080310990dc6899852d233941 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Sun, 19 Jun 2011 16:27:46 -0400 Subject: Further nova-api cleanup. --- bin/nova-api | 63 +++++++++++++++++++++++++++++++++----------------------- nova/__init__.py | 1 + nova/log.py | 2 +- nova/service.py | 2 +- nova/wsgi.py | 5 +---- 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index 7d80b0b78..ff4aa83d1 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -1,7 +1,5 @@ #!/usr/bin/env python -# pylint: disable=C0103 -# 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. @@ -18,40 +16,53 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Starter script for Nova API.""" +"""Starter script for Nova API. -import sys +Starts both the EC2 and OpenStack APIs in separate processes. Pylint warnings +about re-imports should be ignored. -import eventlet.pool +""" -from nova import flags -from nova import log as logging -from nova import service -from nova import utils -from nova import version +# pylint: disable=W0404 +import sys +import multiprocessing -LOG = logging.getLogger("nova.api") -FLAGS = flags.FLAGS -VERSION = version.version_string_with_vcs() +import nova.flags +import nova.log +import nova.service +import nova.version +import nova.utils def launch(service_name): - _service = service.WSGIService(service_name) - _service.start() - _service.wait() + """Launch WSGI service with name matching 'paste' config file section.""" + service = nova.service.WSGIService(service_name) + service.start() + try: + service.wait() + except KeyboardInterrupt: + service.stop() def main(): - utils.default_flagfile() - FLAGS(sys.argv) -# logging.setup() - LOG.audit(_("Starting nova-api node (version %s)") % VERSION) - - pool = eventlet.pool.Pool() - pool.execute(launch, "ec2") - pool.execute(launch, "osapi") - pool.wait_all() + """Begin process of launching both EC2 and OSAPI services.""" + version = nova.version.version_string_with_vcs() + logger = nova.log.getLogger("nova.api") + logger.audit(_("Starting nova-api node (version %s)") % version) + + nova.flags.FLAGS(sys.argv) + nova.utils.default_flagfile() + + pool = multiprocessing.Pool(2) + pool.map_async(launch, ["ec2", "osapi"]) + pool.close() + + try: + pool.join() + except KeyboardInterrupt: + logger.audit(_("Exiting...")) + pool.terminate() if __name__ == '__main__': diff --git a/nova/__init__.py b/nova/__init__.py index 7b05611b9..886eaee20 100644 --- a/nova/__init__.py +++ b/nova/__init__.py @@ -39,6 +39,7 @@ import log as logging def initialize(): gettext.install("nova", unicode=1) logging.setup() + logging.debug(_("Initialized logging.")) initialize() diff --git a/nova/log.py b/nova/log.py index 3080daee2..f8c0ba68d 100644 --- a/nova/log.py +++ b/nova/log.py @@ -319,7 +319,7 @@ def audit(msg, *args, **kwargs): class WritableLogger(object): """A thin wrapper that responds to `write` and logs.""" - def __init__(self, logger, level=logging.DEBUG): + def __init__(self, logger, level=logging.INFO): self.logger = logger self.level = level diff --git a/nova/service.py b/nova/service.py index ec2de9004..4c7d1adc4 100644 --- a/nova/service.py +++ b/nova/service.py @@ -249,7 +249,7 @@ class WSGIService(object): def _find_config(self): """Attempt to find 'paste' configuration file.""" location = wsgi.paste_config_file(self._config_name) - logging.debug(_("Using paste.deploy config at: %s"), location) + logging.info(_("Using paste.deploy config: %s"), location) return location def _load_config(self): diff --git a/nova/wsgi.py b/nova/wsgi.py index 4da8eff82..82ba006c8 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -77,10 +77,7 @@ class Server(object): def wait(self): """Wait until server has been stopped.""" - try: - self._server.wait() - except KeyboardInterrupt: - pass + self._server.wait() class Request(webob.Request): -- cgit From 79402ffbaeae18bb4adaa899743a688ef0bcb24b Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Sun, 19 Jun 2011 18:00:38 -0400 Subject: Cleanup of the cleanup. --- bin/nova-api | 6 ++---- nova/tests/integrated/integrated_helpers.py | 3 --- setup.cfg | 3 --- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index ff4aa83d1..1fcda24f4 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -1,4 +1,5 @@ #!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. @@ -18,13 +19,10 @@ """Starter script for Nova API. -Starts both the EC2 and OpenStack APIs in separate processes. Pylint warnings -about re-imports should be ignored. +Starts both the EC2 and OpenStack APIs in separate processes. """ -# pylint: disable=W0404 - import sys import multiprocessing diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index ba6bef62f..8809bf5f8 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -171,9 +171,6 @@ class _IntegratedTestBase(test.TestCase): self.api = self.user.openstack_api def _start_api_service(self): - #ec2 = service.WSGIService("ec2") - #ec2.start() - osapi = service.WSGIService("osapi") osapi.start() diff --git a/setup.cfg b/setup.cfg index 7efc46f23..9c0a331e3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[develop] -user = True - [build_sphinx] all_files = 1 build-dir = doc/build -- cgit From 927aecb0a3ff1fe561b3c96a4fb9b18c8893c3ae Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Sun, 19 Jun 2011 20:18:29 -0400 Subject: Introduced Loader concept, for paste decouple. --- bin/nova-api | 2 + nova/exception.py | 8 +++ nova/service.py | 38 +++++-------- nova/wsgi.py | 161 +++++++++++++++++++++++++++++++----------------------- 4 files changed, 116 insertions(+), 93 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index 1fcda24f4..885fb0ba4 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -51,6 +51,8 @@ def main(): nova.flags.FLAGS(sys.argv) nova.utils.default_flagfile() + +# launch("osapi") pool = multiprocessing.Pool(2) pool.map_async(launch, ["ec2", "osapi"]) diff --git a/nova/exception.py b/nova/exception.py index f3a452228..0fcd3de95 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -589,3 +589,11 @@ class MigrationError(NovaException): class MalformedRequestBody(NovaException): message = _("Malformed message body: %(reason)s") + + +class PasteConfigNotFound(NotFound): + message = _("Could not find paste config at %(path)s") + + +class PasteAppNotFound(NotFound): + message = _("Could not load paste app '%(name)s' from %(path)s") diff --git a/nova/service.py b/nova/service.py index 4c7d1adc4..e27d61371 100644 --- a/nova/service.py +++ b/nova/service.py @@ -235,34 +235,24 @@ class Service(object): class WSGIService(object): """Provides ability to launch API from a 'paste' configuration.""" - def __init__(self, name, config_name=None): - """Initialize, but do not start, an API service.""" + def __init__(self, name, loader=None): + """Initialize, but do not start the WSGI service. + + :param name: The name of the WSGI service given to the loader. + :param loader: Loads the WSGI application using the given name. + :returns: None + + """ self.name = name - self._config_name = config_name or FLAGS.api_paste_config - self._config_location = self._find_config() - self._config = self._load_config() - self.application = self._load_application() - host = getattr(FLAGS, '%s_listen' % name, "0.0.0.0") - port = getattr(FLAGS, '%s_listen_port' % name, 0) - self.server = wsgi.Server(name, self.application, host, port) - - def _find_config(self): - """Attempt to find 'paste' configuration file.""" - location = wsgi.paste_config_file(self._config_name) - logging.info(_("Using paste.deploy config: %s"), location) - return location - - def _load_config(self): - """Read and return the 'paste' configuration file.""" - return wsgi.load_paste_configuration(self._config_location, self.name) - - def _load_application(self): - """Using the loaded configuration, return the WSGI application.""" - return wsgi.load_paste_app(self._config_location, self.name) + self.loader = loader or wsgi.Loader() + self.application = self.loader.load_app(name) + self.host = getattr(FLAGS, '%s_listen' % name, "0.0.0.0") + self.port = getattr(FLAGS, '%s_listen_port' % name, 0) + self.server = wsgi.Server(name, self.application) def start(self): """Start serving this API using loaded configuration.""" - self.server.start() + self.server.start(self.host, self.port) def stop(self): """Stop serving this API.""" diff --git a/nova/wsgi.py b/nova/wsgi.py index 82ba006c8..328dc083f 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -35,48 +35,78 @@ from paste import deploy from nova import exception from nova import flags -from nova import log as logging +from nova import log from nova import utils FLAGS = flags.FLAGS -LOG = logging.getLogger('nova.wsgi') +LOG = log.getLogger('nova.wsgi') class Server(object): """Server class to manage multiple WSGI sockets and applications.""" default_pool_size = 1000 - logger_name = "eventlet.wsgi.server" - def __init__(self, name, app, host, port, pool_size=None): + def __init__(self, name, app, pool_size=None): + """Initialize, but do not start, a WSGI server. + + :param name: The name to use for logging and other human purposes. + :param app: The WSGI application to serve. + :param pool_size: Maximum number of eventlets to spawn concurrently. + :returns: None + + """ self.name = name self.app = app - self.host = host - self.port = port - self._pool = eventlet.GreenPool(pool_size or self.default_pool_size) - self._log = logging.WritableLogger(logging.getLogger(self.logger_name)) + self.pool_size = pool_size or self.default_pool_size def _start(self, socket): - """Blocking eventlet WSGI server launched from the real 'start'.""" - eventlet.wsgi.server(socket, - self.app, - custom_pool=self._pool, - log=self._log) - - def start(self, backlog=128): - """Serve given WSGI application using the given parameters.""" - socket = eventlet.listen((self.host, self.port), backlog=backlog) + """Run the blocking eventlet WSGI server. + + :param socket: The socket where the WSGI server will serve it's app. + :returns: None + + """ + pool = eventlet.GreenPool(self.pool_size) + logger = log.WritableLogger(log.getLogger("eventlet.wsgi.server")) + eventlet.wsgi.server(socket, self.app, custom_pool=pool, log=logger) + + def start(self, host, port, backlog=128): + """Start serving a WSGI application. + + :param host: IP address to serve the application. + :param port: Port number to server the application. + :param backlog: Maximum number of queued connections. + :returns: None + + """ + socket = eventlet.listen((host, port), backlog=backlog) self._server = eventlet.spawn(self._start, socket) (self.host, self.port) = socket.getsockname() - LOG.info(_('Starting %(name)s on %(host)s:%(port)s') % self.__dict__) + LOG.info(_("Started %(name)s on %(host)s:%(port)s") % self.__dict__) def stop(self): - """Stop this server by killing the greenthread running it.""" + """Stop this server. + + This is not a very nice action, as currently the method by which a + server is stopped is by killing it's eventlet. + + :returns: None + + """ + LOG.debug(_("Stopping WSGI server.")) self._server.kill() def wait(self): - """Wait until server has been stopped.""" + """Block, until the server has stopped. + + Waits on the server's eventlet to finish, then returns. + + :returns: None + + """ + LOG.debug(_("Waiting for WSGI server to stop.")) self._server.wait() @@ -305,57 +335,50 @@ class Router(object): return app -def paste_config_file(basename): - """Find the best location in the system for a paste config file. +class Loader(object): + """Used to load WSGI applications from paste configurations.""" - Search Order - ------------ + def __init__(self, config_path=None): + """Initialize the loader, and attempt to find the config. - The search for a paste config file honors `FLAGS.state_path`, which in a - version checked out from bzr will be the `nova` directory in the top level - of the checkout, and in an installation for a package for your distribution - will likely point to someplace like /etc/nova. + :param config_path: Full or relative path to the paste config. + :returns: None - This method tries to load places likely to be used in development or - experimentation before falling back to the system-wide configuration - in `/etc/nova/`. + """ + config_path = config_path or FLAGS.api_paste_config + self.config_path = self._find_config(config_path) - * Current working directory - * the `etc` directory under state_path, because when working on a checkout - from bzr this will point to the default - * top level of FLAGS.state_path, for distributions - * /etc/nova, which may not be diffrerent from state_path on your distro + def _find_config(self, config_path): + """Find the paste configuration file using the given hint. - """ - configfiles = [basename, - os.path.join(FLAGS.state_path, 'etc', 'nova', basename), - os.path.join(FLAGS.state_path, 'etc', basename), - os.path.join(FLAGS.state_path, basename), - '/etc/nova/%s' % basename] - for configfile in configfiles: - if os.path.exists(configfile): - return configfile - - raise Exception(_("Unable to find paste.deploy config '%s'") % basename) - - -def load_paste_configuration(filename, appname): - """Returns a paste configuration dict, or None.""" - filename = os.path.abspath(filename) - config = None - try: - config = deploy.appconfig('config:%s' % filename, name=appname) - except LookupError: - pass - return config - - -def load_paste_app(filename, appname): - """Builds a wsgi app from a paste config, None if app not configured.""" - filename = os.path.abspath(filename) - app = None - try: - app = deploy.loadapp('config:%s' % filename, name=appname) - except LookupError: - pass - return app + :param config_path: Full or relative path to the paste config. + :returns: Full path of the paste config, if it exists. + :raises: `nova.exception.PasteConfigNotFound` + + """ + possible_locations = [ + config_path, + os.path.join(FLAGS.state_path, "etc", "nova", config_path), + os.path.join(FLAGS.state_path, "etc", config_path), + os.path.join(FLAGS.state_path, config_path), + "/etc/nova/%s" % config_path, + ] + + for path in possible_locations: + if os.path.exists(path): + return os.path.abspath(path) + + raise exception.PasteConfigNotFound(path=os.path.abspath(config_path)) + + def load_app(self, name): + """Return the paste URLMap wrapped WSGI application. + + :param name: Name of the application to load. + :returns: Paste URLMap object wrapping the requested application. + :raises: `nova.exception.PasteAppNotFound` + + """ + app = deploy.loadapp("config:%s" % self.config_path, name=name) + if app is None: + raise exception.PasteAppNotFound(name=name, path=self.config_path) + return app -- cgit From c1b70cc20a17e99fedb0f0a93139424fb89dd9e9 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Sun, 19 Jun 2011 20:26:47 -0400 Subject: Cleanup. --- nova/service.py | 22 +++++++++++++++++++--- nova/tests/integrated/integrated_helpers.py | 5 ++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/nova/service.py b/nova/service.py index e27d61371..bb38cddb6 100644 --- a/nova/service.py +++ b/nova/service.py @@ -251,15 +251,31 @@ class WSGIService(object): self.server = wsgi.Server(name, self.application) def start(self): - """Start serving this API using loaded configuration.""" + """Start serving this service using loaded configuration. + + Also, retrieve updated port number in case '0' was passed in, which + indicates a random port should be used. + + :returns: None + + """ self.server.start(self.host, self.port) + self.port = self.server.port def stop(self): - """Stop serving this API.""" + """Stop serving this API. + + :returns: None + + """ self.server.stop() def wait(self): - """Wait for the service to stop serving this API.""" + """Wait for the service to stop serving this API. + + :returns: None + + """ self.server.wait() diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 8809bf5f8..26de86e74 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -173,10 +173,9 @@ class _IntegratedTestBase(test.TestCase): def _start_api_service(self): osapi = service.WSGIService("osapi") osapi.start() + self.auth_url = 'http://%s:%s/v1.1' % (osapi.host, osapi.port) + LOG.warn(self.auth_url) - host = osapi.server.host - port = osapi.server.port - self.auth_url = 'http://%s:%s/v1.1' % (host, port) def tearDown(self): self.context.cleanup() -- cgit From 5a26a6523cfba2fdeaf0abebac8921f2a3322b13 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Sun, 19 Jun 2011 21:28:51 -0400 Subject: Added tests for WSGI loader. --- nova/wsgi.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/wsgi.py b/nova/wsgi.py index 328dc083f..ce0caf65b 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -378,7 +378,8 @@ class Loader(object): :raises: `nova.exception.PasteAppNotFound` """ - app = deploy.loadapp("config:%s" % self.config_path, name=name) - if app is None: + try: + return deploy.loadapp("config:%s" % self.config_path, name=name) + except LookupError as err: + LOG.error(err) raise exception.PasteAppNotFound(name=name, path=self.config_path) - return app -- cgit From 93d6a1c727ffa5ac2972a26fc8a1e38edc84684a Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Sun, 19 Jun 2011 21:29:43 -0400 Subject: No, really. Added tests for WSGI loader. --- nova/tests/test_wsgi.py | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 nova/tests/test_wsgi.py diff --git a/nova/tests/test_wsgi.py b/nova/tests/test_wsgi.py new file mode 100644 index 000000000..245b51d81 --- /dev/null +++ b/nova/tests/test_wsgi.py @@ -0,0 +1,79 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Unit tests for `nova.wsgi`.""" + +import os.path +import tempfile + +import unittest2 as unittest + +import nova.exception +import nova.test +import nova.wsgi + + +class TestNothingExists(unittest.TestCase): + """Loader tests where os.path.exists always returns False.""" + + def setUp(self): + self._os_path_exists = os.path.exists + os.path.exists = lambda _: False + + def test_config_not_found(self): + self.assertRaises( + nova.exception.PasteConfigNotFound, + nova.wsgi.Loader, + ) + + def tearDown(self): + os.path.exists = self._os_path_exists + + +class TestNormalFilesystem(unittest.TestCase): + """Loader tests where os.path.exists always returns True.""" + + _paste_config = """ +[app:test_app] +use = egg:Paste#static +document_root = /tmp + """ + + def setUp(self): + self.config = tempfile.NamedTemporaryFile(mode="w+t") + self.config.write(self._paste_config.lstrip()) + self.config.seek(0) + self.config.flush() + self.loader = nova.wsgi.Loader(self.config.name) + + def test_config_found(self): + self.assertEquals(self.config.name, self.loader.config_path) + + def test_app_not_found(self): + self.assertRaises( + nova.exception.PasteAppNotFound, + self.loader.load_app, + "non-existant app", + ) + + def test_app_found(self): + url_parser = self.loader.load_app("test_app") + self.assertEquals("/tmp", url_parser.directory) + + def tearDown(self): + self.config.close() -- cgit From dd870291a32d18d0f62592a73a03b9038ae5c3da Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 20 Jun 2011 10:12:43 -0400 Subject: Cleanup and addition of tests for WSGI server. --- bin/nova-api | 2 +- nova/tests/test_wsgi.py | 21 ++++++++++++++++++--- nova/utils.py | 2 -- nova/wsgi.py | 18 ++++++++++++------ tools/pip-requires | 2 +- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index 885fb0ba4..89112a159 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -1,6 +1,6 @@ #!/usr/bin/env python # 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. diff --git a/nova/tests/test_wsgi.py b/nova/tests/test_wsgi.py index 245b51d81..c19bd3ef6 100644 --- a/nova/tests/test_wsgi.py +++ b/nova/tests/test_wsgi.py @@ -28,7 +28,7 @@ import nova.test import nova.wsgi -class TestNothingExists(unittest.TestCase): +class TestLoaderNothingExists(unittest.TestCase): """Loader tests where os.path.exists always returns False.""" def setUp(self): @@ -45,8 +45,8 @@ class TestNothingExists(unittest.TestCase): os.path.exists = self._os_path_exists -class TestNormalFilesystem(unittest.TestCase): - """Loader tests where os.path.exists always returns True.""" +class TestLoaderNormalFilesystem(unittest.TestCase): + """Loader tests with normal filesystem (unmodified os.path module).""" _paste_config = """ [app:test_app] @@ -77,3 +77,18 @@ document_root = /tmp def tearDown(self): self.config.close() + + +class TestWSGIServer(unittest.TestCase): + """WSGI server tests.""" + + def test_no_app(self): + server = nova.wsgi.Server("test_app", None) + self.assertEquals("test_app", server.name) + + def test_start_random_port(self): + server = nova.wsgi.Server("test_random", None) + server.start("127.0.0.1", 0) + self.assertNotEqual(0, server.port) + server.stop() + server.wait() diff --git a/nova/utils.py b/nova/utils.py index 22d0ce940..691134ada 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -238,8 +238,6 @@ def default_flagfile(filename='nova.conf'): filename = "./nova.conf" if not os.path.exists(filename): filename = '/etc/nova/nova.conf' - if not os.path.exists(filename): - return flagfile = ['--flagfile=%s' % filename] sys.argv = sys.argv[:1] + flagfile + sys.argv[1:] diff --git a/nova/wsgi.py b/nova/wsgi.py index ce0caf65b..8a7d38252 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -21,16 +21,16 @@ import os import sys + from xml.dom import minidom import eventlet import eventlet.wsgi -eventlet.patcher.monkey_patch(all=False, socket=True, time=True) -import routes +import greenlet import routes.middleware -import webob import webob.dec import webob.exc + from paste import deploy from nova import exception @@ -39,6 +39,9 @@ from nova import log from nova import utils +eventlet.patcher.monkey_patch(all=False, socket=True, time=True) + + FLAGS = flags.FLAGS LOG = log.getLogger('nova.wsgi') @@ -95,7 +98,7 @@ class Server(object): :returns: None """ - LOG.debug(_("Stopping WSGI server.")) + LOG.info(_("Stopping WSGI server.")) self._server.kill() def wait(self): @@ -106,8 +109,11 @@ class Server(object): :returns: None """ - LOG.debug(_("Waiting for WSGI server to stop.")) - self._server.wait() + LOG.info(_("Waiting for WSGI server to stop.")) + try: + self._server.wait() + except greenlet.GreenletExit: + LOG.info(_("WSGI server has stopped.")) class Request(webob.Request): diff --git a/tools/pip-requires b/tools/pip-requires index 7849dbea9..13523d56e 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -8,7 +8,7 @@ amqplib==0.6.1 anyjson==0.2.4 boto==1.9b carrot==0.10.5 -eventlet==0.9.12 +eventlet lockfile==0.8 python-novaclient==2.5.3 python-daemon==1.5.5 -- cgit From 91050cc49e61b46f55722d8fe7e342c2f8ac926b Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 20 Jun 2011 10:39:17 -0400 Subject: Fix objectstore test. --- nova/tests/test_objectstore.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_objectstore.py b/nova/tests/test_objectstore.py index c35955c9c..be197a219 100644 --- a/nova/tests/test_objectstore.py +++ b/nova/tests/test_objectstore.py @@ -70,8 +70,8 @@ class S3APITestCase(test.TestCase): os.mkdir(FLAGS.buckets_path) router = s3server.S3Application(FLAGS.buckets_path) - self.server = wsgi.Server("s3api", router, FLAGS.s3_host, FLAGS.s3_port) - self.server.start() + self.server = wsgi.Server("s3api", router) + self.server.start(FLAGS.s3_host, FLAGS.s3_port) if not boto.config.has_section('Boto'): boto.config.add_section('Boto') -- cgit From 260af1f3edb7993a6b6374563ef2b46c7fa700df Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 20 Jun 2011 15:55:02 +0100 Subject: Enclosing tokens for xenapi filter in double quotes --- nova/network/xenapi_net.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py index 709ef7f34..7976a7265 100644 --- a/nova/network/xenapi_net.py +++ b/nova/network/xenapi_net.py @@ -56,8 +56,10 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): 'other_config': {}} network_ref = session.call_xenapi('network.create', network_rec) # 2 - find PIF for VLAN - expr = "field 'device' = '%s' and \ - field 'VLAN' = '-1'" % FLAGS.vlan_interface + # Note using double quotes inside single quotes as xapi filter + # only support tokens in double quotes + expr = 'field "device" = "%s" and \ + field "VLAN" = "-1"' % FLAGS.vlan_interface pifs = session.call_xenapi('PIF.get_all_records_where', expr) pif_ref = None # Multiple PIF are ok: we are dealing with a pool -- cgit From c178b3ce44d89b662c5925b7b65aab9c2540cf37 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 20 Jun 2011 14:54:53 -0400 Subject: pep8 fixes --- bin/nova-api | 2 -- nova/tests/integrated/integrated_helpers.py | 1 - nova/tests/test_wsgi.py | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index 89112a159..2345b3f2c 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -51,8 +51,6 @@ def main(): nova.flags.FLAGS(sys.argv) nova.utils.default_flagfile() - -# launch("osapi") pool = multiprocessing.Pool(2) pool.map_async(launch, ["ec2", "osapi"]) diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 26de86e74..47bd8c1e4 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -176,7 +176,6 @@ class _IntegratedTestBase(test.TestCase): self.auth_url = 'http://%s:%s/v1.1' % (osapi.host, osapi.port) LOG.warn(self.auth_url) - def tearDown(self): self.context.cleanup() super(_IntegratedTestBase, self).tearDown() diff --git a/nova/tests/test_wsgi.py b/nova/tests/test_wsgi.py index c19bd3ef6..be18baa95 100644 --- a/nova/tests/test_wsgi.py +++ b/nova/tests/test_wsgi.py @@ -62,7 +62,7 @@ document_root = /tmp self.loader = nova.wsgi.Loader(self.config.name) def test_config_found(self): - self.assertEquals(self.config.name, self.loader.config_path) + self.assertEquals(self.config.name, self.loader.config_path) def test_app_not_found(self): self.assertRaises( -- cgit From dca372d68ab99126f22c7467af12de30bb4488e4 Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 20 Jun 2011 15:28:34 -0700 Subject: 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 dbdb798a7..7226fcfa1 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): -- cgit From e849aa7112dcf24357d46f39195cfefce828a91a Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 20 Jun 2011 19:32:18 -0400 Subject: Removed logging logic from __init__, added concept of Launcher...no tests for it yet. --- bin/nova-api | 40 +++++++++----------------------- nova/__init__.py | 10 +------- nova/service.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- nova/wsgi.py | 1 - 4 files changed, 81 insertions(+), 40 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index 2345b3f2c..563d7c090 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -24,44 +24,26 @@ Starts both the EC2 and OpenStack APIs in separate processes. """ import sys -import multiprocessing -import nova.flags import nova.log import nova.service import nova.version -import nova.utils - - -def launch(service_name): - """Launch WSGI service with name matching 'paste' config file section.""" - service = nova.service.WSGIService(service_name) - service.start() - try: - service.wait() - except KeyboardInterrupt: - service.stop() def main(): - """Begin process of launching both EC2 and OSAPI services.""" - version = nova.version.version_string_with_vcs() - logger = nova.log.getLogger("nova.api") - logger.audit(_("Starting nova-api node (version %s)") % version) - - nova.flags.FLAGS(sys.argv) - nova.utils.default_flagfile() - - pool = multiprocessing.Pool(2) - pool.map_async(launch, ["ec2", "osapi"]) - pool.close() - + """Launch EC2 and OSAPI services.""" + ec2 = nova.service.WSGIService("ec2") + osapi = nova.service.WSGIService("osapi") + + launcher = nova.service.Launcher(sys.argv) + launcher.launch_service(ec2) + launcher.launch_service(osapi) + try: - pool.join() + launcher.wait() except KeyboardInterrupt: - logger.audit(_("Exiting...")) - pool.terminate() - + launcher.stop() + if __name__ == '__main__': sys.exit(main()) diff --git a/nova/__init__.py b/nova/__init__.py index 886eaee20..884c4a713 100644 --- a/nova/__init__.py +++ b/nova/__init__.py @@ -33,13 +33,5 @@ import gettext -import log as logging - -def initialize(): - gettext.install("nova", unicode=1) - logging.setup() - logging.debug(_("Initialized logging.")) - - -initialize() +gettext.install("nova", unicode=1) diff --git a/nova/service.py b/nova/service.py index bb38cddb6..d896de1fe 100644 --- a/nova/service.py +++ b/nova/service.py @@ -19,10 +19,12 @@ """Generic Node baseclass for all workers that run on hosts.""" -import greenlet import inspect +import multiprocessing import os +import greenlet + from eventlet import greenthread from nova import context @@ -53,6 +55,72 @@ flags.DEFINE_string('api_paste_config', "api-paste.ini", 'File name for the paste.deploy config for nova-api') +class Launcher(object): + """Launch one or more services and wait for them to complete.""" + + def __init__(self, _flags=None): + """Initialize the service launcher. + + :param _flags: Flags to use for the services we're going to load. + :returns: None + + """ + self._services = [] + self._version = version.version_string_with_vcs() + logging.setup() + logging.audit(_("Nova Version (%(_version)s)") % self.__dict__) + FLAGS(_flags) + utils.default_flagfile() + + + @staticmethod + def run_service(service): + """Start and wait for a service to finish. + + :param service: Service to run and wait for. + :returns: None + + """ + service.start() + try: + service.wait() + except KeyboardInterrupt: + service.stop() + + def launch_service(self, service): + """Load and start the given service. + + :param service: The service you would like to start. + :returns: None + + """ + process = multiprocessing.Process(target=self.run_service, + args=(service,)) + process.start() + self._services.append(process) + + def stop(self): + """Stop all services which are currently running. + + :returns: None + + """ + for service in self._services: + if service.is_alive(): + service.terminate() + + def wait(self): + """Waits until all services have been stopped, and then returns. + + :returns: None + + """ + for service in self._services: + service.join() + logging.info("Process exited with %d" % service.exitcode) + + + class Service(object): """Base class for workers that run on hosts.""" diff --git a/nova/wsgi.py b/nova/wsgi.py index 8a7d38252..097fa4734 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -109,7 +109,6 @@ class Server(object): :returns: None """ - LOG.info(_("Waiting for WSGI server to stop.")) try: self._server.wait() except greenlet.GreenletExit: -- cgit From 31e3aed4a25e0525797c7fc6cbdce0fa652b3878 Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Mon, 20 Jun 2011 20:33:15 -0700 Subject: Remove the unnecessary insertion of whitespace. This happens to be enough to match this patch apply on recent versions of XenServer / Xen Cloud Platform. --- .../xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch b/plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch index feaf1312d..d42a11eff 100644 --- a/plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch +++ b/plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch @@ -8,7 +8,7 @@ fi ;; -@@ -224,9 +225,11 @@ +@@ -224,6 +225,7 @@ remove) if [ "${TYPE}" = "vif" ] ;then @@ -16,7 +16,3 @@ xenstore-rm "${HOTPLUG}/hotplug" fi logger -t scripts-vif "${dev} has been removed" - remove_from_bridge - ;; - esac -+ -- cgit From c17c73b3d0f07046c677711853e1b93768526e47 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 21 Jun 2011 00:21:33 -0400 Subject: Tests for WSGI/Launcher --- bin/nova-api | 4 ++-- nova/service.py | 4 +--- nova/tests/test_service.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index 563d7c090..20ad4bfa5 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -38,12 +38,12 @@ def main(): launcher = nova.service.Launcher(sys.argv) launcher.launch_service(ec2) launcher.launch_service(osapi) - + try: launcher.wait() except KeyboardInterrupt: launcher.stop() - + if __name__ == '__main__': sys.exit(main()) diff --git a/nova/service.py b/nova/service.py index d896de1fe..08071e80c 100644 --- a/nova/service.py +++ b/nova/service.py @@ -69,9 +69,8 @@ class Launcher(object): self._version = version.version_string_with_vcs() logging.setup() logging.audit(_("Nova Version (%(_version)s)") % self.__dict__) - FLAGS(_flags) utils.default_flagfile() - + FLAGS(_flags or []) @staticmethod def run_service(service): @@ -120,7 +119,6 @@ class Launcher(object): logging.info("Process exited with %d" % service.exitcode) - class Service(object): """Base class for workers that run on hosts.""" diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py index d1cc8bd61..70ef80147 100644 --- a/nova/tests/test_service.py +++ b/nova/tests/test_service.py @@ -21,6 +21,7 @@ Unit Tests for remote procedure calls using queue """ import mox +import unittest2 as unittest from nova import context from nova import db @@ -30,6 +31,7 @@ from nova import rpc from nova import test from nova import service from nova import manager +from nova import wsgi from nova.compute import manager as compute_manager FLAGS = flags.FLAGS @@ -349,3 +351,31 @@ class ServiceTestCase(test.TestCase): serv.stop() db.service_destroy(ctxt, service_ref['id']) + + +class TestWSGIService(test.TestCase): + + def setUp(self): + super(TestWSGIService, self).setUp() + self.stubs.Set(wsgi.Loader, "load_app", mox.MockAnything()) + + def test_service_random_port(self): + test_service = service.WSGIService("test_service") + self.assertEquals(0, test_service.port) + test_service.start() + self.assertNotEqual(0, test_service.port) + test_service.stop() + +class TestLauncher(test.TestCase): + + def setUp(self): + super(TestLauncher, self).setUp() + self.stubs.Set(wsgi.Loader, "load_app", mox.MockAnything()) + self.service = service.WSGIService("test_service") + + def test_launch_app(self): + self.assertEquals(0, self.service.port) + launcher = service.Launcher() + launcher.launch_service(self.service) + self.assertEquals(0, self.service.port) + launcher.stop() -- cgit From e821b96feb49492c7b20afaa7ae0be5143dd4879 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 21 Jun 2011 00:32:31 -0400 Subject: Removed unneeded import. --- bin/nova-api | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/nova-api b/bin/nova-api index 20ad4bfa5..b94928c7b 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -27,7 +27,6 @@ import sys import nova.log import nova.service -import nova.version def main(): -- cgit From 679b00759ab2f183c3372465baa7daab1abeb25e Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 21 Jun 2011 00:35:45 -0400 Subject: Removed debugging and switched eventlet to monkey patch everything. --- nova/service.py | 1 - nova/wsgi.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/service.py b/nova/service.py index 08071e80c..4cf372377 100644 --- a/nova/service.py +++ b/nova/service.py @@ -116,7 +116,6 @@ class Launcher(object): """ for service in self._services: service.join() - logging.info("Process exited with %d" % service.exitcode) class Service(object): diff --git a/nova/wsgi.py b/nova/wsgi.py index 097fa4734..3c4f8a5da 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -39,7 +39,7 @@ from nova import log from nova import utils -eventlet.patcher.monkey_patch(all=False, socket=True, time=True) +eventlet.patcher.monkey_patch() FLAGS = flags.FLAGS -- cgit From c80dbac0b9563fb7afdc1a9ec3f0a851e2673236 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 21 Jun 2011 00:54:14 -0400 Subject: log -> logging to keep with convention --- nova/wsgi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/wsgi.py b/nova/wsgi.py index 3c4f8a5da..2991d1529 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -35,7 +35,7 @@ from paste import deploy from nova import exception from nova import flags -from nova import log +from nova import log as logging from nova import utils @@ -43,7 +43,7 @@ eventlet.patcher.monkey_patch() FLAGS = flags.FLAGS -LOG = log.getLogger('nova.wsgi') +LOG = logging.getLogger('nova.wsgi') class Server(object): @@ -72,7 +72,7 @@ class Server(object): """ pool = eventlet.GreenPool(self.pool_size) - logger = log.WritableLogger(log.getLogger("eventlet.wsgi.server")) + logger = logging.WritableLogger(logging.getLogger("eventlet.wsgi")) eventlet.wsgi.server(socket, self.app, custom_pool=pool, log=logger) def start(self, host, port, backlog=128): -- cgit From 90bfee5c963416c1f807fde4701f0b2755d5021c Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 21 Jun 2011 10:46:29 +0200 Subject: 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 01613d1d8..38c959fae 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 28ad4a417..8851b4a73 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 2fa50ac9b..73a26a087 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, -- cgit From 35e922a2db9b45314108b35e438e9229bea4b977 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 21 Jun 2011 11:39:55 +0100 Subject: fix comment line --- nova/network/xenapi_net.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py index 7976a7265..af295a4f8 100644 --- a/nova/network/xenapi_net.py +++ b/nova/network/xenapi_net.py @@ -56,8 +56,8 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): 'other_config': {}} network_ref = session.call_xenapi('network.create', network_rec) # 2 - find PIF for VLAN - # Note using double quotes inside single quotes as xapi filter - # only support tokens in double quotes + # NOTE(salvatore-orlando): using double quotes inside single quotes + # as xapi filter only support tokens in double quotes expr = 'field "device" = "%s" and \ field "VLAN" = "-1"' % FLAGS.vlan_interface pifs = session.call_xenapi('PIF.get_all_records_where', expr) -- cgit From 0e2b3e932d3e5fe00fed1da95e55808391d4832e Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Tue, 21 Jun 2011 16:55:45 +0400 Subject: Filter out datetime fields from instance_type --- nova/db/sqlalchemy/api.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7119f43eb..5dc2b9e7a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2510,7 +2510,10 @@ def instance_type_get_by_id(context, id): if not inst_type: raise exception.InstanceTypeNotFound(instance_type=id) else: - return dict(inst_type) + res = dict(inst_type) + for field in ['created_at', 'updated_at', 'deleted_at']: + res.pop(field, None) + return res @require_context @@ -2523,7 +2526,10 @@ def instance_type_get_by_name(context, name): if not inst_type: raise exception.InstanceTypeNotFoundByName(instance_type_name=name) else: - return dict(inst_type) + res = dict(inst_type) + for field in ['created_at', 'updated_at', 'deleted_at']: + res.pop(field, None) + return res @require_context @@ -2536,7 +2542,10 @@ def instance_type_get_by_flavor_id(context, id): if not inst_type: raise exception.FlavorNotFound(flavor_id=id) else: - return dict(inst_type) + res = dict(inst_type) + for field in ['created_at', 'updated_at', 'deleted_at']: + res.pop(field, None) + return res @require_admin_context -- cgit From 821f597228ed206564931b6693b134d04ef29e42 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 21 Jun 2011 09:46:00 -0400 Subject: Monkey patching 'os' kills multiprocessing's .join() functionality. Also, messed up the name of the eventlet WSGI logger. --- nova/wsgi.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/wsgi.py b/nova/wsgi.py index 2991d1529..0c869cfb8 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -39,7 +39,7 @@ from nova import log as logging from nova import utils -eventlet.patcher.monkey_patch() +eventlet.patcher.monkey_patch(socket=True, time=True) FLAGS = flags.FLAGS @@ -72,7 +72,8 @@ class Server(object): """ pool = eventlet.GreenPool(self.pool_size) - logger = logging.WritableLogger(logging.getLogger("eventlet.wsgi")) + log_name = "eventlet.wsgi.server" + logger = logging.WritableLogger(logging.getLogger(log_name)) eventlet.wsgi.server(socket, self.app, custom_pool=pool, log=logger) def start(self, host, port, backlog=128): -- cgit From afff25800521e7085ddff7e910195ef5a1f98732 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 21 Jun 2011 09:57:40 -0400 Subject: pep8 fix --- nova/tests/test_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py index 70ef80147..350dc62ee 100644 --- a/nova/tests/test_service.py +++ b/nova/tests/test_service.py @@ -366,6 +366,7 @@ class TestWSGIService(test.TestCase): self.assertNotEqual(0, test_service.port) test_service.stop() + class TestLauncher(test.TestCase): def setUp(self): -- cgit From 8a0d287631b5773a9868ae0e3cce6e2aef1ea501 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 21 Jun 2011 10:35:50 -0400 Subject: Oops, I broke --help on nova-api, fixed now. --- nova/service.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/nova/service.py b/nova/service.py index 4cf372377..f82c64d9c 100644 --- a/nova/service.py +++ b/nova/service.py @@ -67,10 +67,31 @@ class Launcher(object): """ self._services = [] self._version = version.version_string_with_vcs() + self._flags = _flags + self._setup_logging() + self._setup_flags() + + def _setup_logging(self): + """Logic to ensure logging is going to work correctly for services. + + :returns: None + + """ logging.setup() logging.audit(_("Nova Version (%(_version)s)") % self.__dict__) + + def _setup_flags(self): + """Logic to ensure flags/configuration are correctly set. + + :returns: None + + """ utils.default_flagfile() - FLAGS(_flags or []) + FLAGS(self._flags or []) + flags.DEFINE_flag(flags.HelpFlag()) + flags.DEFINE_flag(flags.HelpshortFlag()) + flags.DEFINE_flag(flags.HelpXMLFlag()) + FLAGS.ParseNewFlags() @staticmethod def run_service(service): -- cgit From 652ccbd3d255c5c95337d874b9cba10f0ce40ebb Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 21 Jun 2011 16:59:36 +0200 Subject: 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 e81ef944a..c27efc305 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 -- cgit From 742c21e4e79ce5a26975b31486ded3956a846c55 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 21 Jun 2011 11:25:44 -0400 Subject: Very small alterations, switched from using start() to pass host/port, to just defining them up front in init. Doesn't make sense to set them in start because we can't start more than once any way. Also, unbroke binaries. --- bin/nova-ajax-console-proxy | 5 +++-- bin/nova-direct-api | 7 +++++-- bin/nova-objectstore | 7 +++++-- bin/nova-vncproxy | 7 +++++-- nova/service.py | 13 ++++++++----- nova/tests/test_objectstore.py | 4 ++-- nova/tests/test_wsgi.py | 5 +++-- nova/wsgi.py | 39 ++++++++++++++++++++++----------------- 8 files changed, 53 insertions(+), 34 deletions(-) diff --git a/bin/nova-ajax-console-proxy b/bin/nova-ajax-console-proxy index d88f59e40..21cf68007 100755 --- a/bin/nova-ajax-console-proxy +++ b/bin/nova-ajax-console-proxy @@ -137,8 +137,9 @@ if __name__ == '__main__': utils.default_flagfile() FLAGS(sys.argv) logging.setup() - server = wsgi.Server() + acp_port = FLAGS.ajax_console_proxy_port acp = AjaxConsoleProxy() acp.register_listeners() - server.start(acp, FLAGS.ajax_console_proxy_port, host='0.0.0.0') + server = wsgi.Server("AJAX Console Proxy", acp, port=acp_port) + server.start() server.wait() diff --git a/bin/nova-direct-api b/bin/nova-direct-api index 83ec72722..5d63eb87f 100755 --- a/bin/nova-direct-api +++ b/bin/nova-direct-api @@ -93,6 +93,9 @@ if __name__ == '__main__': with_req = direct.PostParamsMiddleware(with_json) with_auth = direct.DelegatedAuthMiddleware(with_req) - server = wsgi.Server() - server.start(with_auth, FLAGS.direct_port, host=FLAGS.direct_host) + server = wsgi.Server("Direct API", + with_auth, + host=FLAGS.direct_host, + port=FLAGS.direct_port) + server.start() server.wait() diff --git a/bin/nova-objectstore b/bin/nova-objectstore index 6ef841b85..aa0dc063f 100755 --- a/bin/nova-objectstore +++ b/bin/nova-objectstore @@ -50,6 +50,9 @@ if __name__ == '__main__': FLAGS(sys.argv) logging.setup() router = s3server.S3Application(FLAGS.buckets_path) - server = wsgi.Server() - server.start(router, FLAGS.s3_port, host=FLAGS.s3_host) + server = wsgi.Server("S3 Objectstore", + router, + port=FLAGS.s3_port, + host=FLAGS.s3_host) + server.start() server.wait() diff --git a/bin/nova-vncproxy b/bin/nova-vncproxy index ccb97e3a3..62d3b948c 100755 --- a/bin/nova-vncproxy +++ b/bin/nova-vncproxy @@ -96,6 +96,9 @@ if __name__ == "__main__": service.serve() - server = wsgi.Server() - server.start(with_auth, FLAGS.vncproxy_port, host=FLAGS.vncproxy_host) + server = wsgi.Server("VNC Proxy", + with_auth, + host=FLAGS.vncproxy_host, + port=FLAGS.vncproxy_port) + server.start() server.wait() diff --git a/nova/service.py b/nova/service.py index f82c64d9c..854b4572d 100644 --- a/nova/service.py +++ b/nova/service.py @@ -331,10 +331,13 @@ class WSGIService(object): """ self.name = name self.loader = loader or wsgi.Loader() - self.application = self.loader.load_app(name) - self.host = getattr(FLAGS, '%s_listen' % name, "0.0.0.0") - self.port = getattr(FLAGS, '%s_listen_port' % name, 0) - self.server = wsgi.Server(name, self.application) + self.app = self.loader.load_app(name) + self.host = getattr(FLAGS, '%s_listen' % name, None) + self.port = getattr(FLAGS, '%s_listen_port' % name, None) + self.server = wsgi.Server(name, + self.app, + host=self.host, + port=self.port) def start(self): """Start serving this service using loaded configuration. @@ -345,7 +348,7 @@ class WSGIService(object): :returns: None """ - self.server.start(self.host, self.port) + self.server.start() self.port = self.server.port def stop(self): diff --git a/nova/tests/test_objectstore.py b/nova/tests/test_objectstore.py index be197a219..7e69565f3 100644 --- a/nova/tests/test_objectstore.py +++ b/nova/tests/test_objectstore.py @@ -70,8 +70,8 @@ class S3APITestCase(test.TestCase): os.mkdir(FLAGS.buckets_path) router = s3server.S3Application(FLAGS.buckets_path) - self.server = wsgi.Server("s3api", router) - self.server.start(FLAGS.s3_host, FLAGS.s3_port) + self.server = wsgi.Server() + self.server.start(router, host=FLAGS.s3_host, port=FLAGS.s3_port) if not boto.config.has_section('Boto'): boto.config.add_section('Boto') diff --git a/nova/tests/test_wsgi.py b/nova/tests/test_wsgi.py index be18baa95..010fb819e 100644 --- a/nova/tests/test_wsgi.py +++ b/nova/tests/test_wsgi.py @@ -87,8 +87,9 @@ class TestWSGIServer(unittest.TestCase): self.assertEquals("test_app", server.name) def test_start_random_port(self): - server = nova.wsgi.Server("test_random", None) - server.start("127.0.0.1", 0) + server = nova.wsgi.Server("test_random_port", None, host="127.0.0.1") + self.assertEqual(0, server.port) + server.start() self.assertNotEqual(0, server.port) server.stop() server.wait() diff --git a/nova/wsgi.py b/nova/wsgi.py index 0c869cfb8..23d29079f 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -47,47 +47,52 @@ LOG = logging.getLogger('nova.wsgi') class Server(object): - """Server class to manage multiple WSGI sockets and applications.""" + """Server class to manage a WSGI server, serving a WSGI application.""" default_pool_size = 1000 - def __init__(self, name, app, pool_size=None): + def __init__(self, name, app, host=None, port=None, pool_size=None): """Initialize, but do not start, a WSGI server. - :param name: The name to use for logging and other human purposes. + :param name: Pretty name for logging. :param app: The WSGI application to serve. + :param host: IP address to serve the application. + :param port: Port number to server the application. :param pool_size: Maximum number of eventlets to spawn concurrently. :returns: None """ self.name = name self.app = app - self.pool_size = pool_size or self.default_pool_size - - def _start(self, socket): + self.host = host or "0.0.0.0" + self.port = port or 0 + self._server = None + self._socket = None + self._pool = eventlet.GreenPool(pool_size or self.default_pool_size) + self._logger = logging.getLogger("eventlet.wsgi.server") + self._wsgi_logger = logging.WritableLogger(self._logger) + + def _start(self): """Run the blocking eventlet WSGI server. - :param socket: The socket where the WSGI server will serve it's app. :returns: None """ - pool = eventlet.GreenPool(self.pool_size) - log_name = "eventlet.wsgi.server" - logger = logging.WritableLogger(logging.getLogger(log_name)) - eventlet.wsgi.server(socket, self.app, custom_pool=pool, log=logger) + eventlet.wsgi.server(self._socket, + self.app, + custom_pool=self._pool, + log=self._wsgi_logger) - def start(self, host, port, backlog=128): + def start(self, backlog=128): """Start serving a WSGI application. - :param host: IP address to serve the application. - :param port: Port number to server the application. :param backlog: Maximum number of queued connections. :returns: None """ - socket = eventlet.listen((host, port), backlog=backlog) - self._server = eventlet.spawn(self._start, socket) - (self.host, self.port) = socket.getsockname() + self._socket = eventlet.listen((self.host, self.port), backlog=backlog) + self._server = eventlet.spawn(self._start) + (self.host, self.port) = self._socket.getsockname() LOG.info(_("Started %(name)s on %(host)s:%(port)s") % self.__dict__) def stop(self): -- cgit From 7c846ea890f3c7143fd5e158931fc415e53a9bf0 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 21 Jun 2011 11:50:28 -0400 Subject: Fixed objectstore test. --- nova/service.py | 4 ++-- nova/tests/test_objectstore.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/nova/service.py b/nova/service.py index 854b4572d..ca247c0f9 100644 --- a/nova/service.py +++ b/nova/service.py @@ -332,8 +332,8 @@ class WSGIService(object): self.name = name self.loader = loader or wsgi.Loader() self.app = self.loader.load_app(name) - self.host = getattr(FLAGS, '%s_listen' % name, None) - self.port = getattr(FLAGS, '%s_listen_port' % name, None) + self.host = getattr(FLAGS, '%s_listen' % name, "0.0.0.0") + self.port = getattr(FLAGS, '%s_listen_port' % name, 0) self.server = wsgi.Server(name, self.app, host=self.host, diff --git a/nova/tests/test_objectstore.py b/nova/tests/test_objectstore.py index 7e69565f3..39b4e18d7 100644 --- a/nova/tests/test_objectstore.py +++ b/nova/tests/test_objectstore.py @@ -70,8 +70,11 @@ class S3APITestCase(test.TestCase): os.mkdir(FLAGS.buckets_path) router = s3server.S3Application(FLAGS.buckets_path) - self.server = wsgi.Server() - self.server.start(router, host=FLAGS.s3_host, port=FLAGS.s3_port) + self.server = wsgi.Server("S3 Objectstore", + router, + host=FLAGS.s3_host, + port=FLAGS.s3_port) + self.server.start() if not boto.config.has_section('Boto'): boto.config.add_section('Boto') -- cgit From 186598a819c4e9c4b1b76aad61e7df56cdddd5be Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 21 Jun 2011 12:03:27 -0400 Subject: Removed whitespace. --- bin/nova-direct-api | 4 ++-- bin/nova-objectstore | 6 +++--- bin/nova-vncproxy | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/nova-direct-api b/bin/nova-direct-api index 5d63eb87f..c6cf9b2ff 100755 --- a/bin/nova-direct-api +++ b/bin/nova-direct-api @@ -93,8 +93,8 @@ if __name__ == '__main__': with_req = direct.PostParamsMiddleware(with_json) with_auth = direct.DelegatedAuthMiddleware(with_req) - server = wsgi.Server("Direct API", - with_auth, + server = wsgi.Server("Direct API", + with_auth, host=FLAGS.direct_host, port=FLAGS.direct_port) server.start() diff --git a/bin/nova-objectstore b/bin/nova-objectstore index aa0dc063f..1aef3a255 100755 --- a/bin/nova-objectstore +++ b/bin/nova-objectstore @@ -50,9 +50,9 @@ if __name__ == '__main__': FLAGS(sys.argv) logging.setup() router = s3server.S3Application(FLAGS.buckets_path) - server = wsgi.Server("S3 Objectstore", - router, - port=FLAGS.s3_port, + server = wsgi.Server("S3 Objectstore", + router, + port=FLAGS.s3_port, host=FLAGS.s3_host) server.start() server.wait() diff --git a/bin/nova-vncproxy b/bin/nova-vncproxy index 62d3b948c..72271df3a 100755 --- a/bin/nova-vncproxy +++ b/bin/nova-vncproxy @@ -96,7 +96,7 @@ if __name__ == "__main__": service.serve() - server = wsgi.Server("VNC Proxy", + server = wsgi.Server("VNC Proxy", with_auth, host=FLAGS.vncproxy_host, port=FLAGS.vncproxy_port) -- cgit From 6faecbb9617dfc2da283c7b46be36f512db14287 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 21 Jun 2011 12:31:33 -0400 Subject: if we get InstanceNotFound error on create, ignore (means it has been deleted before we got the create message) --- nova/compute/manager.py | 92 +++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 42 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8ab744855..e53ff75fc 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) - - 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) + # 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) + + 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): -- cgit From 13a51049ce5e76fd679b3dee978edce58db21d09 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 22 Jun 2011 09:33:46 -0400 Subject: fix some issues with flags and logging --- nova/service.py | 19 ++++++++++++++----- nova/utils.py | 10 ++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/nova/service.py b/nova/service.py index ca247c0f9..41c6551e0 100644 --- a/nova/service.py +++ b/nova/service.py @@ -38,6 +38,8 @@ from nova import version from nova import wsgi +LOG = logging.getLogger('nova.service') + FLAGS = flags.FLAGS flags.DEFINE_integer('report_interval', 10, 'seconds between nodes reporting state to datastore', @@ -58,18 +60,19 @@ flags.DEFINE_string('api_paste_config', "api-paste.ini", class Launcher(object): """Launch one or more services and wait for them to complete.""" - def __init__(self, _flags=None): + def __init__(self, flags=None): """Initialize the service launcher. - :param _flags: Flags to use for the services we're going to load. + :param flags: Flags to use for the services we're going to load. :returns: None """ self._services = [] self._version = version.version_string_with_vcs() - self._flags = _flags - self._setup_logging() + self._flags = flags self._setup_flags() + self._setup_logging() + self._log_flags() def _setup_logging(self): """Logic to ensure logging is going to work correctly for services. @@ -86,13 +89,19 @@ class Launcher(object): :returns: None """ - utils.default_flagfile() + utils.default_flagfile(args=self._flags) FLAGS(self._flags or []) flags.DEFINE_flag(flags.HelpFlag()) flags.DEFINE_flag(flags.HelpshortFlag()) flags.DEFINE_flag(flags.HelpXMLFlag()) FLAGS.ParseNewFlags() + def _log_flags(self): + LOG.debug(_("Full set of FLAGS:")) + for flag in FLAGS: + flag_get = FLAGS.get(flag, None) + LOG.debug("%(flag)s : %(flag_get)s" % locals()) + @staticmethod def run_service(service): """Start and wait for a service to finish. diff --git a/nova/utils.py b/nova/utils.py index e2ac16f31..a9b0f3128 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -226,8 +226,10 @@ def novadir(): return os.path.abspath(nova.__file__).split('nova/__init__.pyc')[0] -def default_flagfile(filename='nova.conf'): - for arg in sys.argv: +def default_flagfile(filename='nova.conf', args=None): + if args is None: + args = sys.argv + for arg in args: if arg.find('flagfile') != -1: break else: @@ -239,8 +241,8 @@ def default_flagfile(filename='nova.conf'): filename = "./nova.conf" if not os.path.exists(filename): filename = '/etc/nova/nova.conf' - flagfile = ['--flagfile=%s' % filename] - sys.argv = sys.argv[:1] + flagfile + sys.argv[1:] + flagfile = '--flagfile=%s' % filename + args.insert(1, flagfile) def debug(arg): -- cgit From 92b0307879ae0142bc6e0ad78c7c0bbc009c0884 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 22 Jun 2011 15:11:23 -0400 Subject: updating glance image fixtures with checksum attribute; fixing glance image service to use checksum attribute --- nova/image/glance.py | 2 +- nova/tests/api/openstack/test_image_metadata.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/nova/image/glance.py b/nova/image/glance.py index 6e058ab2f..55d948a32 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -59,7 +59,7 @@ class GlanceImageService(service.BaseImageService): """Provides storage and retrieval of disk image objects within Glance.""" GLANCE_ONLY_ATTRS = ['size', 'location', 'disk_format', - 'container_format'] + 'container_format', 'checksum'] # NOTE(sirp): Overriding to use _translate_to_service provided by # BaseImageService diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 56be0f1cc..b55306efd 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -37,6 +37,7 @@ class ImageMetaDataTest(unittest.TestCase): 'name': 'image1', 'deleted': False, 'container_format': None, + 'checksum': None, 'created_at': '2011-03-22T17:40:15', 'disk_format': None, 'updated_at': '2011-03-22T17:40:15', @@ -52,6 +53,7 @@ class ImageMetaDataTest(unittest.TestCase): 'name': 'image2', 'deleted': False, 'container_format': None, + 'checksum': None, 'created_at': '2011-03-22T17:40:15', 'disk_format': None, 'updated_at': '2011-03-22T17:40:15', @@ -67,6 +69,7 @@ class ImageMetaDataTest(unittest.TestCase): 'name': 'image3', 'deleted': False, 'container_format': None, + 'checksum': None, 'created_at': '2011-03-22T17:40:15', 'disk_format': None, 'updated_at': '2011-03-22T17:40:15', @@ -103,7 +106,10 @@ 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['metadata']['key1']) + expected = self.IMAGE_FIXTURES[0]['properties'] + self.assertEqual(len(expected), len(res_dict['metadata'])) + for (key, value) in res_dict['metadata'].items(): + self.assertEqual(value, res_dict['metadata'][key]) def test_show(self): req = webob.Request.blank('/v1.1/images/1/meta/key1') -- cgit From 979a9d2587584f60bfcad9a65da70df5ba3169be Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 22 Jun 2011 15:22:25 -0400 Subject: 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 48d576931..000e834f0 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) -- cgit From 7398819cc00a078a486b4d2f11846ff32db19a88 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 22 Jun 2011 17:07:26 -0400 Subject: moving image show/update into 'meta' container --- nova/api/openstack/image_metadata.py | 15 ++++++++++---- nova/tests/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 ebfe2bde9..07f0fb4fb 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 56be0f1cc..0faf9a26e 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) -- cgit From 1f9cd3e7c97034408b5afe3fc3720c48040dea97 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 22 Jun 2011 17:14:58 -0400 Subject: 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 77028be28..399cd2637 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 bc1903ca5..e77ff79b9 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) -- cgit From 548ac151cd1c7de5249fdeb651895917e83df488 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 22 Jun 2011 18:22:57 -0400 Subject: 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 399cd2637..691264553 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 e77ff79b9..9495eadec 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' -- cgit From 3b8ac87afbc1b2bb9371486697e1dd3ff22a4bc5 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 22 Jun 2011 20:03:35 -0400 Subject: 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 adf8b1c78..3b98b9067 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 -- cgit From b0e24d4dd2f4918ed1cbf85af4b31fdd09def1f6 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 22 Jun 2011 20:27:17 -0400 Subject: 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 3b98b9067..54eaf2082 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) -- cgit From b7684e0d36010050ce8254bbbf4573a6a624fa69 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Thu, 23 Jun 2011 04:28:42 +0400 Subject: allocate and release implementation --- nova/api/openstack/contrib/floating_ips.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index f7a939ddd..9bad6806c 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -18,6 +18,7 @@ from webob import exc from nova import exception from nova import network +from nova import rpc from nova.api.openstack import faults from nova.api.openstack import extensions @@ -69,12 +70,27 @@ class FloatingIPController(object): def create(self, req, body): context = req.environ['nova.context'] - return {'allocate': None} + try: + ip = self.network_api.allocate_floating_ip(context) + except rpc.RemoteError as ex: + if ex.exc_type == 'NoMoreAddresses': + raise exception.NoMoreFloatingIps() + else: + raise + + return {'allocated': ip} def delete(self,req, id): context = req.environ['nova.context'] - return {'release': None } + if id.isdigit(): + ip = self.network_api.get(id) + else: + ip = id + + self.network_api.release_floating_ip(context, address=ip) + + return {'released': ip } def associate(self, req, id, body): context = req.environ['nova.context'] -- cgit From b4defb29694f3f9397ed5335a003e5592668fbaa Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 22 Jun 2011 22:22:56 -0400 Subject: Initial unit test (failing) --- .../api/openstack/test_flavors_extra_specs.py | 70 ++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 nova/tests/api/openstack/test_flavors_extra_specs.py diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py new file mode 100644 index 000000000..1588ebf5a --- /dev/null +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -0,0 +1,70 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 University of Southern California +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import stubout +import unittest +import webob + +from nova import flags +from nova.api import openstack +from nova.tests.api.openstack import fakes +import nova.wsgi + + +def return_flavor_extra_specs(context, flavor_id): + return stub_flavor_extra_specs() + + +def stub_flavor_extra_specs(): + specs = { + "key1": "value1", + "key2": "value2", + "key3": "value3", + "key4": "value4", + "key5": "value5"} + return specs + + +class FlavorsExtraSpecsTest(unittest.TestCase): + + def setUp(self): + super(FlavorsExtraSpecsTest, self).setUp() + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.auth_data = {} + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) + fakes.stub_out_key_pair_funcs(self.stubs) + + def tearDown(self): + self.stubs.UnsetAll() + super(FlavorsExtraSpecsTest, self).tearDown() + + + def test_index(self): + self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', + return_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/extra') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) + self.assertEqual('application/json', res.headers['Content-Type']) + self.assertEqual('value1', res_dict['metadata']['key1']) + + + \ No newline at end of file -- cgit From a9de2c26432b0b6c77e941db0199fd72a54e2d69 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 22 Jun 2011 22:40:00 -0400 Subject: Added flavor extra specs controller --- nova/api/openstack/__init__.py | 6 ++ nova/api/openstack/flavor_extra_specs.py | 106 +++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 nova/api/openstack/flavor_extra_specs.py diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index ddd9580d7..464ba1a9a 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -32,6 +32,7 @@ from nova.api.openstack import faults from nova.api.openstack import backup_schedules from nova.api.openstack import consoles from nova.api.openstack import flavors +from nova.api.openstack import flavor_extra_specs from nova.api.openstack import images from nova.api.openstack import image_metadata from nova.api.openstack import ips @@ -178,3 +179,8 @@ class APIRouterV11(APIRouter): controller=server_metadata.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) + + mapper.resource("flavor_extra_specs", "extra", + controller=flavor_extra_specs.create_resource(), + parent_resource=dict(member_name='flavor', + collection_name='flavors')) diff --git a/nova/api/openstack/flavor_extra_specs.py b/nova/api/openstack/flavor_extra_specs.py new file mode 100644 index 000000000..2dcd85688 --- /dev/null +++ b/nova/api/openstack/flavor_extra_specs.py @@ -0,0 +1,106 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 University of Southern California +# 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 webob import exc + +from nova import db +from nova import quota +from nova.api.openstack import faults +from nova.api.openstack import wsgi + + +class Controller(object): + """ The flavor extra specs API controller for the Openstack API """ + + def __init__(self): + self.compute_api = compute.API() + super(Controller, self).__init__() + + def _get_extra_specs(self, context, flavor_id): + extra_specs = self.db.instance_type_extra_specs_get(context, flavor_id) + specs_dict = {} + for key, value in specs.iteritems(): + specs_dict[key] = value + return dict(extra=specs_dict) + + def _check_body(self, body): + if body == None or body == "": + expl = _('No Request Body') + raise exc.HTTPBadRequest(explanation=expl) + + def index(self, req, flavor_id): + """ Returns the list of extra specs for a givenflavor """ + context = req.environ['nova.context'] + return self._get_extra_specs(context, flavor_id) + + def create(self, req, flavor_id, body): + self._check_body(body) + context = req.environ['nova.context'] + specs = body.get('extra') + try: + self.db.instance_type_extra_specs_update_or_create(context, + flavor_id, + specs) + except quota.QuotaError as error: + self._handle_quota_error(error) + return body + + def update(self, req, flavor_id, id, body): + self._check_body(body) + context = req.environ['nova.context'] + if not id in body: + expl = _('Request body and URI mismatch') + raise exc.HTTPBadRequest(explanation=expl) + if len(body) > 1: + expl = _('Request body contains too many items') + raise exc.HTTPBadRequest(explanation=expl) + try: + self.db.instance_type_extra_specs_update_or_create(context, + flavor_id, + body) + except quota.QuotaError as error: + self._handle_quota_error(error) + + return body + + def show(self, req, flavor_id, id): + """ Return a single extra spec item """ + context = req.environ['nova.context'] + specs = self._get_extra_specs(context, flavor_id) + if id in specs['extra']: + return {id: specs['extra'][id]} + else: + return faults.Fault(exc.HTTPNotFound()) + + def delete(self, req, flavor_id, id): + """ Deletes an existing extra spec """ + context = req.environ['nova.context'] + self.instance_type_extra_specs_delete(context, flavor_id, id) + + def _handle_quota_error(self, error): + """Reraise quota errors as api-specific http exceptions.""" + if error.code == "MetadataLimitExceeded": + raise exc.HTTPBadRequest(explanation=error.message) + raise error + + +def create_resource(): + serializers = { + 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), + } + + return wsgi.Resource(Controller(), serializers=serializers) -- cgit From 173bb3c54b7ce9874f6bf880a5df8966fd508c38 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 22 Jun 2011 22:43:44 -0400 Subject: Bug fixing --- nova/api/openstack/flavor_extra_specs.py | 8 ++------ nova/tests/api/openstack/test_flavors_extra_specs.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/flavor_extra_specs.py b/nova/api/openstack/flavor_extra_specs.py index 2dcd85688..d14bed813 100644 --- a/nova/api/openstack/flavor_extra_specs.py +++ b/nova/api/openstack/flavor_extra_specs.py @@ -26,14 +26,10 @@ from nova.api.openstack import wsgi class Controller(object): """ The flavor extra specs API controller for the Openstack API """ - def __init__(self): - self.compute_api = compute.API() - super(Controller, self).__init__() - def _get_extra_specs(self, context, flavor_id): - extra_specs = self.db.instance_type_extra_specs_get(context, flavor_id) + extra_specs = db.instance_type_extra_specs_get(context, flavor_id) specs_dict = {} - for key, value in specs.iteritems(): + for key, value in extra_specs.iteritems(): specs_dict[key] = value return dict(extra=specs_dict) diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index 1588ebf5a..ac42d50a6 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -64,7 +64,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value1', res_dict['metadata']['key1']) + self.assertEqual('value1', res_dict['extra']['key1']) \ No newline at end of file -- cgit From cbff29973a3cbbd2997675f117bf62a589ef06a9 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 22 Jun 2011 22:52:42 -0400 Subject: Now stubbing nova.db instead of nova.db.api --- nova/tests/api/openstack/test_flavors_extra_specs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index ac42d50a6..e8df4ca39 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -56,7 +56,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_index(self): - self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', + self.stubs.Set(nova.db, 'instance_type_extra_specs_get', return_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra') req.environ['api.version'] = '1.1' -- cgit From a480b926a824766d3367eefed8d6757ad2919e7f Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 22 Jun 2011 23:01:41 -0400 Subject: Two tests passing --- .../api/openstack/test_flavors_extra_specs.py | 161 +++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index e8df4ca39..c09d6b285 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -26,10 +26,30 @@ from nova.tests.api.openstack import fakes import nova.wsgi +def return_create_flavor_extra_specs_max(context, flavor_id, extra_specs): + return stub_max_flavor_extra_specs() + + +def return_create_flavor_extra_specs(context, flavor_id, extra_specs): + return stub_flavor_extra_specs() + + +def return_flavor_extra_specs(context, flavor_id): + return stub_flavor_extra_specs() + + def return_flavor_extra_specs(context, flavor_id): return stub_flavor_extra_specs() +def return_empty_flavor_extra_specs(context, flavor_id): + return {} + + +def delete_flavor_extra_specs(context, flavor_id, key): + pass + + def stub_flavor_extra_specs(): specs = { "key1": "value1", @@ -38,6 +58,12 @@ def stub_flavor_extra_specs(): "key4": "value4", "key5": "value5"} return specs + +def stub_max_flavor_extra_specs(): + extra_specs = {"extra": {}} + for num in range(FLAGS.quota_extra_specs_items): + extra_specs['extra']['key%i' % num] = "blah" + return extra_specs class FlavorsExtraSpecsTest(unittest.TestCase): @@ -66,5 +92,140 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.assertEqual('application/json', res.headers['Content-Type']) self.assertEqual('value1', res_dict['extra']['key1']) + def test_index_no_data(self): + self.stubs.Set(nova.db, 'instance_type_extra_specs_get', + return_empty_flavor_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/extra') + 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('application/json', res.headers['Content-Type']) + self.assertEqual(0, len(res_dict['extra'])) + + def test_show(self): + self.stubs.Set(nova.db, 'instance_type_extra_specs_get', + return_flavor_metadata) + req = webob.Request.blank('/v1.1/flavors/1/extra/key5') + 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('application/json', res.headers['Content-Type']) + self.assertEqual('value5', res_dict['key5']) + + def test_show_meta_not_found(self): + self.stubs.Set(nova.db, 'instance_type_extra_specs_get', + return_empty_flavor_metadata) + req = webob.Request.blank('/v1.1/flavors/1/extra/key6') + 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_delete(self): + self.stubs.Set(nova.db, 'instance_type_extra_specs_delete', + delete_flavor_metadata) + req = webob.Request.blank('/v1.1/flavors/1/extra/key5') + req.environ['api.version'] = '1.1' + req.method = 'DELETE' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + + def test_create(self): + self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', + return_create_instance_type_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/extra') + req.environ['api.version'] = '1.1' + req.method = 'POST' + req.body = '{"metadata": {"key1": "value1"}}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) + self.assertEqual('value1', res_dict['extra']['key1']) + + def test_create_empty_body(self): + self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', + return_create_instance_type_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/extra') + req.environ['api.version'] = '1.1' + req.method = 'POST' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_update_item(self): + self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', + return_create_instance_type_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/extra/key1') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"key1": "value1"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) + res_dict = json.loads(res.body) + self.assertEqual('value1', res_dict['key1']) + + def test_update_item_empty_body(self): + self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', + return_create_instance_type_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/extra/key1') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + 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): + self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', + return_create_instance_type_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/extra/key1') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + 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) + + def test_update_item_body_uri_mismatch(self): + self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', + return_create_instance_type_extra_specs) + req = webob.Request.blank('/v1.1/flavors/1/extra/bad') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"key1": "value1"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_too_many_metadata_items_on_create(self): + self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', + return_create_instance_type_extra_specs) + data = {"metadata": {}} + for num in range(FLAGS.quota_metadata_items + 1): + data['metadata']['key%i' % num] = "blah" + json_string = str(data).replace("\'", "\"") + req = webob.Request.blank('/v1.1/flavors/1/extra') + req.environ['api.version'] = '1.1' + req.method = 'POST' + req.body = json_string + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_to_many_metadata_items_on_update_item(self): + self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', + return_create_instance_type_extra_specs_max) + req = webob.Request.blank('/v1.1/flavors/1/extra/key1') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"a new key": "a new value"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) \ No newline at end of file -- cgit From 6afcabac7442aa2e3944a3fef3d3452c189c1901 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 22 Jun 2011 23:14:39 -0400 Subject: Now passing unit tests --- nova/api/openstack/flavor_extra_specs.py | 12 +-- .../api/openstack/test_flavors_extra_specs.py | 93 +++++++--------------- 2 files changed, 36 insertions(+), 69 deletions(-) diff --git a/nova/api/openstack/flavor_extra_specs.py b/nova/api/openstack/flavor_extra_specs.py index d14bed813..6a6d2f7a1 100644 --- a/nova/api/openstack/flavor_extra_specs.py +++ b/nova/api/openstack/flavor_extra_specs.py @@ -27,7 +27,7 @@ class Controller(object): """ The flavor extra specs API controller for the Openstack API """ def _get_extra_specs(self, context, flavor_id): - extra_specs = db.instance_type_extra_specs_get(context, flavor_id) + extra_specs = db.api.instance_type_extra_specs_get(context, flavor_id) specs_dict = {} for key, value in extra_specs.iteritems(): specs_dict[key] = value @@ -48,9 +48,9 @@ class Controller(object): context = req.environ['nova.context'] specs = body.get('extra') try: - self.db.instance_type_extra_specs_update_or_create(context, - flavor_id, - specs) + db.api.instance_type_extra_specs_update_or_create(context, + flavor_id, + specs) except quota.QuotaError as error: self._handle_quota_error(error) return body @@ -65,7 +65,7 @@ class Controller(object): expl = _('Request body contains too many items') raise exc.HTTPBadRequest(explanation=expl) try: - self.db.instance_type_extra_specs_update_or_create(context, + db.api.instance_type_extra_specs_update_or_create(context, flavor_id, body) except quota.QuotaError as error: @@ -85,7 +85,7 @@ class Controller(object): def delete(self, req, flavor_id, id): """ Deletes an existing extra spec """ context = req.environ['nova.context'] - self.instance_type_extra_specs_delete(context, flavor_id, id) + db.api.instance_type_extra_specs_delete(context, flavor_id, id) def _handle_quota_error(self, error): """Reraise quota errors as api-specific http exceptions.""" diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index c09d6b285..14f6e7d43 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -26,10 +26,6 @@ from nova.tests.api.openstack import fakes import nova.wsgi -def return_create_flavor_extra_specs_max(context, flavor_id, extra_specs): - return stub_max_flavor_extra_specs() - - def return_create_flavor_extra_specs(context, flavor_id, extra_specs): return stub_flavor_extra_specs() @@ -58,16 +54,10 @@ def stub_flavor_extra_specs(): "key4": "value4", "key5": "value5"} return specs - -def stub_max_flavor_extra_specs(): - extra_specs = {"extra": {}} - for num in range(FLAGS.quota_extra_specs_items): - extra_specs['extra']['key%i' % num] = "blah" - return extra_specs class FlavorsExtraSpecsTest(unittest.TestCase): - + def setUp(self): super(FlavorsExtraSpecsTest, self).setUp() self.stubs = stubout.StubOutForTesting() @@ -80,9 +70,8 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.stubs.UnsetAll() super(FlavorsExtraSpecsTest, self).tearDown() - def test_index(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_get', + self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra') req.environ['api.version'] = '1.1' @@ -91,9 +80,9 @@ class FlavorsExtraSpecsTest(unittest.TestCase): res_dict = json.loads(res.body) self.assertEqual('application/json', res.headers['Content-Type']) self.assertEqual('value1', res_dict['extra']['key1']) - + def test_index_no_data(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_get', + self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_empty_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra') req.environ['api.version'] = '1.1' @@ -104,8 +93,8 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.assertEqual(0, len(res_dict['extra'])) def test_show(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_get', - return_flavor_metadata) + self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', + return_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra/key5') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) @@ -114,9 +103,9 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.assertEqual('application/json', res.headers['Content-Type']) self.assertEqual('value5', res_dict['key5']) - def test_show_meta_not_found(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_get', - return_empty_flavor_metadata) + def test_show_spec_not_found(self): + self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', + return_empty_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra/key6') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) @@ -124,8 +113,8 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.assertEqual(404, res.status_int) def test_delete(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_delete', - delete_flavor_metadata) + self.stubs.Set(nova.db.api, 'instance_type_extra_specs_delete', + delete_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra/key5') req.environ['api.version'] = '1.1' req.method = 'DELETE' @@ -133,12 +122,13 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.assertEqual(200, res.status_int) def test_create(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', - return_create_instance_type_extra_specs) + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra') req.environ['api.version'] = '1.1' req.method = 'POST' - req.body = '{"metadata": {"key1": "value1"}}' + req.body = '{"extra": {"key1": "value1"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -147,8 +137,9 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.assertEqual('value1', res_dict['extra']['key1']) def test_create_empty_body(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', - return_create_instance_type_extra_specs) + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra') req.environ['api.version'] = '1.1' req.method = 'POST' @@ -157,8 +148,9 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.assertEqual(400, res.status_int) def test_update_item(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', - return_create_instance_type_extra_specs) + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' @@ -171,8 +163,9 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.assertEqual('value1', res_dict['key1']) def test_update_item_empty_body(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', - return_create_instance_type_extra_specs) + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' @@ -181,8 +174,9 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.assertEqual(400, res.status_int) def test_update_item_too_many_keys(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', - return_create_instance_type_extra_specs) + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' @@ -192,8 +186,9 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.assertEqual(400, res.status_int) def test_update_item_body_uri_mismatch(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', - return_create_instance_type_extra_specs) + self.stubs.Set(nova.db.api, + 'instance_type_extra_specs_update_or_create', + return_create_flavor_extra_specs) req = webob.Request.blank('/v1.1/flavors/1/extra/bad') req.environ['api.version'] = '1.1' req.method = 'PUT' @@ -201,31 +196,3 @@ class FlavorsExtraSpecsTest(unittest.TestCase): req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) - - def test_too_many_metadata_items_on_create(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', - return_create_instance_type_extra_specs) - data = {"metadata": {}} - for num in range(FLAGS.quota_metadata_items + 1): - data['metadata']['key%i' % num] = "blah" - json_string = str(data).replace("\'", "\"") - req = webob.Request.blank('/v1.1/flavors/1/extra') - req.environ['api.version'] = '1.1' - req.method = 'POST' - req.body = json_string - req.headers["content-type"] = "application/json" - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, res.status_int) - - def test_to_many_metadata_items_on_update_item(self): - self.stubs.Set(nova.db, 'instance_type_extra_specs_update_or_create', - return_create_instance_type_extra_specs_max) - req = webob.Request.blank('/v1.1/flavors/1/extra/key1') - req.environ['api.version'] = '1.1' - req.method = 'PUT' - req.body = '{"a new key": "a new value"}' - req.headers["content-type"] = "application/json" - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, res.status_int) - - \ No newline at end of file -- cgit From 2059a683e11169a35b35819575926fc6cbc1a3f1 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 22 Jun 2011 23:27:49 -0400 Subject: run launcher first since it initializes global flags and logging --- bin/nova-api | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/nova-api b/bin/nova-api index b94928c7b..121f6f9a0 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -31,10 +31,11 @@ import nova.service def main(): """Launch EC2 and OSAPI services.""" + launcher = nova.service.Launcher(sys.argv) + ec2 = nova.service.WSGIService("ec2") osapi = nova.service.WSGIService("osapi") - launcher = nova.service.Launcher(sys.argv) launcher.launch_service(ec2) launcher.launch_service(osapi) -- cgit From 2e2c432e35bf9c283df83de8d76191855d2ce2be Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Wed, 22 Jun 2011 23:38:58 -0400 Subject: Returned code to original location --- nova/db/api.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index 0c9f45db6..0ac3153bc 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1292,6 +1292,17 @@ def instance_metadata_update_or_create(context, instance_id, metadata): #################### +def agent_build_create(context, values): + """Create a new agent build entry.""" + return IMPL.agent_build_create(context, values) + + +def agent_build_get_by_triple(context, hypervisor, os, architecture): + """Get agent build by hypervisor/OS/architecture triple.""" + return IMPL.agent_build_get_by_triple(context, hypervisor, os, + architecture) + + def agent_build_get_all(context): """Get all agent builds.""" return IMPL.agent_build_get_all(context) @@ -1307,16 +1318,6 @@ def agent_build_update(context, agent_build_id, values): IMPL.agent_build_update(context, agent_build_id, values) -def agent_build_create(context, values): - """Create a new agent build entry.""" - return IMPL.agent_build_create(context, values) - - -def agent_build_get_by_triple(context, hypervisor, os, architecture): - """Get agent build by hypervisor/OS/architecture triple.""" - return IMPL.agent_build_get_by_triple(context, hypervisor, os, - architecture) - #################### -- cgit From c2a0f15457ec49e95de0a2e7cd6c8b60e81a4994 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 23 Jun 2011 09:57:22 -0400 Subject: Fixed typo --- nova/network/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index 38bcbe0e5..bf0456522 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -328,7 +328,7 @@ class NetworkManager(manager.SchedulerDependentManager): if gateway_v6: # use a pre-defined gateway if one is provided - net['gateway_v6'] = str(list(gateway_v6)[1])) + net['gateway_v6'] = str(list(gateway_v6)[1]) else: net['gateway_v6'] = str(list(project_net_v6)[1]) -- cgit From 02085b96528b66b322f5c1ce5281d0284f9cbe40 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 23 Jun 2011 13:28:13 -0400 Subject: Move migration to newer version. --- .../versions/023_add_provider_firewall_rules.py | 75 ---------------------- .../versions/027_add_provider_firewall_rules.py | 75 ++++++++++++++++++++++ 2 files changed, 75 insertions(+), 75 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/027_add_provider_firewall_rules.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py deleted file mode 100644 index 5aa30f7a8..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/023_add_provider_firewall_rules.py +++ /dev/null @@ -1,75 +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. - -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 diff --git a/nova/db/sqlalchemy/migrate_repo/versions/027_add_provider_firewall_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/027_add_provider_firewall_rules.py new file mode 100644 index 000000000..5aa30f7a8 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/027_add_provider_firewall_rules.py @@ -0,0 +1,75 @@ +# 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 -- cgit From 1a3fb4332401e5fb3b5b090034ecf4fdf47246cf Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 23 Jun 2011 13:54:45 -0400 Subject: 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 000000000..70a00f999 --- /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']) + -- cgit From 203f3f85b6d66735f52013cbe5a736ef82d7a083 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 23 Jun 2011 13:59:26 -0400 Subject: 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 70a00f999..7ecaf1c09 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']) - -- cgit From ac4baa5990c45a6a521a1786e680426ba617c65a Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 23 Jun 2011 14:13:27 -0400 Subject: Add test for listing provider firewall rules. --- nova/tests/test_adminapi.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nova/tests/test_adminapi.py b/nova/tests/test_adminapi.py index 7ecaf1c09..2b90d49e9 100644 --- a/nova/tests/test_adminapi.py +++ b/nova/tests/test_adminapi.py @@ -87,3 +87,10 @@ 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']) + + def test_list_blocked_ips(self): + """Make sure we can see the external blocks that exist.""" + result = self.api.describe_external_address_blocks(self.context) + num = len(db.provider_fw_rule_get_all(self.context)) + # we only list IP, not tcp/udp/icmp rules + self.assertEqual(num / 3, len(result['externalIpBlockInfo'])) -- cgit From 6e2ebfa1dc29e50f74f1b337d1b5349bc3c78cdc Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 23 Jun 2011 14:16:11 -0400 Subject: Make sure there are actually rules to test against. --- nova/tests/test_adminapi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/test_adminapi.py b/nova/tests/test_adminapi.py index 2b90d49e9..4a96a3dd9 100644 --- a/nova/tests/test_adminapi.py +++ b/nova/tests/test_adminapi.py @@ -90,6 +90,7 @@ class AdminApiTestCase(test.TestCase): def test_list_blocked_ips(self): """Make sure we can see the external blocks that exist.""" + self.api.block_external_addresses(self.context, '1.1.1.2/32') result = self.api.describe_external_address_blocks(self.context) num = len(db.provider_fw_rule_get_all(self.context)) # we only list IP, not tcp/udp/icmp rules -- cgit From 9a6e9a1af9359fb4a9261f59f57113f252f0d6e9 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 23 Jun 2011 14:45:37 -0400 Subject: Make firewall rules tests idempotent, move IPy=>netaddr, add deltete test. --- nova/api/ec2/admin.py | 2 +- nova/tests/test_adminapi.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index b8fc8f114..df7876b9d 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -382,7 +382,7 @@ class AdminController(object): LOG.audit(_('Removing ip block from %s'), cidr, context=context) cidr = urllib.unquote(cidr).decode() # raise if invalid - IPy.IP(cidr) + netaddr.IPNetwork(cidr) rules = db.provider_fw_rule_get_all_by_cidr(context, cidr) for rule in rules: db.provider_fw_rule_destroy(context, rule['id']) diff --git a/nova/tests/test_adminapi.py b/nova/tests/test_adminapi.py index 4a96a3dd9..ce826fd5b 100644 --- a/nova/tests/test_adminapi.py +++ b/nova/tests/test_adminapi.py @@ -85,6 +85,7 @@ class AdminApiTestCase(test.TestCase): 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.api.remove_external_address_block(self.context, '1.1.1.1/32') self.assertEqual('OK', result['status']) self.assertEqual('Added 3 rules', result['message']) @@ -93,5 +94,18 @@ class AdminApiTestCase(test.TestCase): self.api.block_external_addresses(self.context, '1.1.1.2/32') result = self.api.describe_external_address_blocks(self.context) num = len(db.provider_fw_rule_get_all(self.context)) + self.api.remove_external_address_block(self.context, '1.1.1.2/32') # we only list IP, not tcp/udp/icmp rules self.assertEqual(num / 3, len(result['externalIpBlockInfo'])) + + def test_remove_ip_block(self): + """Remove ip blocks.""" + result = self.api.block_external_addresses(self.context, '1.1.1.3/32') + self.assertEqual('OK', result['status']) + num0 = len(db.provider_fw_rule_get_all(self.context)) + result = self.api.remove_external_address_block(self.context, + '1.1.1.3/32') + self.assertEqual('OK', result['status']) + self.assertEqual('Deleted 3 rules', result['message']) + num1 = len(db.provider_fw_rule_get_all(self.context)) + self.assert_(num1 < num0) -- cgit From 51d93c5b1722bef9783cd7572c1464a084ece0aa Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 23 Jun 2011 14:52:58 -0400 Subject: libvirt test for deleting provider firewall rules. --- nova/tests/test_libvirt.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index ee94d3c17..d12e21063 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -1115,6 +1115,13 @@ class IptablesFirewallTestCase(test.TestCase): provjump_rules.append(rule) self.assertEqual(1, len(provjump_rules)) + # remove a rule from the db, cast to compute to refresh rule + db.provider_fw_rule_destroy(admin_ctxt, provider_fw1['id']) + 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)) + class NWFilterTestCase(test.TestCase): def setUp(self): -- cgit From c2216547d0c55e32a4f8203129f4604f4ba004c7 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Fri, 24 Jun 2011 00:39:37 +0400 Subject: Implemented view and added tests --- nova/api/openstack/contrib/floating_ips.py | 57 +++++++++++++--------- nova/network/api.py | 2 +- nova/tests/api/openstack/contrib/__init__.py | 15 ++++++ .../api/openstack/contrib/test_floating_ips.py | 47 ++++++++++++++++++ 4 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 nova/tests/api/openstack/contrib/__init__.py create mode 100644 nova/tests/api/openstack/contrib/test_floating_ips.py diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 9bad6806c..dc048869c 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -22,13 +22,25 @@ from nova import rpc from nova.api.openstack import faults from nova.api.openstack import extensions -def _translate_floating_ip_detail_view(context, floating_ip): - #TODO(enugaev) implement view - return None -def _translate_floating_ips_view(context, floating_ips): - #TODO(adiantum) implement view - return [] +def _translate_floating_ip_view(floating_ip): + result = {'id': floating_ip['id'], + 'ip': floating_ip['address']} + if 'fixed_ip' in floating_ip: + result['fixed_ip'] = floating_ip['fixed_ip']['address'] + else: + result['fixed_ip'] = None + if 'instance' in floating_ip: + result['instance_id'] = floating_ip['instance']['id'] + else: + result['instance_id'] = None + return {'floating_ip': result} + + +def _translate_floating_ips_view(floating_ips): + return {'floating_ips': [_translate_floating_ip_view(floating_ip) + for floating_ip in floating_ips]} + class FloatingIPController(object): """The Volumes API controller for the OpenStack API.""" @@ -48,7 +60,7 @@ class FloatingIPController(object): super(FloatingIPController, self).__init__() def show(self, req, id): - """Return data about the given volume.""" + """Return data about the given floating ip.""" context = req.environ['nova.context'] try: @@ -56,16 +68,14 @@ class FloatingIPController(object): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return {'floating_ips': _translate_floating_ip_detail_view(context, - floating_ip)} + return _translate_floating_ip_view(floating_ip) def index(self, req): context = req.environ['nova.context'] floating_ips = self.network_api.list(context) - return {'floating_ips' : _translate_floating_ips_view(context, - floating_ips)} + return _translate_floating_ips_view(floating_ips) def create(self, req, body): context = req.environ['nova.context'] @@ -80,17 +90,13 @@ class FloatingIPController(object): return {'allocated': ip} - def delete(self,req, id): + def delete(self, req, id): context = req.environ['nova.context'] - - if id.isdigit(): - ip = self.network_api.get(id) - else: - ip = id - + + ip = self._get_ip_by_id(context, id) self.network_api.release_floating_ip(context, address=ip) - return {'released': ip } + return {'released': ip} def associate(self, req, id, body): context = req.environ['nova.context'] @@ -102,6 +108,14 @@ class FloatingIPController(object): return {'disassociate': None} + def _get_ip_by_id(self, context, value): + """Checks that value is id and then returns its address. + If value is ip then return value.""" + if value.isdigit(): + return self.network_api.get(context, value)['address'] + return value + + class Floating_ips(extensions.ExtensionDescriptor): def get_name(self): return "Floating_ips" @@ -124,9 +138,8 @@ class Floating_ips(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('floating_ips', FloatingIPController(), member_actions={ - 'associate' : 'POST', - 'disassociate' : 'POST'}) + 'associate': 'POST', + 'disassociate': 'POST'}) resources.append(res) return resources - diff --git a/nova/network/api.py b/nova/network/api.py index 001c5a6f6..a9a7ba552 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -35,7 +35,7 @@ class API(base.Base): """API for interacting with the network manager.""" def get(self, context, id): - rv = self.db.floating_ip_get(context) + rv = self.db.floating_ip_get(context, id) return dict(rv.iteritems()) def list(self, context): diff --git a/nova/tests/api/openstack/contrib/__init__.py b/nova/tests/api/openstack/contrib/__init__.py new file mode 100644 index 000000000..848908a95 --- /dev/null +++ b/nova/tests/api/openstack/contrib/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py new file mode 100644 index 000000000..9e079c9b0 --- /dev/null +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -0,0 +1,47 @@ +# Copyright 2011 Eldar Nugaev +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from nova import context +from nova import db +from nova import test +from nova.api.openstack.contrib.floating_ips import FloatingIPController +from nova.api.openstack.contrib.floating_ips import \ + _translate_floating_ip_view + + +class FloatingIpTest(test.TestCase): + address = "10.10.10.10" + + def _create_floating_ip(self): + """Create a volume object.""" + host = "fake_host" + return db.floating_ip_create(self.context, + {'address': self.address, + 'host': host}) + + def setUp(self): + super(FloatingIpTest, self).setUp() + self.floating_ips = FloatingIPController() + self.context = context.get_admin_context() + + def test_translate_floating_ip_view(self): + floating_ip_address = self._create_floating_ip() + floating_ip = db.floating_ip_get_by_address(self.context, + floating_ip_address) + view = _translate_floating_ip_view(floating_ip) + self.assertTrue('floating_ip' in view) + self.assertTrue(view['floating_ip']['id']) + self.assertEqual(view['floating_ip']['ip'], self.address) + self.assertEqual(view['floating_ip']['fixed_ip'], None) + self.assertEqual(view['floating_ip']['instance_id'], None) -- cgit From 655a783d5a0ef2ddadcf119793cd34513a45fe27 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 23 Jun 2011 21:31:00 -0400 Subject: Created Bootstrapper to handle Nova bootstrapping logic. --- bin/nova-api | 5 +++-- nova/service.py | 36 +----------------------------------- nova/utils.py | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index 121f6f9a0..ea99a1b48 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -25,17 +25,18 @@ Starts both the EC2 and OpenStack APIs in separate processes. import sys -import nova.log import nova.service +import nova.utils def main(): """Launch EC2 and OSAPI services.""" - launcher = nova.service.Launcher(sys.argv) + nova.utils.Bootstrapper.bootstrap_binary(sys.argv) ec2 = nova.service.WSGIService("ec2") osapi = nova.service.WSGIService("osapi") + launcher = nova.service.Launcher() launcher.launch_service(ec2) launcher.launch_service(osapi) diff --git a/nova/service.py b/nova/service.py index 41c6551e0..00e4f61e5 100644 --- a/nova/service.py +++ b/nova/service.py @@ -60,47 +60,13 @@ flags.DEFINE_string('api_paste_config', "api-paste.ini", class Launcher(object): """Launch one or more services and wait for them to complete.""" - def __init__(self, flags=None): + def __init__(self): """Initialize the service launcher. - :param flags: Flags to use for the services we're going to load. :returns: None """ self._services = [] - self._version = version.version_string_with_vcs() - self._flags = flags - self._setup_flags() - self._setup_logging() - self._log_flags() - - def _setup_logging(self): - """Logic to ensure logging is going to work correctly for services. - - :returns: None - - """ - logging.setup() - logging.audit(_("Nova Version (%(_version)s)") % self.__dict__) - - def _setup_flags(self): - """Logic to ensure flags/configuration are correctly set. - - :returns: None - - """ - utils.default_flagfile(args=self._flags) - FLAGS(self._flags or []) - flags.DEFINE_flag(flags.HelpFlag()) - flags.DEFINE_flag(flags.HelpshortFlag()) - flags.DEFINE_flag(flags.HelpXMLFlag()) - FLAGS.ParseNewFlags() - - def _log_flags(self): - LOG.debug(_("Full set of FLAGS:")) - for flag in FLAGS: - flag_get = FLAGS.get(flag, None) - LOG.debug("%(flag)s : %(flag_get)s" % locals()) @staticmethod def run_service(service): diff --git a/nova/utils.py b/nova/utils.py index a9b0f3128..a6b8d4cbe 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -743,3 +743,40 @@ def is_uuid_like(val): if not isinstance(val, basestring): return False return (len(val) == 36) and (val.count('-') == 4) + + +class Bootstrapper(object): + """Provides environment bootstrapping capabilities for entry points.""" + + @staticmethod + def bootstrap_binary(argv): + """Initialize the Nova environment using command line arguments.""" + Bootstrapper.setup_flags(argv) + Bootstrapper.setup_logging() + Bootstrapper.log_flags() + + @staticmethod + def setup_logging(): + """Initialize logging and log a message indicating the Nova version.""" + logging.setup() + logging.audit(_("Nova Version (%s)") % + version.version_string_with_vcs()) + + @staticmethod + def setup_flags(input_flags): + """Initialize flags, load flag file, and print help if needed.""" + default_flagfile(args=input_flags) + FLAGS(input_flags or []) + flags.DEFINE_flag(flags.HelpFlag()) + flags.DEFINE_flag(flags.HelpshortFlag()) + flags.DEFINE_flag(flags.HelpXMLFlag()) + FLAGS.ParseNewFlags() + + @staticmethod + def log_flags(): + """Log the list of all active flags being used.""" + logging.audit(_("Currently active flags:")) + for key in FLAGS: + value = FLAGS.get(key, None) + logging.audit(_("%(key)s : %(value)s" % locals())) + -- cgit From 7f578a0f657c076bf97c33dca15f1c78bd11b607 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Thu, 23 Jun 2011 22:55:51 -0400 Subject: Now automatically populates the instance_type dict with extra_specs upon being retrieved from the database. --- nova/db/api.py | 1 - nova/db/sqlalchemy/api.py | 26 ++++++++++-- nova/tests/test_instance_types_extra_specs.py | 59 +++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index 0ac3153bc..636748168 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1318,7 +1318,6 @@ def agent_build_update(context, agent_build_id, values): IMPL.agent_build_update(context, agent_build_id, values) - #################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 0692bc917..86950f109 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2581,6 +2581,18 @@ def instance_type_create(_context, values): return instance_type_ref +def _inst_type_query_to_dict(inst_type_query): + """Takes an instance type query returned by sqlalchemy + and returns it as a dictionary. + """ + extra_specs_objs = inst_type_query['extra_specs'] + extra_specs = dict([(x['key'], x['value']) for x in \ + inst_type_query['extra_specs']]) + inst_type_dict = dict(inst_type_query) + inst_type_dict['extra_specs'] = extra_specs + return inst_type_dict + + @require_context def instance_type_get_all(context, inactive=False): """ @@ -2589,17 +2601,19 @@ def instance_type_get_all(context, inactive=False): session = get_session() if inactive: inst_types = session.query(models.InstanceTypes).\ + options(joinedload('extra_specs')).\ order_by("name").\ all() else: inst_types = session.query(models.InstanceTypes).\ + options(joinedload('extra_specs')).\ filter_by(deleted=False).\ order_by("name").\ all() if inst_types: inst_dict = {} for i in inst_types: - inst_dict[i['name']] = dict(i) + inst_dict[i['name']] = _inst_type_query_to_dict(i) return inst_dict else: raise exception.NoInstanceTypesFound() @@ -2610,12 +2624,14 @@ def instance_type_get_by_id(context, id): """Returns a dict describing specific instance_type""" session = get_session() inst_type = session.query(models.InstanceTypes).\ + options(joinedload('extra_specs')).\ filter_by(id=id).\ first() + if not inst_type: raise exception.InstanceTypeNotFound(instance_type=id) else: - return dict(inst_type) + return _inst_type_query_to_dict(inst_type) @require_context @@ -2623,12 +2639,13 @@ def instance_type_get_by_name(context, name): """Returns a dict describing specific instance_type""" session = get_session() inst_type = session.query(models.InstanceTypes).\ + options(joinedload('extra_specs')).\ filter_by(name=name).\ first() if not inst_type: raise exception.InstanceTypeNotFoundByName(instance_type_name=name) else: - return dict(inst_type) + return _inst_type_query_to_dict(inst_type) @require_context @@ -2636,12 +2653,13 @@ def instance_type_get_by_flavor_id(context, id): """Returns a dict describing specific flavor_id""" session = get_session() inst_type = session.query(models.InstanceTypes).\ + options(joinedload('extra_specs')).\ filter_by(flavorid=int(id)).\ first() if not inst_type: raise exception.FlavorNotFound(flavor_id=id) else: - return dict(inst_type) + return _inst_type_query_to_dict(inst_type) @require_admin_context diff --git a/nova/tests/test_instance_types_extra_specs.py b/nova/tests/test_instance_types_extra_specs.py index e739225fc..c26cf82ff 100644 --- a/nova/tests/test_instance_types_extra_specs.py +++ b/nova/tests/test_instance_types_extra_specs.py @@ -104,3 +104,62 @@ class InstanceTypeExtraSpecsTestCase(test.TestCase): context.get_admin_context(), self.instance_type_id) self.assertEquals(expected_specs, actual_specs) + + def test_instance_type_get_by_id_with_extra_specs(self): + instance_type = db.api.instance_type_get_by_id( + context.get_admin_context(), + self.instance_type_id) + self.assertEquals(instance_type['extra_specs'], + dict(cpu_arch="x86_64", + cpu_model="Nehalem", + xpu_arch="fermi", + xpus="2", + xpu_model="Tesla 2050")) + instance_type = db.api.instance_type_get_by_id( + context.get_admin_context(), + 5) + self.assertEquals(instance_type['extra_specs'], {}) + + def test_instance_type_get_by_name_with_extra_specs(self): + instance_type = db.api.instance_type_get_by_name( + context.get_admin_context(), + "cg1.4xlarge") + self.assertEquals(instance_type['extra_specs'], + dict(cpu_arch="x86_64", + cpu_model="Nehalem", + xpu_arch="fermi", + xpus="2", + xpu_model="Tesla 2050")) + + instance_type = db.api.instance_type_get_by_name( + context.get_admin_context(), + "m1.small") + self.assertEquals(instance_type['extra_specs'], {}) + + def test_instance_type_get_by_id_with_extra_specs(self): + instance_type = db.api.instance_type_get_by_flavor_id( + context.get_admin_context(), + 105) + self.assertEquals(instance_type['extra_specs'], + dict(cpu_arch="x86_64", + cpu_model="Nehalem", + xpu_arch="fermi", + xpus="2", + xpu_model="Tesla 2050")) + + instance_type = db.api.instance_type_get_by_flavor_id( + context.get_admin_context(), + 2) + self.assertEquals(instance_type['extra_specs'], {}) + + def test_instance_type_get_all(self): + specs = dict(cpu_arch="x86_64", + cpu_model="Nehalem", + xpu_arch="fermi", + xpus='2', + xpu_model="Tesla 2050") + + types = db.api.instance_type_get_all(context.get_admin_context()) + + self.assertEquals(types['cg1.4xlarge']['extra_specs'], specs) + self.assertEquals(types['m1.small']['extra_specs'], {}) -- cgit From 188dd9117318cc4f5ebe0be9d19b9737a43ce68b Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Thu, 23 Jun 2011 23:42:44 -0400 Subject: Starting to transition instance type extra specs API to an extension API --- nova/api/openstack/__init__.py | 5 - nova/api/openstack/contrib/flavorextraspecs.py | 123 +++++++++++++++++++++ nova/api/openstack/flavor_extra_specs.py | 102 ----------------- .../api/openstack/test_flavors_extra_specs.py | 20 +++- 4 files changed, 139 insertions(+), 111 deletions(-) create mode 100644 nova/api/openstack/contrib/flavorextraspecs.py delete mode 100644 nova/api/openstack/flavor_extra_specs.py diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 857a5431b..859cac669 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -178,8 +178,3 @@ class APIRouterV11(APIRouter): controller=server_metadata.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) - - mapper.resource("flavor_extra_specs", "extra", - controller=flavor_extra_specs.create_resource(), - parent_resource=dict(member_name='flavor', - collection_name='flavors')) diff --git a/nova/api/openstack/contrib/flavorextraspecs.py b/nova/api/openstack/contrib/flavorextraspecs.py new file mode 100644 index 000000000..24c5da7b2 --- /dev/null +++ b/nova/api/openstack/contrib/flavorextraspecs.py @@ -0,0 +1,123 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 University of Southern California +# 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. + +""" The instance type extra specs extension""" + +from webob import exc + +from nova import db +from nova import quota +from nova.api.openstack import extensions +from nova.api.openstack import faults +from nova.api.openstack import wsgi + + +class FlavorExtraSpecsController(object): + """ The flavor extra specs API controller for the Openstack API """ + + def _get_extra_specs(self, context, flavor_id): + extra_specs = db.api.instance_type_extra_specs_get(context, flavor_id) + specs_dict = {} + for key, value in extra_specs.iteritems(): + specs_dict[key] = value + return dict(extra=specs_dict) + + def _check_body(self, body): + if body == None or body == "": + expl = _('No Request Body') + raise exc.HTTPBadRequest(explanation=expl) + + def index(self, req, flavor_id): + """ Returns the list of extra specs for a givenflavor """ + context = req.environ['nova.context'] + return self._get_extra_specs(context, flavor_id) + + def create(self, req, flavor_id, body): + self._check_body(body) + context = req.environ['nova.context'] + specs = body.get('extra') + try: + db.api.instance_type_extra_specs_update_or_create(context, + flavor_id, + specs) + except quota.QuotaError as error: + self._handle_quota_error(error) + return body + + def update(self, req, flavor_id, id, body): + self._check_body(body) + context = req.environ['nova.context'] + if not id in body: + expl = _('Request body and URI mismatch') + raise exc.HTTPBadRequest(explanation=expl) + if len(body) > 1: + expl = _('Request body contains too many items') + raise exc.HTTPBadRequest(explanation=expl) + try: + db.api.instance_type_extra_specs_update_or_create(context, + flavor_id, + body) + except quota.QuotaError as error: + self._handle_quota_error(error) + + return body + + def show(self, req, flavor_id, id): + """ Return a single extra spec item """ + context = req.environ['nova.context'] + specs = self._get_extra_specs(context, flavor_id) + if id in specs['extra']: + return {id: specs['extra'][id]} + else: + return faults.Fault(exc.HTTPNotFound()) + + def delete(self, req, flavor_id, id): + """ Deletes an existing extra spec """ + context = req.environ['nova.context'] + db.api.instance_type_extra_specs_delete(context, flavor_id, id) + + def _handle_quota_error(self, error): + """Reraise quota errors as api-specific http exceptions.""" + if error.code == "MetadataLimitExceeded": + raise exc.HTTPBadRequest(explanation=error.message) + raise error + +class Flavorextraspecs(extensions.ExtensionDescriptor): + def get_name(self): + return "FlavorExtraSpecs" + + def get_alias(self): + return "flavor-extra-specs" + + def get_description(self): + return "Instance type (flavor) extra specs" + + def get_namespace(self): + return \ + "http://docs.openstack.org/ext/flavor-extra-specs/api/v1.1" + + def get_updated(self): + return "2011-06-23T00:00:00+00:00" + + def get_resources(self): + resources = [] + + res = extensions.ResourceExtension('flavor-extra-specs', + FlavorExtraSpecsController()) + resources.append(res) + + return resources diff --git a/nova/api/openstack/flavor_extra_specs.py b/nova/api/openstack/flavor_extra_specs.py deleted file mode 100644 index 6a6d2f7a1..000000000 --- a/nova/api/openstack/flavor_extra_specs.py +++ /dev/null @@ -1,102 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 University of Southern California -# 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 webob import exc - -from nova import db -from nova import quota -from nova.api.openstack import faults -from nova.api.openstack import wsgi - - -class Controller(object): - """ The flavor extra specs API controller for the Openstack API """ - - def _get_extra_specs(self, context, flavor_id): - extra_specs = db.api.instance_type_extra_specs_get(context, flavor_id) - specs_dict = {} - for key, value in extra_specs.iteritems(): - specs_dict[key] = value - return dict(extra=specs_dict) - - def _check_body(self, body): - if body == None or body == "": - expl = _('No Request Body') - raise exc.HTTPBadRequest(explanation=expl) - - def index(self, req, flavor_id): - """ Returns the list of extra specs for a givenflavor """ - context = req.environ['nova.context'] - return self._get_extra_specs(context, flavor_id) - - def create(self, req, flavor_id, body): - self._check_body(body) - context = req.environ['nova.context'] - specs = body.get('extra') - try: - db.api.instance_type_extra_specs_update_or_create(context, - flavor_id, - specs) - except quota.QuotaError as error: - self._handle_quota_error(error) - return body - - def update(self, req, flavor_id, id, body): - self._check_body(body) - context = req.environ['nova.context'] - if not id in body: - expl = _('Request body and URI mismatch') - raise exc.HTTPBadRequest(explanation=expl) - if len(body) > 1: - expl = _('Request body contains too many items') - raise exc.HTTPBadRequest(explanation=expl) - try: - db.api.instance_type_extra_specs_update_or_create(context, - flavor_id, - body) - except quota.QuotaError as error: - self._handle_quota_error(error) - - return body - - def show(self, req, flavor_id, id): - """ Return a single extra spec item """ - context = req.environ['nova.context'] - specs = self._get_extra_specs(context, flavor_id) - if id in specs['extra']: - return {id: specs['extra'][id]} - else: - return faults.Fault(exc.HTTPNotFound()) - - def delete(self, req, flavor_id, id): - """ Deletes an existing extra spec """ - context = req.environ['nova.context'] - db.api.instance_type_extra_specs_delete(context, flavor_id, id) - - def _handle_quota_error(self, error): - """Reraise quota errors as api-specific http exceptions.""" - if error.code == "MetadataLimitExceeded": - raise exc.HTTPBadRequest(explanation=error.message) - raise error - - -def create_resource(): - serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), - } - - return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index 14f6e7d43..1fe0884b6 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -19,12 +19,16 @@ import json import stubout import unittest import webob +import os.path + from nova import flags from nova.api import openstack +from nova.api.openstack import extensions from nova.tests.api.openstack import fakes import nova.wsgi +FLAGS = flags.FLAGS def return_create_flavor_extra_specs(context, flavor_id, extra_specs): return stub_flavor_extra_specs() @@ -60,6 +64,8 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def setUp(self): super(FlavorsExtraSpecsTest, self).setUp() + FLAGS.osapi_extensions_path = os.path.join(os.path.dirname(__file__), + "extensions") self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthDatabase.data = {} @@ -73,13 +79,19 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/extra') - req.environ['api.version'] = '1.1' - res = req.get_response(fakes.wsgi_app()) + app = openstack.APIRouterV11() + ext_midware = extensions.ExtensionMiddleware(app) + #request = webob.Request.blank('/flavors-extra-specs/1') + request = webob.Request.blank('/flavors-extra-specs') + res = request.get_response(ext_midware) + print res + #req.environ['api.version'] = '1.1' + #res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value1', res_dict['extra']['key1']) + print res_dict + self.assertEqual('value1', res_dict['1']['key1']) def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', -- cgit From 7a9dc4adc343aa9cf8c21cef741b3bfe409fc41e Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 24 Jun 2011 00:45:53 -0400 Subject: Committing some broken code in advance of trying a different strategy for specifying args to extensions.ResoruceExtensions, using parent --- nova/api/openstack/contrib/flavorextraspecs.py | 21 ++++++++------ .../api/openstack/test_flavors_extra_specs.py | 33 ++++++++++------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/nova/api/openstack/contrib/flavorextraspecs.py b/nova/api/openstack/contrib/flavorextraspecs.py index 24c5da7b2..f2f740bff 100644 --- a/nova/api/openstack/contrib/flavorextraspecs.py +++ b/nova/api/openstack/contrib/flavorextraspecs.py @@ -34,7 +34,7 @@ class FlavorExtraSpecsController(object): specs_dict = {} for key, value in extra_specs.iteritems(): specs_dict[key] = value - return dict(extra=specs_dict) + return dict(extra_specs=specs_dict) def _check_body(self, body): if body == None or body == "": @@ -43,13 +43,14 @@ class FlavorExtraSpecsController(object): def index(self, req, flavor_id): """ Returns the list of extra specs for a givenflavor """ + print req.environ context = req.environ['nova.context'] return self._get_extra_specs(context, flavor_id) def create(self, req, flavor_id, body): self._check_body(body) context = req.environ['nova.context'] - specs = body.get('extra') + specs = body.get('extra_specs') try: db.api.instance_type_extra_specs_update_or_create(context, flavor_id, @@ -80,8 +81,8 @@ class FlavorExtraSpecsController(object): """ Return a single extra spec item """ context = req.environ['nova.context'] specs = self._get_extra_specs(context, flavor_id) - if id in specs['extra']: - return {id: specs['extra'][id]} + if id in specs['extra_specs']: + return {id: specs['extra_specs'][id]} else: return faults.Fault(exc.HTTPNotFound()) @@ -108,16 +109,18 @@ class Flavorextraspecs(extensions.ExtensionDescriptor): def get_namespace(self): return \ - "http://docs.openstack.org/ext/flavor-extra-specs/api/v1.1" + "http://docs.openstack.org/ext/flavor_extra_specs/api/v1.1" def get_updated(self): return "2011-06-23T00:00:00+00:00" def get_resources(self): resources = [] - - res = extensions.ResourceExtension('flavor-extra-specs', - FlavorExtraSpecsController()) + res = extensions.ResourceExtension('flavor_extra_specs/:(flavor_id)', + FlavorExtraSpecsController(), + member_actions={ + 'show' : 'flavor_extra_specs/:(flavor_id)/extra/:(id)' }) resources.append(res) - return resources + + diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index 1fe0884b6..caa07ee73 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -24,6 +24,7 @@ import os.path from nova import flags from nova.api import openstack +from nova.api.openstack import auth from nova.api.openstack import extensions from nova.tests.api.openstack import fakes import nova.wsgi @@ -61,6 +62,7 @@ def stub_flavor_extra_specs(): class FlavorsExtraSpecsTest(unittest.TestCase): + def setUp(self): super(FlavorsExtraSpecsTest, self).setUp() @@ -71,6 +73,9 @@ class FlavorsExtraSpecsTest(unittest.TestCase): fakes.FakeAuthDatabase.data = {} fakes.stub_out_auth(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) + self.mware = auth.AuthMiddleware( + extensions.ExtensionMiddleware( + openstack.APIRouterV11())) def tearDown(self): self.stubs.UnsetAll() @@ -79,39 +84,31 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) - app = openstack.APIRouterV11() - ext_midware = extensions.ExtensionMiddleware(app) - #request = webob.Request.blank('/flavors-extra-specs/1') - request = webob.Request.blank('/flavors-extra-specs') - res = request.get_response(ext_midware) - print res - #req.environ['api.version'] = '1.1' - #res = req.get_response(fakes.wsgi_app()) + request = webob.Request.blank('/flavor_extra_specs/1') + res = request.get_response(self.mware) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) self.assertEqual('application/json', res.headers['Content-Type']) - print res_dict - self.assertEqual('value1', res_dict['1']['key1']) + self.assertEqual('value1', res_dict['extra_specs']['key1']) def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_empty_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/extra') - req.environ['api.version'] = '1.1' - res = req.get_response(fakes.wsgi_app()) + req = webob.Request.blank('/flavor_extra_specs/1') + res = req.get_response(self.mware) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual(0, len(res_dict['extra'])) + self.assertEqual(0, len(res_dict['extra_specs'])) def test_show(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/extra/key5') - req.environ['api.version'] = '1.1' - res = req.get_response(fakes.wsgi_app()) - res_dict = json.loads(res.body) + req = webob.Request.blank('/flavor_extra_specs/1/extra/key5') + res = req.get_response(self.mware) + print res self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) self.assertEqual('application/json', res.headers['Content-Type']) self.assertEqual('value5', res_dict['key5']) -- cgit From 4a0fcd6c1d5540c4bec29ef2585987300654c8b7 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 24 Jun 2011 01:01:30 -0400 Subject: All tests passing --- nova/api/openstack/contrib/flavorextraspecs.py | 15 +++--- .../api/openstack/test_flavors_extra_specs.py | 53 +++++++++------------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/nova/api/openstack/contrib/flavorextraspecs.py b/nova/api/openstack/contrib/flavorextraspecs.py index f2f740bff..ac67fbf43 100644 --- a/nova/api/openstack/contrib/flavorextraspecs.py +++ b/nova/api/openstack/contrib/flavorextraspecs.py @@ -43,7 +43,6 @@ class FlavorExtraSpecsController(object): def index(self, req, flavor_id): """ Returns the list of extra specs for a givenflavor """ - print req.environ context = req.environ['nova.context'] return self._get_extra_specs(context, flavor_id) @@ -97,7 +96,9 @@ class FlavorExtraSpecsController(object): raise exc.HTTPBadRequest(explanation=error.message) raise error + class Flavorextraspecs(extensions.ExtensionDescriptor): + def get_name(self): return "FlavorExtraSpecs" @@ -116,11 +117,11 @@ class Flavorextraspecs(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - res = extensions.ResourceExtension('flavor_extra_specs/:(flavor_id)', - FlavorExtraSpecsController(), - member_actions={ - 'show' : 'flavor_extra_specs/:(flavor_id)/extra/:(id)' }) - resources.append(res) - return resources + res = extensions.ResourceExtension( + 'flavor_extra_specs', + FlavorExtraSpecsController(), + parent=dict(member_name='flavor', collection_name='flavors')) + resources.append(res) + return resources diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index caa07ee73..0fe7ec19f 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -31,6 +31,7 @@ import nova.wsgi FLAGS = flags.FLAGS + def return_create_flavor_extra_specs(context, flavor_id, extra_specs): return stub_flavor_extra_specs() @@ -62,7 +63,6 @@ def stub_flavor_extra_specs(): class FlavorsExtraSpecsTest(unittest.TestCase): - def setUp(self): super(FlavorsExtraSpecsTest, self).setUp() @@ -84,7 +84,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) - request = webob.Request.blank('/flavor_extra_specs/1') + request = webob.Request.blank('/flavors/1/flavor_extra_specs') res = request.get_response(self.mware) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) @@ -94,7 +94,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_empty_flavor_extra_specs) - req = webob.Request.blank('/flavor_extra_specs/1') + req = webob.Request.blank('/flavors/1/flavor_extra_specs') res = req.get_response(self.mware) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -104,9 +104,8 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_show(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) - req = webob.Request.blank('/flavor_extra_specs/1/extra/key5') + req = webob.Request.blank('/flavors/1/flavor_extra_specs/key5') res = req.get_response(self.mware) - print res self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) self.assertEqual('application/json', res.headers['Content-Type']) @@ -115,57 +114,52 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_show_spec_not_found(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_empty_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/extra/key6') - req.environ['api.version'] = '1.1' - res = req.get_response(fakes.wsgi_app()) + req = webob.Request.blank('/flavors/1/flavor_extra_specs/key6') + res = req.get_response(self.mware) res_dict = json.loads(res.body) self.assertEqual(404, res.status_int) def test_delete(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_delete', delete_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/extra/key5') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/flavors/1/flavor_extra_specs/key5') req.method = 'DELETE' - res = req.get_response(fakes.wsgi_app()) + res = req.get_response(self.mware) self.assertEqual(200, res.status_int) def test_create(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/extra') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/flavors/1/flavor_extra_specs') req.method = 'POST' - req.body = '{"extra": {"key1": "value1"}}' + req.body = '{"extra_specs": {"key1": "value1"}}' req.headers["content-type"] = "application/json" - res = req.get_response(fakes.wsgi_app()) + res = req.get_response(self.mware) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value1', res_dict['extra']['key1']) + self.assertEqual('value1', res_dict['extra_specs']['key1']) def test_create_empty_body(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/extra') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/flavors/1/flavor_extra_specs') req.method = 'POST' req.headers["content-type"] = "application/json" - res = req.get_response(fakes.wsgi_app()) + res = req.get_response(self.mware) self.assertEqual(400, res.status_int) def test_update_item(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/extra/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/flavors/1/flavor_extra_specs/key1') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" - res = req.get_response(fakes.wsgi_app()) + res = req.get_response(self.mware) self.assertEqual(200, res.status_int) self.assertEqual('application/json', res.headers['Content-Type']) res_dict = json.loads(res.body) @@ -175,33 +169,30 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/extra/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/flavors/1/flavor_extra_specs/key1') req.method = 'PUT' req.headers["content-type"] = "application/json" - res = req.get_response(fakes.wsgi_app()) + res = req.get_response(self.mware) self.assertEqual(400, res.status_int) def test_update_item_too_many_keys(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/extra/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/flavors/1/flavor_extra_specs/key1') req.method = 'PUT' req.body = '{"key1": "value1", "key2": "value2"}' req.headers["content-type"] = "application/json" - res = req.get_response(fakes.wsgi_app()) + res = req.get_response(self.mware) self.assertEqual(400, res.status_int) def test_update_item_body_uri_mismatch(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/extra/bad') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/flavors/1/flavor_extra_specs/bad') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" - res = req.get_response(fakes.wsgi_app()) + res = req.get_response(self.mware) self.assertEqual(400, res.status_int) -- cgit From 52319f7e4e55e78f4fdd9c76b3ab593322edc875 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 24 Jun 2011 01:02:28 -0400 Subject: Renamed from flavor_extra_specs to extra_specs --- nova/api/openstack/contrib/flavorextraspecs.py | 2 +- .../api/openstack/test_flavors_extra_specs.py | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/nova/api/openstack/contrib/flavorextraspecs.py b/nova/api/openstack/contrib/flavorextraspecs.py index ac67fbf43..7fc8e7954 100644 --- a/nova/api/openstack/contrib/flavorextraspecs.py +++ b/nova/api/openstack/contrib/flavorextraspecs.py @@ -118,7 +118,7 @@ class Flavorextraspecs(extensions.ExtensionDescriptor): def get_resources(self): resources = [] res = extensions.ResourceExtension( - 'flavor_extra_specs', + 'extra_specs', FlavorExtraSpecsController(), parent=dict(member_name='flavor', collection_name='flavors')) diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index 0fe7ec19f..686e9cf55 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -84,7 +84,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) - request = webob.Request.blank('/flavors/1/flavor_extra_specs') + request = webob.Request.blank('/flavors/1/extra_specs') res = request.get_response(self.mware) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) @@ -94,7 +94,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_empty_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/flavor_extra_specs') + req = webob.Request.blank('/flavors/1/extra_specs') res = req.get_response(self.mware) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -104,7 +104,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_show(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/flavor_extra_specs/key5') + req = webob.Request.blank('/flavors/1/extra_specs/key5') res = req.get_response(self.mware) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) @@ -114,7 +114,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_show_spec_not_found(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_empty_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/flavor_extra_specs/key6') + req = webob.Request.blank('/flavors/1/extra_specs/key6') res = req.get_response(self.mware) res_dict = json.loads(res.body) self.assertEqual(404, res.status_int) @@ -122,7 +122,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): def test_delete(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_delete', delete_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/flavor_extra_specs/key5') + req = webob.Request.blank('/flavors/1/extra_specs/key5') req.method = 'DELETE' res = req.get_response(self.mware) self.assertEqual(200, res.status_int) @@ -131,7 +131,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/flavor_extra_specs') + req = webob.Request.blank('/flavors/1/extra_specs') req.method = 'POST' req.body = '{"extra_specs": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -145,7 +145,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/flavor_extra_specs') + req = webob.Request.blank('/flavors/1/extra_specs') req.method = 'POST' req.headers["content-type"] = "application/json" res = req.get_response(self.mware) @@ -155,7 +155,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/flavor_extra_specs/key1') + req = webob.Request.blank('/flavors/1/extra_specs/key1') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" @@ -169,7 +169,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/flavor_extra_specs/key1') + req = webob.Request.blank('/flavors/1/extra_specs/key1') req.method = 'PUT' req.headers["content-type"] = "application/json" res = req.get_response(self.mware) @@ -179,7 +179,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/flavor_extra_specs/key1') + req = webob.Request.blank('/flavors/1/extra_specs/key1') req.method = 'PUT' req.body = '{"key1": "value1", "key2": "value2"}' req.headers["content-type"] = "application/json" @@ -190,7 +190,7 @@ class FlavorsExtraSpecsTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/flavors/1/flavor_extra_specs/bad') + req = webob.Request.blank('/flavors/1/extra_specs/bad') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" -- cgit From 48f3bccc3372023c35a75671e25e9089dd4ed836 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 24 Jun 2011 01:34:47 -0400 Subject: pep8 fixes --- nova/api/openstack/contrib/flavorextraspecs.py | 1 - nova/scheduler/host_filter.py | 24 ++++++++++++++++++++++-- nova/tests/scheduler/test_host_filter.py | 24 +++++++++++++++++++++++- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/contrib/flavorextraspecs.py b/nova/api/openstack/contrib/flavorextraspecs.py index 7fc8e7954..8518bf2bd 100644 --- a/nova/api/openstack/contrib/flavorextraspecs.py +++ b/nova/api/openstack/contrib/flavorextraspecs.py @@ -122,6 +122,5 @@ class Flavorextraspecs(extensions.ExtensionDescriptor): FlavorExtraSpecsController(), parent=dict(member_name='flavor', collection_name='flavors')) - resources.append(res) return resources diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index bd6b26608..7d4b9ad91 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -93,6 +93,22 @@ class InstanceTypeFilter(HostFilter): """Use instance_type to filter hosts.""" return (self._full_name(), instance_type) + def _satisfies_extra_specs(self, capabilities, instance_type): + """Check that the capabilities provided by the compute service + satisfy the extra specs associated with the instance type""" + + # Note(lorinh): For now, we are just checking exact matching on the + # values. Later on, we want to handle numerical + # values so we can represent things like number of GPU cards + try: + for key, value in instance_type['extra_specs'].iteritems(): + if capabilities[key] != value: + return False + except KeyError: + return False + + return True + def filter_hosts(self, zone_manager, query): """Return a list of hosts that can create instance_type.""" instance_type = query @@ -103,8 +119,12 @@ class InstanceTypeFilter(HostFilter): disk_bytes = capabilities['disk_available'] spec_ram = instance_type['memory_mb'] spec_disk = instance_type['local_gb'] - if host_ram_mb >= spec_ram and disk_bytes >= spec_disk: - selected_hosts.append((host, capabilities)) + extra_specs = instance_type['extra_specs'] + + if host_ram_mb >= spec_ram and \ + disk_bytes >= spec_disk and \ + self._satisfies_extra_specs(capabilities, instance_type): + selected_hosts.append((host, capabilities)) return selected_hosts #host entries (currently) are like: diff --git a/nova/tests/scheduler/test_host_filter.py b/nova/tests/scheduler/test_host_filter.py index 10eafde08..75a2cb21c 100644 --- a/nova/tests/scheduler/test_host_filter.py +++ b/nova/tests/scheduler/test_host_filter.py @@ -67,13 +67,24 @@ class HostFilterTestCase(test.TestCase): flavorid=1, swap=500, rxtx_quota=30000, - rxtx_cap=200) + rxtx_cap=200, + extra_specs={}) + self.gpu_instance_type = dict(name='tiny.gpu', + memory_mb=50, + vcpus=10, + local_gb=500, + flavorid=2, + swap=500, + rxtx_quota=30000, + rxtx_cap=200, + extra_specs={'gpu': 'nvidia'}) self.zone_manager = FakeZoneManager() states = {} for x in xrange(10): states['host%02d' % (x + 1)] = {'compute': self._host_caps(x)} self.zone_manager.service_states = states + self.zone_manager.service_states['host07']['compute']['gpu'] = 'nvidia' def tearDown(self): FLAGS.default_host_filter = self.old_flag @@ -116,6 +127,17 @@ class HostFilterTestCase(test.TestCase): self.assertEquals('host05', just_hosts[0]) self.assertEquals('host10', just_hosts[5]) + def test_instance_type_filter_extra_specs(self): + hf = host_filter.InstanceTypeFilter() + # filter all hosts that can support 50 ram and 500 disk + name, cooked = hf.instance_type_to_filter(self.gpu_instance_type) + self.assertEquals('nova.scheduler.host_filter.InstanceTypeFilter', + name) + hosts = hf.filter_hosts(self.zone_manager, cooked) + self.assertEquals(1, len(hosts)) + just_hosts = [host for host, caps in hosts] + self.assertEquals('host07', just_hosts[0]) + def test_json_filter(self): hf = host_filter.JsonFilter() # filter all hosts that can support 50 ram and 500 disk -- cgit From e6dcd9b4008feb9a053edcd7c6f6020772a03c59 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 24 Jun 2011 01:44:22 -0400 Subject: Dealing with cases where extra_specs wasn't defined --- nova/scheduler/host_filter.py | 4 ++++ nova/tests/test_host_filter.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 7d4b9ad91..9c396cc0c 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -97,9 +97,13 @@ class InstanceTypeFilter(HostFilter): """Check that the capabilities provided by the compute service satisfy the extra specs associated with the instance type""" + if 'extra_specs' not in instance_type: + return True + # Note(lorinh): For now, we are just checking exact matching on the # values. Later on, we want to handle numerical # values so we can represent things like number of GPU cards + try: for key, value in instance_type['extra_specs'].iteritems(): if capabilities[key] != value: diff --git a/nova/tests/test_host_filter.py b/nova/tests/test_host_filter.py index 3361c7b73..438f3e522 100644 --- a/nova/tests/test_host_filter.py +++ b/nova/tests/test_host_filter.py @@ -67,7 +67,8 @@ class HostFilterTestCase(test.TestCase): flavorid=1, swap=500, rxtx_quota=30000, - rxtx_cap=200) + rxtx_cap=200, + extra_specs={}) self.zone_manager = FakeZoneManager() states = {} -- cgit From d4fc1d77a4b7c668453042b83e34da76ee3c3818 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 24 Jun 2011 09:54:38 +0400 Subject: Unwind last commit, force anyjson to use our serialization methods. --- bin/nova-manage | 8 ++++---- nova/compute/api.py | 1 + nova/db/sqlalchemy/api.py | 15 +++------------ nova/utils.py | 9 +++++++++ 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 0147ae21b..e09ea495d 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -874,10 +874,10 @@ class InstanceTypeCommands(object): try: instance_types.create(name, memory, vcpus, local_gb, flavorid, swap, rxtx_quota, rxtx_cap) - except exception.InvalidInputException: - print "Must supply valid parameters to create instance_type" - print e - sys.exit(1) + #except exception.InvalidInputException: + # print "Must supply valid parameters to create instance_type" + # print e + # sys.exit(1) except exception.ApiError, e: print "\n\n" print "\n%s" % e diff --git a/nova/compute/api.py b/nova/compute/api.py index f1c31a092..9a77a2aa2 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -291,6 +291,7 @@ class API(base.Base): 'blob': zone_blob, 'num_instances': num_instances, } + LOG.debug(request_spec) rpc.cast(context, FLAGS.scheduler_topic, diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 5dc2b9e7a..7119f43eb 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2510,10 +2510,7 @@ def instance_type_get_by_id(context, id): if not inst_type: raise exception.InstanceTypeNotFound(instance_type=id) else: - res = dict(inst_type) - for field in ['created_at', 'updated_at', 'deleted_at']: - res.pop(field, None) - return res + return dict(inst_type) @require_context @@ -2526,10 +2523,7 @@ def instance_type_get_by_name(context, name): if not inst_type: raise exception.InstanceTypeNotFoundByName(instance_type_name=name) else: - res = dict(inst_type) - for field in ['created_at', 'updated_at', 'deleted_at']: - res.pop(field, None) - return res + return dict(inst_type) @require_context @@ -2542,10 +2536,7 @@ def instance_type_get_by_flavor_id(context, id): if not inst_type: raise exception.FlavorNotFound(flavor_id=id) else: - res = dict(inst_type) - for field in ['created_at', 'updated_at', 'deleted_at']: - res.pop(field, None) - return res + return dict(inst_type) @require_admin_context diff --git a/nova/utils.py b/nova/utils.py index 691134ada..a77cf7993 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -525,6 +525,15 @@ def loads(s): return json.loads(s) +try: + import anyjson +except ImportError: + pass +else: + anyjson._modules.append(("nova.utils", "dumps", TypeError, "loads", ValueError)) + anyjson.force_implementation("nova.utils") + + _semaphores = {} -- cgit From 178ddd56da98f5baf5e9d232bdab8d5565e7e98b Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 24 Jun 2011 15:20:24 +0400 Subject: Add reconnect on server fail to LDAP driver. --- nova/auth/ldapdriver.py | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index e9532473d..4af91b613 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -101,6 +101,41 @@ def sanitize(fn): return _wrapped +class LDAPWrapper(object): + def __init__(self, ldap, url, user, password): + self.ldap = ldap + self.url = url + self.user = user + self.password = password + self.conn = None + + def __wrap_reconnect(f): + def inner(self, *args, **kwargs): + if self.conn is None: + self.connect() + return f(self.conn)(*args, **kwargs) + else: + try: + return f(self.conn)(*args, **kwargs) + except self.ldap.SERVER_DOWN: + self.connect() + return f(self.conn)(*args, **kwargs) + return inner + + def connect(self): + try: + self.conn = self.ldap.initialize(self.url) + self.conn.bind_s(self.user, self.password) + except self.ldap.SERVER_DOWN: + self.conn = None + raise + + search_s = __wrap_reconnect(lambda conn: conn.search_s) + add_s = __wrap_reconnect(lambda conn: conn.add_s) + delete_s = __wrap_reconnect(lambda conn: conn.delete_s) + modify_s = __wrap_reconnect(lambda conn: conn.modify_s) + + class LdapDriver(object): """Ldap Auth driver @@ -124,8 +159,8 @@ class LdapDriver(object): LdapDriver.project_objectclass = 'novaProject' self.__cache = None if LdapDriver.conn is None: - LdapDriver.conn = self.ldap.initialize(FLAGS.ldap_url) - LdapDriver.conn.simple_bind_s(FLAGS.ldap_user_dn, + LdapDriver.conn = LDAPWrapper(self.ldap, FLAGS.ldap_url, + FLAGS.ldap_user_dn, FLAGS.ldap_password) if LdapDriver.mc is None: LdapDriver.mc = memcache.Client(FLAGS.memcached_servers, debug=0) -- cgit From 60e520bca98d5c4b7ba4f2cc7465982392fc3855 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 24 Jun 2011 15:26:15 +0400 Subject: Use simple_bind_s instead of bind_s --- nova/auth/ldapdriver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 4af91b613..bc37d2d87 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -125,7 +125,7 @@ class LDAPWrapper(object): def connect(self): try: self.conn = self.ldap.initialize(self.url) - self.conn.bind_s(self.user, self.password) + self.conn.simple_bind_s(self.user, self.password) except self.ldap.SERVER_DOWN: self.conn = None raise -- cgit From 58cc475649276a8722113960bf3f4d21d6513ca2 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 24 Jun 2011 15:55:06 +0400 Subject: Add reconnect test. --- nova/auth/fakeldap.py | 24 ++++++++++++++++++++++++ nova/tests/test_auth.py | 9 +++++++++ 2 files changed, 33 insertions(+) diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py index 79afb9109..f1e769278 100644 --- a/nova/auth/fakeldap.py +++ b/nova/auth/fakeldap.py @@ -100,6 +100,11 @@ class OBJECT_CLASS_VIOLATION(Exception): # pylint: disable=C0103 pass +class SERVER_DOWN(Exception): # pylint: disable=C0103 + """Duplicate exception class from real LDAP module.""" + pass + + def initialize(_uri): """Opens a fake connection with an LDAP server.""" return FakeLDAP() @@ -202,25 +207,38 @@ def _to_json(unencoded): return json.dumps(list(unencoded)) +server_fail = False + + class FakeLDAP(object): """Fake LDAP connection.""" def simple_bind_s(self, dn, password): """This method is ignored, but provided for compatibility.""" + if server_fail: + raise SERVER_DOWN pass def unbind_s(self): """This method is ignored, but provided for compatibility.""" + if server_fail: + raise SERVER_DOWN pass def add_s(self, dn, attr): """Add an object with the specified attributes at dn.""" + if server_fail: + raise SERVER_DOWN + key = "%s%s" % (self.__prefix, dn) value_dict = dict([(k, _to_json(v)) for k, v in attr]) Store.instance().hmset(key, value_dict) def delete_s(self, dn): """Remove the ldap object at specified dn.""" + if server_fail: + raise SERVER_DOWN + Store.instance().delete("%s%s" % (self.__prefix, dn)) def modify_s(self, dn, attrs): @@ -232,6 +250,9 @@ class FakeLDAP(object): ([MOD_ADD | MOD_DELETE | MOD_REPACE], attribute, value) """ + if server_fail: + raise SERVER_DOWN + store = Store.instance() key = "%s%s" % (self.__prefix, dn) @@ -255,6 +276,9 @@ class FakeLDAP(object): fields -- fields to return. Returns all fields if not specified """ + if server_fail: + raise SERVER_DOWN + if scope != SCOPE_BASE and scope != SCOPE_SUBTREE: raise NotImplementedError(str(scope)) store = Store.instance() diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index 7d00bddfe..4aebbe940 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -25,6 +25,7 @@ from nova import log as logging from nova import test from nova.auth import manager from nova.api.ec2 import cloud +from nova.auth import fakeldap FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.auth_unittest') @@ -369,6 +370,14 @@ class _AuthManagerBaseTestCase(test.TestCase): class AuthManagerLdapTestCase(_AuthManagerBaseTestCase): auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver' + def test_reconnect_on_server_failure(self): + self.manager.get_users() + fakeldap.server_fail = True + with self.assertRaises(fakeldap.SERVER_DOWN): + self.manager.get_users() + fakeldap.server_fail = False + self.manager.get_users() + class AuthManagerDbTestCase(_AuthManagerBaseTestCase): auth_driver = 'nova.auth.dbdriver.DbDriver' -- cgit From a1c5726a9e0095de88c9d10c09999b2dcdb6211e Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 24 Jun 2011 16:07:33 +0400 Subject: Remove extra debug line. --- nova/compute/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index b570eb986..af18741b6 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -325,7 +325,6 @@ class API(base.Base): 'blob': zone_blob, 'num_instances': num_instances, } - LOG.debug(request_spec) rpc.cast(context, FLAGS.scheduler_topic, -- cgit From 4c46c44d7c1458ccaa3919110de12dfceef406c1 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 24 Jun 2011 10:37:43 -0400 Subject: Removed an import --- nova/api/openstack/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 859cac669..f24017df0 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -32,7 +32,6 @@ from nova.api.openstack import faults from nova.api.openstack import backup_schedules from nova.api.openstack import consoles from nova.api.openstack import flavors -from nova.api.openstack import flavor_extra_specs from nova.api.openstack import images from nova.api.openstack import image_metadata from nova.api.openstack import ips -- cgit From 101fcf7488f4f2b42102da0533c5d97c8f53dd49 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 24 Jun 2011 10:52:59 -0400 Subject: Edited the host filter test case for extra specs --- nova/scheduler/host_filter.py | 4 ++-- nova/tests/scheduler/test_host_filter.py | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 9c396cc0c..5f61f9456 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -103,7 +103,7 @@ class InstanceTypeFilter(HostFilter): # Note(lorinh): For now, we are just checking exact matching on the # values. Later on, we want to handle numerical # values so we can represent things like number of GPU cards - + try: for key, value in instance_type['extra_specs'].iteritems(): if capabilities[key] != value: @@ -128,7 +128,7 @@ class InstanceTypeFilter(HostFilter): if host_ram_mb >= spec_ram and \ disk_bytes >= spec_disk and \ self._satisfies_extra_specs(capabilities, instance_type): - selected_hosts.append((host, capabilities)) + selected_hosts.append((host, capabilities)) return selected_hosts #host entries (currently) are like: diff --git a/nova/tests/scheduler/test_host_filter.py b/nova/tests/scheduler/test_host_filter.py index 75a2cb21c..b1892dab4 100644 --- a/nova/tests/scheduler/test_host_filter.py +++ b/nova/tests/scheduler/test_host_filter.py @@ -77,14 +77,26 @@ class HostFilterTestCase(test.TestCase): swap=500, rxtx_quota=30000, rxtx_cap=200, - extra_specs={'gpu': 'nvidia'}) + extra_specs={'xpu_arch': 'fermi', + 'xpu_info': 'Tesla 2050'}) self.zone_manager = FakeZoneManager() states = {} for x in xrange(10): states['host%02d' % (x + 1)] = {'compute': self._host_caps(x)} self.zone_manager.service_states = states - self.zone_manager.service_states['host07']['compute']['gpu'] = 'nvidia' + + # Add some extra capabilities to some hosts + host07 = self.zone_manager.service_states['host07']['compute'] + host07['xpu_arch'] = 'fermi' + host07['xpu_info'] = 'Tesla 2050' + + host08 = self.zone_manager.service_states['host08']['compute'] + host08['xpu_arch'] = 'radeon' + + host09 = self.zone_manager.service_states['host09']['compute'] + host09['xpu_arch'] = 'fermi' + host09['xpu_info'] = 'Tesla 2150' def tearDown(self): FLAGS.default_host_filter = self.old_flag -- cgit From c5745c0cb61bb6ab375a1e52d5e203a7a0a76366 Mon Sep 17 00:00:00 2001 From: Kirill Shileev Date: Fri, 24 Jun 2011 20:59:32 +0400 Subject: associate diassociate untested, first attept to test --- nova/api/openstack/contrib/floating_ips.py | 27 +++++++++++++---- .../api/openstack/contrib/test_floating_ips.py | 34 +++++++++++++++++----- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index dc048869c..23864c352 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -43,7 +43,7 @@ def _translate_floating_ips_view(floating_ips): class FloatingIPController(object): - """The Volumes API controller for the OpenStack API.""" + """The Floating IPs API controller for the OpenStack API.""" _serialization_metadata = { 'application/xml': { @@ -98,15 +98,32 @@ class FloatingIPController(object): return {'released': ip} - def associate(self, req, id, body): + def associate(self, req, id_ip, body): + """ /floating_ips/ip or id/associate fixed ip in body """ context = req.environ['nova.context'] + floating_ip = self._get_ip_by_id(context, id_ip) - return {'associate': None} + fixed_ip = body['associate_address']['fixed_ip'] - def disassociate(self, req, id, body): + try: + self.network_api.associate_floating_ip(context, floating_ip, fixed_ip) + except rpc.RemoteError: + raise + + return {'associated': [floating_ip, fixed_ip]} + + def disassociate(self, req, id_ip, body): + """ POST /floating_ips/{ip | ip_id}/disassociate """ context = req.environ['nova.context'] - return {'disassociate': None} + floating_ip = self._get_ip_by_id(context, id_ip) + + try: + self.network_api.disassociate_floating_ip(context, floating_ip) + except rpc.RemoteError: + raise + + return {'disassociated': floating_ip} def _get_ip_by_id(self, context, value): """Checks that value is id and then returns its address. diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 9e079c9b0..4ac1cc270 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -15,24 +15,27 @@ from nova import context from nova import db from nova import test +import webob from nova.api.openstack.contrib.floating_ips import FloatingIPController from nova.api.openstack.contrib.floating_ips import \ _translate_floating_ip_view class FloatingIpTest(test.TestCase): - address = "10.10.10.10" + floating_ip_address = "10.10.10.10" + fixed_ip_address = '100.100.100.100' + + def _create_fixed_ip(self): + """Create a fixed ip object. Returns address as string""" + return db.fixed_ip_create(self.context, {'address': self.floating_ip_address}) def _create_floating_ip(self): - """Create a volume object.""" - host = "fake_host" - return db.floating_ip_create(self.context, - {'address': self.address, - 'host': host}) + """Create a floating ip object. Returns address as string""" + return db.floating_ip_create(self.context, {'address': self.floating_ip_address, }) def setUp(self): super(FloatingIpTest, self).setUp() - self.floating_ips = FloatingIPController() + self.controller = FloatingIPController() self.context = context.get_admin_context() def test_translate_floating_ip_view(self): @@ -42,6 +45,21 @@ class FloatingIpTest(test.TestCase): view = _translate_floating_ip_view(floating_ip) self.assertTrue('floating_ip' in view) self.assertTrue(view['floating_ip']['id']) - self.assertEqual(view['floating_ip']['ip'], self.address) + self.assertEqual(view['floating_ip']['ip'], self.floating_ip_address) self.assertEqual(view['floating_ip']['fixed_ip'], None) self.assertEqual(view['floating_ip']['instance_id'], None) + + def test_associate_by_address(self): + fixed_ip_address = self._create_fixed_ip() + floating_ip_address = self._create_floating_ip() + floating_ip = db.floating_ip_get_by_address(self.context, floating_ip_address) + + self.assertEqual(floating_ip['address'], self.floating_ip_address) + self.assertEqual(floating_ip['fixed_ip_id'], None) + body = {'associate_address': {'fixed_ip': self.fixed_ip_address}} + + req = webob.Request.blank('/v1.1/floating_ips//associate') + raise Exception(req.__dict__) + #self.controller.associate(req, self.floating_ip_address, body) + #self.assertEqual(floating_ip['fixed_ip'], self.fixed_ip_address) + -- cgit From 4c3993ebcee2cf1abe24a9065822c88bbcb0df55 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Fri, 24 Jun 2011 23:31:21 +0400 Subject: add stubs for flating api os api testing --- .../api/openstack/contrib/test_floating_ips.py | 43 +++++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 0faeaeb39..21b8fdec3 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -15,14 +15,33 @@ from nova import context from nova import db from nova import test +from nova import network +from nova.tests.api.openstack import fakes import stubout import webob -from nova.api.openstack.contrib.floating_ips import FloatingIPController from nova.api.openstack.contrib.floating_ips import \ _translate_floating_ip_view +def network_api_get(): + pass + +def network_api_list(): + pass + +def network_api_allocate(): + pass + +def network_api_release(): + pass + +def network_api_associate(): + pass + +def network_api_disassociate(): + pass + class FloatingIpTest(test.TestCase): floating_ip_address = "10.10.10.10" @@ -40,10 +59,30 @@ class FloatingIpTest(test.TestCase): def setUp(self): super(FloatingIpTest, self).setUp() - self.controller = FloatingIPController() self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.reset_fake_data() + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_networking(self.stubs) + fakes.stub_out_rate_limiting(self.stubs) + fakes.stub_out_auth(self.stubs) + self.stubs.Set(network.api, "get", + network_api_get) + self.stubs.Set(network.api, "list", + network_api_list) + self.stubs.Set(network.api, "allocate_floating_ip", + network_api_allocate) + self.stubs.Set(network.api, "release_floating_ip", + network_api_release) + self.stubs.Set(network.api, "associate_floating_ip", + network_api_associate) + self.stubs.Set(network.api, "disassociate_floating_ip", + network_api_disassociate) self.context = context.get_admin_context() + def tearDown(self): + self.stubs.UnsetAll() + super(FloatingIpTest, self).tearDown() + def test_translate_floating_ip_view(self): floating_ip_address = self._create_floating_ip() floating_ip = db.floating_ip_get_by_address(self.context, -- cgit From 09d439cd74290a6b2532376afc94d2c8e23cdda6 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Fri, 24 Jun 2011 23:55:18 +0400 Subject: stub tests --- Authors | 2 +- nova/api/openstack/contrib/floating_ips.py | 1 + .../api/openstack/contrib/test_floating_ips.py | 47 ++++++++++++---------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Authors b/Authors index 94fcf7f5b..bf2ba81d9 100644 --- a/Authors +++ b/Authors @@ -29,7 +29,7 @@ Ewan Mellor Gabe Westmaas Hisaharu Ishii Hisaki Ohara -Ilya Alekseyev +Ilya Alekseyev Isaku Yamahata Jason Cannavale Jason Koelker diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 23864c352..924b504a8 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -2,6 +2,7 @@ # Copyright 2011 OpenStack LLC. # Copyright 2011 Grid Dynamics +# Copyright 2011 Eldar Nugaev, Kirill Shileev, Ilya Alekseyev # # 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 diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 21b8fdec3..31b571eb9 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -15,12 +15,16 @@ from nova import context from nova import db from nova import test +<<<<<<< TREE +from nova.api.openstack.contrib.floating_ips import FloatingIPController +======= from nova import network from nova.tests.api.openstack import fakes import stubout import webob +>>>>>>> MERGE-SOURCE from nova.api.openstack.contrib.floating_ips import \ _translate_floating_ip_view @@ -44,18 +48,14 @@ def network_api_disassociate(): class FloatingIpTest(test.TestCase): - floating_ip_address = "10.10.10.10" - fixed_ip_address = '100.100.100.100' - - def _create_fixed_ip(self): - """Create a fixed ip object. Returns address as string""" - return db.fixed_ip_create(self.context, - {'address': self.fixed_ip_address}) + address = "10.10.10.10" def _create_floating_ip(self): - """Create a floating ip object. Returns address as string""" + """Create a volume object.""" + host = "fake_host" return db.floating_ip_create(self.context, - {'address': self.floating_ip_address}) + {'address': self.address, + 'host': host}) def setUp(self): super(FloatingIpTest, self).setUp() @@ -90,22 +90,27 @@ class FloatingIpTest(test.TestCase): view = _translate_floating_ip_view(floating_ip) self.assertTrue('floating_ip' in view) self.assertTrue(view['floating_ip']['id']) - self.assertEqual(view['floating_ip']['ip'], self.floating_ip_address) + self.assertEqual(view['floating_ip']['ip'], self.address) self.assertEqual(view['floating_ip']['fixed_ip'], None) self.assertEqual(view['floating_ip']['instance_id'], None) + def test_translate_floating_ips_view(self): + pass - def test_associate_by_address(self): - fixed_ip_address = self._create_fixed_ip() - floating_ip_address = self._create_floating_ip() - floating_ip = db.floating_ip_get_by_address(self.context, floating_ip_address) + def test_floating_ips_list(self): + pass + + def test_floating_ip_show(self): + pass + + def test_floating_ip_allocate(self): + pass - self.assertEqual(floating_ip['address'], self.floating_ip_address) - self.assertEqual(floating_ip['fixed_ip_id'], None) - body = {'associate_address': {'fixed_ip': self.fixed_ip_address}} + def test_floating_ip_release(self): + pass - req = webob.Request.blank('/v1.1/floating_ips//associate') - raise Exception(req.__dict__) - #self.controller.associate(req, self.floating_ip_address, body) - #self.assertEqual(floating_ip['fixed_ip'], self.fixed_ip_address) + def test_floating_ip_associate(self): + pass + def test_floating_ip_disassociate(self): + pass \ No newline at end of file -- cgit From 153621b9f3a4480b544de5ccd2a96bf4d63adbc9 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Fri, 24 Jun 2011 23:57:10 +0400 Subject: conflict resolved --- nova/tests/api/openstack/contrib/test_floating_ips.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 31b571eb9..5dca0b5ea 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -15,16 +15,12 @@ from nova import context from nova import db from nova import test -<<<<<<< TREE -from nova.api.openstack.contrib.floating_ips import FloatingIPController -======= from nova import network from nova.tests.api.openstack import fakes import stubout import webob ->>>>>>> MERGE-SOURCE from nova.api.openstack.contrib.floating_ips import \ _translate_floating_ip_view -- cgit From ee82eb5916cd87ee984d00a07759d7c7648c6976 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sat, 25 Jun 2011 00:30:22 +0400 Subject: fix tests for extensions --- .../api/openstack/contrib/test_floating_ips.py | 33 ++++++++++++++-------- nova/tests/api/openstack/fakes.py | 10 ++----- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 5dca0b5ea..bc3c20bf2 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -12,20 +12,23 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + +import json +import stubout +import webob + from nova import context from nova import db from nova import test from nova import network from nova.tests.api.openstack import fakes -import stubout -import webob - from nova.api.openstack.contrib.floating_ips import \ _translate_floating_ip_view -def network_api_get(): - pass +def network_api_get(self, context, id): + return {'id': 1, + 'address': '10.10.10.10'} def network_api_list(): pass @@ -61,17 +64,17 @@ class FloatingIpTest(test.TestCase): fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_auth(self.stubs) - self.stubs.Set(network.api, "get", + self.stubs.Set(network.api.API, "get", network_api_get) - self.stubs.Set(network.api, "list", + self.stubs.Set(network.api.API, "list", network_api_list) - self.stubs.Set(network.api, "allocate_floating_ip", + self.stubs.Set(network.api.API, "allocate_floating_ip", network_api_allocate) - self.stubs.Set(network.api, "release_floating_ip", + self.stubs.Set(network.api.API, "release_floating_ip", network_api_release) - self.stubs.Set(network.api, "associate_floating_ip", + self.stubs.Set(network.api.API, "associate_floating_ip", network_api_associate) - self.stubs.Set(network.api, "disassociate_floating_ip", + self.stubs.Set(network.api.API, "disassociate_floating_ip", network_api_disassociate) self.context = context.get_admin_context() @@ -97,7 +100,13 @@ class FloatingIpTest(test.TestCase): pass def test_floating_ip_show(self): - pass + req = webob.Request.blank('/v1.1/floating_ips/1') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['floating_ip']['id'], 1) + self.assertEqual(res_dict['floating_ip']['ip'], '10.10.10.10') + self.assertEqual(res_dict['floating_ip']['fixed_ip'], None) + self.assertEqual(res_dict['floating_ip']['instance_id'], None) def test_floating_ip_allocate(self): pass diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index a10fb7433..a818c1330 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -16,7 +16,6 @@ # under the License. import copy -import json import random import string @@ -29,19 +28,15 @@ from glance.common import exception as glance_exc from nova import context from nova import exception as exc -from nova import flags from nova import utils import nova.api.openstack.auth from nova.api import openstack from nova.api.openstack import auth +from nova.api.openstack import extensions from nova.api.openstack import versions from nova.api.openstack import limits from nova.auth.manager import User, Project import nova.image.fake -from nova.image import glance -from nova.image import local -from nova.image import service -from nova.tests import fake_flags from nova.wsgi import Router @@ -83,7 +78,8 @@ def wsgi_app(inner_app10=None, inner_app11=None): api10 = openstack.FaultWrapper(auth.AuthMiddleware( limits.RateLimitingMiddleware(inner_app10))) api11 = openstack.FaultWrapper(auth.AuthMiddleware( - limits.RateLimitingMiddleware(inner_app11))) + limits.RateLimitingMiddleware( + extensions.ExtensionMiddleware(inner_app11)))) mapper['/v1.0'] = api10 mapper['/v1.1'] = api11 mapper['/'] = openstack.FaultWrapper(versions.Versions()) -- cgit From cbd0622ffbd021d404270be8b35b3e4839dd0ea0 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Sat, 25 Jun 2011 00:33:40 +0400 Subject: some tests --- Authors | 2 +- nova/tests/api/openstack/contrib/test_floating_ips.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Authors b/Authors index bf2ba81d9..09064051d 100644 --- a/Authors +++ b/Authors @@ -29,7 +29,7 @@ Ewan Mellor Gabe Westmaas Hisaharu Ishii Hisaki Ohara -Ilya Alekseyev +Ilya Alekseyev Isaku Yamahata Jason Cannavale Jason Koelker diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 5dca0b5ea..55bd8c1a1 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -20,6 +20,7 @@ from nova.tests.api.openstack import fakes import stubout import webob +import json from nova.api.openstack.contrib.floating_ips import \ _translate_floating_ip_view @@ -30,8 +31,8 @@ def network_api_get(): def network_api_list(): pass -def network_api_allocate(): - pass +def network_api_allocate(context): + return '10.10.10.10' def network_api_release(): pass @@ -47,7 +48,7 @@ class FloatingIpTest(test.TestCase): address = "10.10.10.10" def _create_floating_ip(self): - """Create a volume object.""" + """Create a floating ip object.""" host = "fake_host" return db.floating_ip_create(self.context, {'address': self.address, @@ -100,7 +101,13 @@ class FloatingIpTest(test.TestCase): pass def test_floating_ip_allocate(self): - pass + req = webob.Request.blank('/v1.1/floating_ips') + req.method = 'POST' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + ip = json.loads(res.body)['allocated'] + expected = '10.10.10.10' + self.assertEqual(ip, expected) def test_floating_ip_release(self): pass -- cgit From 91cf150ac2acdc742f56cc03a67dbee833525bce Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sat, 25 Jun 2011 01:47:25 +0400 Subject: implement list test --- .../api/openstack/contrib/test_floating_ips.py | 37 ++++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index bc3c20bf2..494569a97 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -23,25 +23,34 @@ from nova import test from nova import network from nova.tests.api.openstack import fakes -from nova.api.openstack.contrib.floating_ips import \ +from nova.api.openstack.contrib.floating_ips import\ _translate_floating_ip_view def network_api_get(self, context, id): - return {'id': 1, - 'address': '10.10.10.10'} + return {'id': id, 'address': '10.10.10.10'} + + +def network_api_list(self, context): + return [{'id': 1, + 'address': '10.10.10.10', + 'instance': {'id': 11}, + 'fixed_ip': {'address': '10.0.0.1'}}, + {'id': 2, + 'address': '10.10.10.11'}] -def network_api_list(): - pass def network_api_allocate(): pass + def network_api_release(): pass + def network_api_associate(): pass + def network_api_disassociate(): pass @@ -93,12 +102,20 @@ class FloatingIpTest(test.TestCase): self.assertEqual(view['floating_ip']['fixed_ip'], None) self.assertEqual(view['floating_ip']['instance_id'], None) - def test_translate_floating_ips_view(self): - pass - def test_floating_ips_list(self): - pass - + req = webob.Request.blank('/v1.1/floating_ips') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + response = {'floating_ips': [{'floating_ip': {'instance_id': 11, + 'ip': '10.10.10.10', + 'fixed_ip': '10.0.0.1', + 'id': 1}}, + {'floating_ip': {'instance_id': None, + 'ip': '10.10.10.11', + 'fixed_ip': None, + 'id': 2}}]} + self.assertEqual(res_dict, response) + def test_floating_ip_show(self): req = webob.Request.blank('/v1.1/floating_ips/1') res = req.get_response(fakes.wsgi_app()) -- cgit From a770864d308242bfcfa8dadb210595785d8fa71f Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Sat, 25 Jun 2011 02:42:27 +0400 Subject: tests --- nova/api/openstack/contrib/floating_ips.py | 14 +++++++---- .../api/openstack/contrib/test_floating_ips.py | 29 ++++++++++++++-------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index c6bc85c61..95502a5c5 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -104,10 +104,10 @@ class FloatingIPController(object): "id": ip['id'], "floating_ip": ip['address']}} - def associate(self, req, id_ip, body): + def associate(self, req, id, body): """ /floating_ips/{id}/associate fixed ip in body """ context = req.environ['nova.context'] - floating_ip = self._get_ip_by_id(context, id_ip) + floating_ip = self._get_ip_by_id(context, id) fixed_ip = body['associate_address']['fixed_ip'] @@ -117,13 +117,17 @@ class FloatingIPController(object): except rpc.RemoteError: raise - return {'associated': [floating_ip, fixed_ip]} + return {'associated': + { + "floating_ip_id": id, + "floating_ip": floating_ip, + "fixed_ip": fixed_ip}} - def disassociate(self, req, id_ip, body): + def disassociate(self, req, id, body): """ POST /floating_ips/{id}/disassociate """ context = req.environ['nova.context'] - floating_ip = self._get_ip_by_id(context, id_ip) + floating_ip = self._get_ip_by_id(context, id) try: self.network_api.disassociate_floating_ip(context, floating_ip) diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index efdfdcf74..1f2012ec7 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -28,7 +28,7 @@ from nova.api.openstack.contrib.floating_ips import FloatingIPController from nova.api.openstack.contrib.floating_ips import _translate_floating_ip_view def network_api_get(self, context, id): - return {'id': id, 'address': '10.10.10.10'} + return {'id': 1, 'address': '10.10.10.10'} def network_api_list(self, context): @@ -47,7 +47,7 @@ def network_api_allocate(self, context): def network_api_release(self, context, address): pass -def network_api_associate(): +def network_api_associate(self, context,floating_ip, fixed_ip): pass @@ -97,12 +97,6 @@ class FloatingIpTest(test.TestCase): self._delete_floating_ip() super(FloatingIpTest, self).tearDown() - def test_get_ip_by_id(self): - ip = self.controller._get_ip_by_id(self.context, '10.10.10.10') - self.assertEqual(ip, '10.10.10.10') - ip = self.controller._get_ip_by_id(self.context, '1') - self.assertEqual(ip, '10.10.10.10') - def test_translate_floating_ip_view(self): floating_ip_address = self._create_floating_ip() floating_ip = db.floating_ip_get_by_address(self.context, @@ -154,15 +148,28 @@ class FloatingIpTest(test.TestCase): req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) - ip = json.loads(res.body)['released'] + actual = json.loads(res.body)['released'] expected = { "id": 1, "floating_ip": '10.10.10.10' } - self.assertEqual(ip, expected) + self.assertEqual(actual, expected) def test_floating_ip_associate(self): - pass + body = dict(associate_address=dict(fixed_ip='1.2.3.4')) + req = webob.Request.blank('/v1.1/floating_ips/1/associate') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + actual = json.loads(res.body)['associated'] + expected = { + "floating_ip_id": '1', + "floating_ip": "10.10.10.10", + "fixed_ip": "1.2.3.4"} + self.assertEqual(actual, expected) def test_floating_ip_disassociate(self): pass \ No newline at end of file -- cgit From 62018b1abaa007f8f530ba08d74413c59e2814cb Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Sat, 25 Jun 2011 03:03:17 +0400 Subject: fixes --- Authors | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Authors b/Authors index 09064051d..c4281ca16 100644 --- a/Authors +++ b/Authors @@ -22,14 +22,14 @@ David Pravec Dean Troyer Devin Carlen Ed Leafe -Eldar Nugaev +Eldar Nugaev Eric Day Eric Windisch Ewan Mellor Gabe Westmaas Hisaharu Ishii Hisaki Ohara -Ilya Alekseyev +Ilya Alekseyev Isaku Yamahata Jason Cannavale Jason Koelker @@ -53,6 +53,7 @@ Kei Masumoto Ken Pepple Kevin Bringard Kevin L. Mitchell +Kirill Shileev Koji Iida Lorin Hochstein Lvov Maxim -- cgit From 5e4d90b33ddb993294232eea168a768486ba0bf4 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Sat, 25 Jun 2011 03:05:09 +0400 Subject: added disassociate method to tests --- nova/api/openstack/contrib/floating_ips.py | 12 ++++++---- nova/db/api.py | 2 ++ nova/db/sqlalchemy/api.py | 2 ++ nova/exception.py | 2 ++ .../api/openstack/contrib/test_floating_ips.py | 28 ++++++++++++++++------ 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 95502a5c5..467b46712 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -91,7 +91,7 @@ class FloatingIPController(object): raise return {'allocated': { - "id" : ip['id'], + "id": ip['id'], "floating_ip": ip['address']}} def delete(self, req, id): @@ -126,15 +126,17 @@ class FloatingIPController(object): def disassociate(self, req, id, body): """ POST /floating_ips/{id}/disassociate """ context = req.environ['nova.context'] - - floating_ip = self._get_ip_by_id(context, id) + floating_ip = self.network_api.get(context, id) + address = floating_ip['address'] + fixed_ip = floating_ip['fixed_ip']['address'] try: - self.network_api.disassociate_floating_ip(context, floating_ip) + self.network_api.disassociate_floating_ip(context, address) except rpc.RemoteError: raise - return {'disassociated': floating_ip} + return {'disassociated': {'floating_ip': address, + 'fixed_ip': fixed_ip}} def _get_ip_by_id(self, context, value): """Checks that value is id and then returns its address.""" diff --git a/nova/db/api.py b/nova/db/api.py index 8d4b7c8b7..c6ba24478 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -291,10 +291,12 @@ def floating_ip_get_by_address(context, address): """Get a floating ip by address or raise if it doesn't exist.""" return IMPL.floating_ip_get_by_address(context, address) + def floating_ip_get_by_ip(context, ip): """Get a floating ip by floating address.""" return IMPL.floating_ip_get_by_ip(context, ip) + def floating_ip_update(context, address, values): """Update a floating ip by address or raise if it doesn't exist.""" return IMPL.floating_ip_update(context, address, values) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7735ec8c2..87f112588 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -609,6 +609,7 @@ def floating_ip_get_by_address(context, address, session=None): return result + @require_context def floating_ip_get_by_ip(context, ip, session=None): if not session: @@ -624,6 +625,7 @@ def floating_ip_get_by_ip(context, ip, session=None): return result + @require_context def floating_ip_update(context, address, values): session = get_session() diff --git a/nova/exception.py b/nova/exception.py index 64abff9af..8230c2b26 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -359,9 +359,11 @@ class DatastoreNotFound(NotFound): class NoFixedIpsFoundForInstance(NotFound): message = _("Instance %(instance_id)s has zero fixed ips.") + class FloatingIpNotDefined(NotFound): message = _("Floating ip %(floating_ip)s not found") + class FloatingIpNotFound(NotFound): message = _("Floating ip not found for fixed address %(fixed_ip)s.") diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 1f2012ec7..4e26994dd 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -27,8 +27,10 @@ from nova.tests.api.openstack import fakes from nova.api.openstack.contrib.floating_ips import FloatingIPController from nova.api.openstack.contrib.floating_ips import _translate_floating_ip_view + def network_api_get(self, context, id): - return {'id': 1, 'address': '10.10.10.10'} + return {'id': 1, 'address': '10.10.10.10', + 'fixed_ip': {'address': '11.0.0.1'}} def network_api_list(self, context): @@ -47,11 +49,12 @@ def network_api_allocate(self, context): def network_api_release(self, context, address): pass -def network_api_associate(self, context,floating_ip, fixed_ip): + +def network_api_associate(self, context, floating_ip, fixed_ip): pass -def network_api_disassociate(): +def network_api_disassociate(self, context, floating_address): pass @@ -111,6 +114,7 @@ class FloatingIpTest(test.TestCase): def test_floating_ips_list(self): req = webob.Request.blank('/v1.1/floating_ips') res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) response = {'floating_ips': [{'floating_ip': {'instance_id': 11, 'ip': '10.10.10.10', @@ -121,14 +125,15 @@ class FloatingIpTest(test.TestCase): 'fixed_ip': None, 'id': 2}}]} self.assertEqual(res_dict, response) - + def test_floating_ip_show(self): req = webob.Request.blank('/v1.1/floating_ips/1') res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) self.assertEqual(res_dict['floating_ip']['id'], 1) self.assertEqual(res_dict['floating_ip']['ip'], '10.10.10.10') - self.assertEqual(res_dict['floating_ip']['fixed_ip'], None) + self.assertEqual(res_dict['floating_ip']['fixed_ip'], '11.0.0.1') self.assertEqual(res_dict['floating_ip']['instance_id'], None) def test_floating_ip_allocate(self): @@ -161,7 +166,7 @@ class FloatingIpTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" - + res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) actual = json.loads(res.body)['associated'] @@ -172,4 +177,13 @@ class FloatingIpTest(test.TestCase): self.assertEqual(actual, expected) def test_floating_ip_disassociate(self): - pass \ No newline at end of file + req = webob.Request.blank('/v1.1/floating_ips/1/disassociate') + req.method = 'POST' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + ip = json.loads(res.body)['disassociated'] + expected = { + "floating_ip": '10.10.10.10', + "fixed_ip": '11.0.0.1' + } + self.assertEqual(ip, expected) -- cgit From 584615837f949b4d9d2a99880d03539789467f2c Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Sat, 25 Jun 2011 04:45:15 +0400 Subject: mailmap --- .mailmap | 2 ++ Authors | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 3f0238ee9..89094fbbb 100644 --- a/.mailmap +++ b/.mailmap @@ -47,3 +47,5 @@ + + diff --git a/Authors b/Authors index c4281ca16..21a9bf94b 100644 --- a/Authors +++ b/Authors @@ -29,7 +29,7 @@ Ewan Mellor Gabe Westmaas Hisaharu Ishii Hisaki Ohara -Ilya Alekseyev +Ilya Alekseyev Isaku Yamahata Jason Cannavale Jason Koelker -- cgit From fd3da28b567a66a31f2833a3c00d0b6ccf55eed8 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Sat, 25 Jun 2011 04:47:06 +0400 Subject: mailmap --- .mailmap | 2 ++ Authors | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index 89094fbbb..6673d0a26 100644 --- a/.mailmap +++ b/.mailmap @@ -49,3 +49,5 @@ + + \ No newline at end of file diff --git a/Authors b/Authors index 21a9bf94b..c3a65f1b4 100644 --- a/Authors +++ b/Authors @@ -22,7 +22,7 @@ David Pravec Dean Troyer Devin Carlen Ed Leafe -Eldar Nugaev +Eldar Nugaev Eric Day Eric Windisch Ewan Mellor @@ -53,7 +53,7 @@ Kei Masumoto Ken Pepple Kevin Bringard Kevin L. Mitchell -Kirill Shileev +Kirill Shileev Koji Iida Lorin Hochstein Lvov Maxim -- cgit From 706bfc7e3f1cb1d5c56e988abf264c71c54ac0ce Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Fri, 24 Jun 2011 23:50:12 -0400 Subject: Renamed _inst_type_query_to_dict -> _dict_with_extra_specs. pep8 version is no longer explicitly specified in pip-requires --- nova/db/sqlalchemy/api.py | 16 ++++++++-------- tools/pip-requires | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 116c534f1..38fe2e2c6 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2621,14 +2621,14 @@ def instance_type_create(_context, values): return instance_type_ref -def _inst_type_query_to_dict(inst_type_query): +def _dict_with_extra_specs(inst_type_query): """Takes an instance type query returned by sqlalchemy - and returns it as a dictionary. + and returns it as a dictionary, converting the extra_specs + from a list of key/value pairs to a dictionary """ - extra_specs_objs = inst_type_query['extra_specs'] + inst_type_dict = dict(inst_type_query) extra_specs = dict([(x['key'], x['value']) for x in \ inst_type_query['extra_specs']]) - inst_type_dict = dict(inst_type_query) inst_type_dict['extra_specs'] = extra_specs return inst_type_dict @@ -2653,7 +2653,7 @@ def instance_type_get_all(context, inactive=False): if inst_types: inst_dict = {} for i in inst_types: - inst_dict[i['name']] = _inst_type_query_to_dict(i) + inst_dict[i['name']] = _dict_with_extra_specs(i) return inst_dict else: raise exception.NoInstanceTypesFound() @@ -2671,7 +2671,7 @@ def instance_type_get_by_id(context, id): if not inst_type: raise exception.InstanceTypeNotFound(instance_type=id) else: - return _inst_type_query_to_dict(inst_type) + return _dict_with_extra_specs(inst_type) @require_context @@ -2685,7 +2685,7 @@ def instance_type_get_by_name(context, name): if not inst_type: raise exception.InstanceTypeNotFoundByName(instance_type_name=name) else: - return _inst_type_query_to_dict(inst_type) + return _dict_with_extra_specs(inst_type) @require_context @@ -2699,7 +2699,7 @@ def instance_type_get_by_flavor_id(context, id): if not inst_type: raise exception.FlavorNotFound(flavor_id=id) else: - return _inst_type_query_to_dict(inst_type) + return _dict_with_extra_specs(inst_type) @require_admin_context diff --git a/tools/pip-requires b/tools/pip-requires index 5d31a814d..2aaffd9f3 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,5 +1,5 @@ SQLAlchemy==0.6.3 -pep8==0.5.0 +pep8 pylint==0.19 Cheetah==2.4.4 M2Crypto==0.20.2 -- cgit From af4e663dee05c907d7ccddc3bb929ff114e876cc Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Sat, 25 Jun 2011 00:05:38 -0400 Subject: Updated _dict_with_extra_specs docstring --- nova/db/sqlalchemy/api.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 38fe2e2c6..55f4bf072 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2624,7 +2624,14 @@ def instance_type_create(_context, values): def _dict_with_extra_specs(inst_type_query): """Takes an instance type query returned by sqlalchemy and returns it as a dictionary, converting the extra_specs - from a list of key/value pairs to a dictionary + entry from a list of dicts: + + 'extra_specs' : [{'key': 'k1', 'value': 'v1', ...}, ...] + + to a single dict: + + 'extra_specs' : {'k1': 'v1'} + """ inst_type_dict = dict(inst_type_query) extra_specs = dict([(x['key'], x['value']) for x in \ -- cgit From 0f5e7930b48ddd48a803ff5afd25f980df2e31b6 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Sat, 25 Jun 2011 00:22:59 -0400 Subject: pep8 --- nova/db/sqlalchemy/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 55f4bf072..bf5cd5e23 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2626,12 +2626,12 @@ def _dict_with_extra_specs(inst_type_query): and returns it as a dictionary, converting the extra_specs entry from a list of dicts: - 'extra_specs' : [{'key': 'k1', 'value': 'v1', ...}, ...] - + 'extra_specs' : [{'key': 'k1', 'value': 'v1', ...}, ...] + to a single dict: - + 'extra_specs' : {'k1': 'v1'} - + """ inst_type_dict = dict(inst_type_query) extra_specs = dict([(x['key'], x['value']) for x in \ -- cgit From e253cd3cf01d29106daff1592a7c629307b449ff Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Sat, 25 Jun 2011 14:04:40 +0400 Subject: PEP8 fix --- nova/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/utils.py b/nova/utils.py index 8f9ca42c1..6d8324e5b 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -531,7 +531,8 @@ try: except ImportError: pass else: - anyjson._modules.append(("nova.utils", "dumps", TypeError, "loads", ValueError)) + anyjson._modules.append(("nova.utils", "dumps", TypeError, + "loads", ValueError)) anyjson.force_implementation("nova.utils") -- cgit From 9978d656d262a95e17a60a2c137664b315f8191a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 25 Jun 2011 17:26:38 -0700 Subject: only create the db if it doesn't exist, add an option -r to run_tests.py to delete it --- nova/tests/__init__.py | 2 +- run_tests.py | 7 +++---- run_tests.sh | 7 +++++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py index 7fba02a93..5e0cb718e 100644 --- a/nova/tests/__init__.py +++ b/nova/tests/__init__.py @@ -50,7 +50,7 @@ def setup(): testdb = os.path.join(FLAGS.state_path, FLAGS.sqlite_db) if os.path.exists(testdb): - os.unlink(testdb) + return migration.db_sync() ctxt = context.get_admin_context() network_manager.VlanManager().create_networks(ctxt, diff --git a/run_tests.py b/run_tests.py index bb33f9139..fd836967e 100644 --- a/run_tests.py +++ b/run_tests.py @@ -69,7 +69,6 @@ from nose import core from nose import result from nova import log as logging -from nova.tests import fake_flags class _AnsiColorizer(object): @@ -211,11 +210,11 @@ class NovaTestResult(result.TextTestResult): break sys.stdout = stdout - # NOTE(lorinh): Initialize start_time in case a sqlalchemy-migrate - # error results in it failing to be initialized later. Otherwise, + # NOTE(lorinh): Initialize start_time in case a sqlalchemy-migrate + # error results in it failing to be initialized later. Otherwise, # _handleElapsedTime will fail, causing the wrong error message to # be outputted. - self.start_time = time.time() + self.start_time = time.time() def getDescription(self, test): return str(test) diff --git a/run_tests.sh b/run_tests.sh index c3f06f837..2ea221ae3 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -6,6 +6,7 @@ function usage { echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" + echo " -r, --recreate-db Recreate the test database." echo " -x, --stop Stop running tests after the first error or failure." echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -p, --pep8 Just run pep8" @@ -23,6 +24,7 @@ function process_option { -h|--help) usage;; -V|--virtual-env) let always_venv=1; let never_venv=0;; -N|--no-virtual-env) let always_venv=0; let never_venv=1;; + -r|--recreate-db) let recreate_db=1;; -f|--force) let force=1;; -p|--pep8) let just_pep8=1;; -*) noseopts="$noseopts $1";; @@ -39,6 +41,7 @@ noseargs= noseopts= wrapper="" just_pep8=0 +recreate_db=0 for arg in "$@"; do process_option $arg @@ -108,6 +111,10 @@ if [ $just_pep8 -eq 1 ]; then exit fi +if [ $recreate_db -eq 1 ]; then + rm tests.sqlite +fi + run_tests || exit # NOTE(sirp): we only want to run pep8 when we're running the full-test suite, -- cgit From 21cbac8334a37f15595088bc0c99f8f04451f1a7 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Mon, 27 Jun 2011 00:20:53 -0400 Subject: Adding files for building an rpm for xenserver xenapi plugins. --- plugins/xenserver/xenapi/contrib/build.sh | 20 ++++++++++++ .../rpmbuild/SPECS/nova-xenapi-plugins.spec | 36 ++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100755 plugins/xenserver/xenapi/contrib/build.sh create mode 100644 plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/nova-xenapi-plugins.spec diff --git a/plugins/xenserver/xenapi/contrib/build.sh b/plugins/xenserver/xenapi/contrib/build.sh new file mode 100755 index 000000000..b194fface --- /dev/null +++ b/plugins/xenserver/xenapi/contrib/build.sh @@ -0,0 +1,20 @@ +#!/bin/bash +PACKAGE=nova-xenapi-plugins +RPMBUILD_DIR=$PWD/rpmbuild +if [ ! -d $RPMBUILD_DIR ]; then + echo $RPMBUILD_DIR is missing + exit 1 +fi + +for dir in BUILD BUILDROOT SRPMS RPMS SOURCES; do + rm -rf $RPMBUILD_DIR/$dir + mkdir -p $RPMBUILD_DIR/$dir +done + +rm -rf /tmp/$PACKAGE +mkdir /tmp/$PACKAGE +cp -r ../etc/xapi.d /tmp/$PACKAGE +tar czf $RPMBUILD_DIR/SOURCES/$PACKAGE.tar.gz -C /tmp $PACKAGE + +rpmbuild -ba --nodeps --define "_topdir $RPMBUILD_DIR" \ + $RPMBUILD_DIR/SPECS/$PACKAGE.spec diff --git a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/nova-xenapi-plugins.spec b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/nova-xenapi-plugins.spec new file mode 100644 index 000000000..1a61dbbad --- /dev/null +++ b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/nova-xenapi-plugins.spec @@ -0,0 +1,36 @@ +Name: nova-xenapi-plugins +Version: 1.0 +Release: 1 +Summary: Files for XenAPI support. +License: Apache +Group: Applications/Utilities +Source0: nova-xenapi-plugins.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%define debug_package %{nil} + +%description +This package contains files that are required for XenAPI support for OpenStack. + +%prep +%setup -q -n nova-xenapi-plugins + +%install +rm -rf $RPM_BUILD_ROOT +mkdir -p $RPM_BUILD_ROOT/etc +cp -r xapi.d $RPM_BUILD_ROOT/etc +chmod u+x $RPM_BUILD_ROOT/etc/xapi.d/plugins/objectstore +#%{_fixperms} $RPM_BUILD_ROOT/* + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +/etc/xapi.d/plugins/agent +/etc/xapi.d/plugins/glance +/etc/xapi.d/plugins/migration +/etc/xapi.d/plugins/objectstore +/etc/xapi.d/plugins/pluginlib_nova.py +/etc/xapi.d/plugins/xenhost +/etc/xapi.d/plugins/xenstore.py -- cgit From ef1f4d33fa5763ea602c2fc1098a4b230b86e82b Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 27 Jun 2011 16:33:01 +0400 Subject: review issues fixed --- nova/api/openstack/contrib/floating_ips.py | 20 ++++++++++---------- nova/db/sqlalchemy/api.py | 8 ++++---- nova/exception.py | 4 ++-- nova/network/api.py | 4 ++-- .../tests/api/openstack/contrib/test_floating_ips.py | 12 ++++++------ 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 467b46712..ca192f501 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -65,7 +65,7 @@ class FloatingIPController(object): context = req.environ['nova.context'] try: - floating_ip = self.network_api.get(context, id) + floating_ip = self.network_api.get_floating_ip(context, id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -74,7 +74,7 @@ class FloatingIPController(object): def index(self, req): context = req.environ['nova.context'] - floating_ips = self.network_api.list(context) + floating_ips = self.network_api.list_floating_ips(context) return _translate_floating_ips_view(floating_ips) @@ -97,7 +97,7 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] - ip = self.network_api.get(context, id) + ip = self.network_api.get_floating_ip(context, id) self.network_api.release_floating_ip(context, address=ip) return {'released': { @@ -126,7 +126,7 @@ class FloatingIPController(object): def disassociate(self, req, id, body): """ POST /floating_ips/{id}/disassociate """ context = req.environ['nova.context'] - floating_ip = self.network_api.get(context, id) + floating_ip = self.network_api.get_floating_ip(context, id) address = floating_ip['address'] fixed_ip = floating_ip['fixed_ip']['address'] @@ -140,7 +140,7 @@ class FloatingIPController(object): def _get_ip_by_id(self, context, value): """Checks that value is id and then returns its address.""" - return self.network_api.get(context, value)['address'] + return self.network_api.get_floating_ip(context, value)['address'] class Floating_ips(extensions.ExtensionDescriptor): @@ -148,7 +148,7 @@ class Floating_ips(extensions.ExtensionDescriptor): return "Floating_ips" def get_alias(self): - return "FLOATING_IPS" + return "os-floating-ips" def get_description(self): return "Floating IPs support" @@ -163,10 +163,10 @@ class Floating_ips(extensions.ExtensionDescriptor): resources = [] res = extensions.ResourceExtension('floating_ips', - FloatingIPController(), - member_actions={ - 'associate': 'POST', - 'disassociate': 'POST'}) + FloatingIPController(), + member_actions={ + 'associate': 'POST', + 'disassociate': 'POST'}) resources.append(res) return resources diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 426ea5127..55badb9e0 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -448,7 +448,7 @@ def floating_ip_get(context, ip_id): filter_by(deleted=False).\ first() if not result: - raise exception.FloatingIpNotFound() + raise exception.FloatingIpNotFoundForFixedAddress() return result @@ -605,7 +605,7 @@ def floating_ip_get_by_address(context, address, session=None): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.FloatingIpNotFound(fixed_ip=address) + raise exception.FloatingIpNotFoundForFixedAddress(fixed_ip=address) return result @@ -621,7 +621,7 @@ def floating_ip_get_by_ip(context, ip, session=None): first() if not result: - raise exception.FloatingIpNotDefined(floating_ip=ip) + raise exception.FloatingIpNotFound(floating_ip=ip) return result @@ -761,7 +761,7 @@ def fixed_ip_get_by_address(context, address, session=None): options(joinedload('instance')).\ first() if not result: - raise exception.FloatingIpNotFound(fixed_ip=address) + raise exception.FloatingIpNotFoundForFixedAddress(fixed_ip=address) if is_user_context(context): authorize_project_context(context, result.instance.project_id) diff --git a/nova/exception.py b/nova/exception.py index 8230c2b26..8a63a79f2 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -360,11 +360,11 @@ class NoFixedIpsFoundForInstance(NotFound): message = _("Instance %(instance_id)s has zero fixed ips.") -class FloatingIpNotDefined(NotFound): +class FloatingIpNotFound(NotFound): message = _("Floating ip %(floating_ip)s not found") -class FloatingIpNotFound(NotFound): +class FloatingIpNotFoundForFixedAddress(NotFound): message = _("Floating ip not found for fixed address %(fixed_ip)s.") diff --git a/nova/network/api.py b/nova/network/api.py index f1c2dd1f6..dd3ed1709 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -34,7 +34,7 @@ LOG = logging.getLogger('nova.network') class API(base.Base): """API for interacting with the network manager.""" - def get(self, context, id): + def get_floating_ip(self, context, id): rv = self.db.floating_ip_get(context, id) return dict(rv.iteritems()) @@ -42,7 +42,7 @@ class API(base.Base): res = self.db.floating_ip_get_by_ip(context, address) return dict(res.iteritems()) - def list(self, context): + def list_floating_ips(self, context): ips = self.db.floating_ip_get_all_by_project(context, context.project_id) return ips diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 4e26994dd..4a74861bd 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -28,12 +28,12 @@ from nova.api.openstack.contrib.floating_ips import FloatingIPController from nova.api.openstack.contrib.floating_ips import _translate_floating_ip_view -def network_api_get(self, context, id): +def network_api_get_floating_ip(self, context, id): return {'id': 1, 'address': '10.10.10.10', 'fixed_ip': {'address': '11.0.0.1'}} -def network_api_list(self, context): +def network_api_list_floating_ips(self, context): return [{'id': 1, 'address': '10.10.10.10', 'instance': {'id': 11}, @@ -80,10 +80,10 @@ class FloatingIpTest(test.TestCase): fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_auth(self.stubs) - self.stubs.Set(network.api.API, "get", - network_api_get) - self.stubs.Set(network.api.API, "list", - network_api_list) + self.stubs.Set(network.api.API, "get_floating_ip", + network_api_get_floating_ip) + self.stubs.Set(network.api.API, "list_floating_ips", + network_api_list_floating_ips) self.stubs.Set(network.api.API, "allocate_floating_ip", network_api_allocate) self.stubs.Set(network.api.API, "release_floating_ip", -- cgit From 75dd73b1246904fd11bf9b566bf2319d3e6bada5 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 27 Jun 2011 17:05:35 +0400 Subject: fixed pep style --- nova/db/sqlalchemy/api.py | 6 +++--- nova/tests/api/openstack/contrib/test_floating_ips.py | 9 +++------ nova/tests/api/openstack/fakes.py | 2 -- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 55badb9e0..d20e27617 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -429,14 +429,14 @@ def certificate_update(context, certificate_id, values): ################### @require_context -def floating_ip_get(context, ip_id): +def floating_ip_get(context, id): session = get_session() result = None if is_admin_context(context): result = session.query(models.FloatingIp).\ options(joinedload('fixed_ip')).\ options(joinedload_all('fixed_ip.instance')).\ - filter_by(id=ip_id).\ + filter_by(id=id).\ filter_by(deleted=can_read_deleted(context)).\ first() elif is_user_context(context): @@ -444,7 +444,7 @@ def floating_ip_get(context, ip_id): options(joinedload('fixed_ip')).\ options(joinedload_all('fixed_ip.instance')).\ filter_by(project_id=context.project_id).\ - filter_by(id=ip_id).\ + filter_by(id=id).\ filter_by(deleted=False).\ first() if not result: diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 4a74861bd..bf62925aa 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -144,8 +144,7 @@ class FloatingIpTest(test.TestCase): ip = json.loads(res.body)['allocated'] expected = { "id": 1, - "floating_ip": '10.10.10.10' - } + "floating_ip": '10.10.10.10'} self.assertEqual(ip, expected) def test_floating_ip_release(self): @@ -156,8 +155,7 @@ class FloatingIpTest(test.TestCase): actual = json.loads(res.body)['released'] expected = { "id": 1, - "floating_ip": '10.10.10.10' - } + "floating_ip": '10.10.10.10'} self.assertEqual(actual, expected) def test_floating_ip_associate(self): @@ -184,6 +182,5 @@ class FloatingIpTest(test.TestCase): ip = json.loads(res.body)['disassociated'] expected = { "floating_ip": '10.10.10.10', - "fixed_ip": '11.0.0.1' - } + "fixed_ip": '11.0.0.1'} self.assertEqual(ip, expected) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 2ecdd111f..eeb96cb12 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -37,11 +37,9 @@ from nova.api.openstack import versions from nova.api.openstack import limits from nova.auth.manager import User, Project import nova.image.fake - from nova.image import glance from nova.image import service from nova.tests import fake_flags - from nova.wsgi import Router -- cgit From c16f97249e4f0626f8b8d4a7af070201641770b8 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Mon, 27 Jun 2011 15:54:43 +0000 Subject: Clarify help verbiage --- nova/virt/xenapi/vmops.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index ad096f7eb..1e908f513 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -48,7 +48,8 @@ LOG = logging.getLogger("nova.virt.xenapi.vmops") FLAGS = flags.FLAGS flags.DEFINE_integer('windows_version_timeout', 300, - 'time to wait for windows agent to be fully operational') + 'number of seconds to wait for windows agent to be ' + 'fully operational') def cmp_version(a, b): -- cgit From 998b5ba2c709054c535583195ba489454e384f41 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Mon, 27 Jun 2011 12:04:44 -0400 Subject: Changed package name to openstack-xen-plugins per dprince's suggestion. All the files in /etc/xapi.d/plugins must be executable. Added dependency on parted. Renamed build.sh to build-rpm.sh. --- plugins/xenserver/xenapi/contrib/build-rpm.sh | 20 ++++++++++++ plugins/xenserver/xenapi/contrib/build.sh | 20 ------------ .../rpmbuild/SPECS/nova-xenapi-plugins.spec | 36 ---------------------- .../rpmbuild/SPECS/openstack-xen-plugins.spec | 36 ++++++++++++++++++++++ 4 files changed, 56 insertions(+), 56 deletions(-) create mode 100755 plugins/xenserver/xenapi/contrib/build-rpm.sh delete mode 100755 plugins/xenserver/xenapi/contrib/build.sh delete mode 100644 plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/nova-xenapi-plugins.spec create mode 100644 plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec diff --git a/plugins/xenserver/xenapi/contrib/build-rpm.sh b/plugins/xenserver/xenapi/contrib/build-rpm.sh new file mode 100755 index 000000000..f7bed4d84 --- /dev/null +++ b/plugins/xenserver/xenapi/contrib/build-rpm.sh @@ -0,0 +1,20 @@ +#!/bin/bash +PACKAGE=openstack-xen-plugins +RPMBUILD_DIR=$PWD/rpmbuild +if [ ! -d $RPMBUILD_DIR ]; then + echo $RPMBUILD_DIR is missing + exit 1 +fi + +for dir in BUILD BUILDROOT SRPMS RPMS SOURCES; do + rm -rf $RPMBUILD_DIR/$dir + mkdir -p $RPMBUILD_DIR/$dir +done + +rm -rf /tmp/$PACKAGE +mkdir /tmp/$PACKAGE +cp -r ../etc/xapi.d /tmp/$PACKAGE +tar czf $RPMBUILD_DIR/SOURCES/$PACKAGE.tar.gz -C /tmp $PACKAGE + +rpmbuild -ba --nodeps --define "_topdir $RPMBUILD_DIR" \ + $RPMBUILD_DIR/SPECS/$PACKAGE.spec diff --git a/plugins/xenserver/xenapi/contrib/build.sh b/plugins/xenserver/xenapi/contrib/build.sh deleted file mode 100755 index b194fface..000000000 --- a/plugins/xenserver/xenapi/contrib/build.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -PACKAGE=nova-xenapi-plugins -RPMBUILD_DIR=$PWD/rpmbuild -if [ ! -d $RPMBUILD_DIR ]; then - echo $RPMBUILD_DIR is missing - exit 1 -fi - -for dir in BUILD BUILDROOT SRPMS RPMS SOURCES; do - rm -rf $RPMBUILD_DIR/$dir - mkdir -p $RPMBUILD_DIR/$dir -done - -rm -rf /tmp/$PACKAGE -mkdir /tmp/$PACKAGE -cp -r ../etc/xapi.d /tmp/$PACKAGE -tar czf $RPMBUILD_DIR/SOURCES/$PACKAGE.tar.gz -C /tmp $PACKAGE - -rpmbuild -ba --nodeps --define "_topdir $RPMBUILD_DIR" \ - $RPMBUILD_DIR/SPECS/$PACKAGE.spec diff --git a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/nova-xenapi-plugins.spec b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/nova-xenapi-plugins.spec deleted file mode 100644 index 1a61dbbad..000000000 --- a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/nova-xenapi-plugins.spec +++ /dev/null @@ -1,36 +0,0 @@ -Name: nova-xenapi-plugins -Version: 1.0 -Release: 1 -Summary: Files for XenAPI support. -License: Apache -Group: Applications/Utilities -Source0: nova-xenapi-plugins.tar.gz -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) - -%define debug_package %{nil} - -%description -This package contains files that are required for XenAPI support for OpenStack. - -%prep -%setup -q -n nova-xenapi-plugins - -%install -rm -rf $RPM_BUILD_ROOT -mkdir -p $RPM_BUILD_ROOT/etc -cp -r xapi.d $RPM_BUILD_ROOT/etc -chmod u+x $RPM_BUILD_ROOT/etc/xapi.d/plugins/objectstore -#%{_fixperms} $RPM_BUILD_ROOT/* - -%clean -rm -rf $RPM_BUILD_ROOT - -%files -%defattr(-,root,root,-) -/etc/xapi.d/plugins/agent -/etc/xapi.d/plugins/glance -/etc/xapi.d/plugins/migration -/etc/xapi.d/plugins/objectstore -/etc/xapi.d/plugins/pluginlib_nova.py -/etc/xapi.d/plugins/xenhost -/etc/xapi.d/plugins/xenstore.py diff --git a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec new file mode 100644 index 000000000..864ca7ff7 --- /dev/null +++ b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec @@ -0,0 +1,36 @@ +Name: openstack-xen-plugins +Version: 2011.3 +Release: 1 +Summary: Files for XenAPI support. +License: Apache +Group: Applications/Utilities +Source0: openstack-xen-plugins.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +Requires: parted + +%define debug_package %{nil} + +%description +This package contains files that are required for XenAPI support for OpenStack. + +%prep +%setup -q -n openstack-xen-plugins + +%install +rm -rf $RPM_BUILD_ROOT +mkdir -p $RPM_BUILD_ROOT/etc +cp -r xapi.d $RPM_BUILD_ROOT/etc +chmod a+x $RPM_BUILD_ROOT/etc/xapi.d/plugins/* + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +/etc/xapi.d/plugins/agent +/etc/xapi.d/plugins/glance +/etc/xapi.d/plugins/migration +/etc/xapi.d/plugins/objectstore +/etc/xapi.d/plugins/pluginlib_nova.py +/etc/xapi.d/plugins/xenhost +/etc/xapi.d/plugins/xenstore.py -- cgit From 7b6ded922adc26f26dd208d5de1763b708866cea Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Mon, 27 Jun 2011 12:10:47 -0400 Subject: Updating license to ASL 2.0 --- .../xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec index 864ca7ff7..91ff20e5f 100644 --- a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec +++ b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec @@ -2,7 +2,7 @@ Name: openstack-xen-plugins Version: 2011.3 Release: 1 Summary: Files for XenAPI support. -License: Apache +License: ASL 2.0 Group: Applications/Utilities Source0: openstack-xen-plugins.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -- cgit From 7c4f83bc8f119ff4486f913bd3e5ef7eff5b338f Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 27 Jun 2011 20:36:53 +0400 Subject: changed extension alias to os-floating-ips --- nova/api/openstack/contrib/floating_ips.py | 2 +- nova/tests/api/openstack/contrib/test_floating_ips.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index ca192f501..914ec5bfb 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -162,7 +162,7 @@ class Floating_ips(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - res = extensions.ResourceExtension('floating_ips', + res = extensions.ResourceExtension('os-floating-ips', FloatingIPController(), member_actions={ 'associate': 'POST', diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index bf62925aa..de1eb2f53 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -112,7 +112,7 @@ class FloatingIpTest(test.TestCase): self.assertEqual(view['floating_ip']['instance_id'], None) def test_floating_ips_list(self): - req = webob.Request.blank('/v1.1/floating_ips') + req = webob.Request.blank('/v1.1/os-floating-ips') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) @@ -127,7 +127,7 @@ class FloatingIpTest(test.TestCase): self.assertEqual(res_dict, response) def test_floating_ip_show(self): - req = webob.Request.blank('/v1.1/floating_ips/1') + req = webob.Request.blank('/v1.1/os-floating-ips/1') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) @@ -137,7 +137,7 @@ class FloatingIpTest(test.TestCase): self.assertEqual(res_dict['floating_ip']['instance_id'], None) def test_floating_ip_allocate(self): - req = webob.Request.blank('/v1.1/floating_ips') + req = webob.Request.blank('/v1.1/os-floating-ips') req.method = 'POST' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -148,7 +148,7 @@ class FloatingIpTest(test.TestCase): self.assertEqual(ip, expected) def test_floating_ip_release(self): - req = webob.Request.blank('/v1.1/floating_ips/1') + req = webob.Request.blank('/v1.1/os-floating-ips/1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -160,7 +160,7 @@ class FloatingIpTest(test.TestCase): def test_floating_ip_associate(self): body = dict(associate_address=dict(fixed_ip='1.2.3.4')) - req = webob.Request.blank('/v1.1/floating_ips/1/associate') + req = webob.Request.blank('/v1.1/os-floating-ips/1/associate') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -175,7 +175,7 @@ class FloatingIpTest(test.TestCase): self.assertEqual(actual, expected) def test_floating_ip_disassociate(self): - req = webob.Request.blank('/v1.1/floating_ips/1/disassociate') + req = webob.Request.blank('/v1.1/os-floating-ips/1/disassociate') req.method = 'POST' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) -- cgit From 3d4ec00de0458533d5e8d5eac9d686dd6b626e49 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 27 Jun 2011 10:51:05 -0700 Subject: make sure basic filters are setup on instance restart --- nova/virt/libvirt/connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index b06bfb714..fadf77629 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -185,6 +185,7 @@ class LibvirtConnection(driver.ComputeDriver): if state != power_state.RUNNING: continue + self.firewall_driver.setup_basic_filtering(instance) self.firewall_driver.prepare_instance_filter(instance) self.firewall_driver.apply_instance_filter(instance) -- cgit From 9855acc214a02d5181a0d8a735e57a89146127db Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 27 Jun 2011 15:49:24 -0400 Subject: Added nova.version to utils.py --- nova/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/utils.py b/nova/utils.py index a6b8d4cbe..badd67599 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -46,6 +46,7 @@ from eventlet.green import subprocess from nova import exception from nova import flags from nova import log as logging +from nova import version LOG = logging.getLogger("nova.utils") -- cgit From 4b8bcf30f934ea91290b7fe41536ba06ee832b3f Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Tue, 28 Jun 2011 08:57:05 +0000 Subject: Re-merging code for generating system-usages to get around bzr merge braindeadness. --- bin/instance-usage-audit | 127 +++++++++++++++++++++++++++++++++++++++++ nova/compute/manager.py | 94 ++++++++++++++++++++++++++++++ nova/db/api.py | 5 ++ nova/db/sqlalchemy/api.py | 18 ++++++ nova/notifier/test_notifier.py | 28 +++++++++ nova/tests/test_compute.py | 77 +++++++++++++++++++++++++ 6 files changed, 349 insertions(+) create mode 100755 bin/instance-usage-audit create mode 100644 nova/notifier/test_notifier.py diff --git a/bin/instance-usage-audit b/bin/instance-usage-audit new file mode 100755 index 000000000..1124cf550 --- /dev/null +++ b/bin/instance-usage-audit @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Openstack, LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Cron script to generate usage notifications for instances neither created + nor destroyed in a given time period. + + Together with the notifications generated by compute on instance + create/delete/resize, over that ime period, this allows an external + system consuming usage notification feeds to calculate instance usage + for each tenant. + + Time periods are specified like so: + [mdy] + + 1m = previous month. If the script is run April 1, it will generate usages + for March 1 thry March 31. + 3m = 3 previous months. + 90d = previous 90 days. + 1y = previous year. If run on Jan 1, it generates usages for + Jan 1 thru Dec 31 of the previous year. +""" + +import datetime +import gettext +import os +import sys +import time + +# If ../nova/__init__.py exists, add ../ to Python search path, so that +# it will override what happens to be installed in /usr/(local/)lib/python... +POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), + os.pardir, + os.pardir)) +if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')): + sys.path.insert(0, POSSIBLE_TOPDIR) + +gettext.install('nova', unicode=1) + + +from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import log as logging +from nova import utils + +from nova.notifier import api as notifier_api + +FLAGS = flags.FLAGS +flags.DEFINE_string('instance_usage_audit_period', '1m', + 'time period to generate instance usages for.') + +def time_period(period): + today = datetime.date.today() + unit = period[-1] + if unit not in 'mdy': + raise ValueError('Time period must be m, d, or y') + n = int(period[:-1]) + if unit == 'm': + year = today.year - (n//12) + n = n % 12 + if n >= today.month: + year -= 1 + month = 12 + (today.month - n) + else: + month = today.month - n + begin = datetime.datetime(day=1, month=month, year=year) + end = datetime.datetime(day=1, month=today.month, year=today.year) + + elif unit == 'y': + begin = datetime.datetime(day=1, month=1, year=today.year - n) + end = datetime.datetime(day=1, month=1, year=today.year) + + elif unit == 'd': + b = today - datetime.timedelta(days=n) + begin = datetime.datetime(day=b.day, month=b.month, year=b.year) + end = datetime.datetime(day=today.day, + month=today.month, + year=today.year) + + return (begin, end) + +if __name__ == '__main__': + utils.default_flagfile() + flags.FLAGS(sys.argv) + logging.setup() + begin, end = time_period(FLAGS.instance_usage_audit_period) + print "Creating usages for %s until %s" % (str(begin), str(end)) + instances = db.instance_get_active_by_window(context.get_admin_context(), + begin, + end) + print "%s instances" % len(instances) + for instance_ref in instances: + usage_info = dict( + tenant_id=instance_ref['project_id'], + user_id=instance_ref['user_id'], + instance_id=instance_ref['id'], + instance_type=instance_ref['instance_type']['name'], + instance_type_id=instance_ref['instance_type_id'], + display_name=instance_ref['display_name'], + created_at=str(instance_ref['created_at']), + launched_at=str(instance_ref['launched_at']) \ + if instance_ref['launched_at'] else '', + image_id=instance_ref['image_id'], + audit_period_begining=str(begin), + audit_period_ending=str(end)) + notifier_api.notify('compute.%s' % FLAGS.host, + 'compute.instance.exists', + notifier_api.INFO, + usage_info) + + diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 923feaa59..d0e9cdf95 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -50,10 +50,12 @@ from nova import flags from nova import log as logging from nova import manager from nova import network +from nova import notifier from nova import rpc from nova import utils from nova import volume from nova.compute import power_state +from nova.notifier import api as notifier_api from nova.virt import driver @@ -275,6 +277,21 @@ class ComputeManager(manager.SchedulerDependentManager): self._update_launched_at(context, instance_id) self._update_state(context, instance_id) + usage_info = dict( + tenant_id=instance_ref['project_id'], + user_id=instance_ref['user_id'], + instance_id=instance_ref['id'], + instance_type=instance_ref['instance_type']['name'], + instance_type_id=instance_ref['instance_type_id'], + display_name=instance_ref['display_name'], + created_at=str(instance_ref['created_at']), + launched_at=str(instance_ref['launched_at']) \ + if instance_ref['launched_at'] else '', + image_id=instance_ref['image_id']) + notifier_api.notify('compute.%s' % self.host, + 'compute.instance.create', + notifier_api.INFO, + usage_info) @exception.wrap_exception @checks_instance_lock @@ -327,6 +344,21 @@ class ComputeManager(manager.SchedulerDependentManager): # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) + usage_info = dict( + tenant_id=instance_ref['project_id'], + user_id=instance_ref['user_id'], + instance_id=instance_ref['id'], + instance_type=instance_ref['instance_type']['name'], + instance_type_id=instance_ref['instance_type_id'], + display_name=instance_ref['display_name'], + created_at=str(instance_ref['created_at']), + launched_at=str(instance_ref['launched_at']) \ + if instance_ref['launched_at'] else '', + image_id=instance_ref['image_id']) + notifier_api.notify('compute.%s' % self.host, + 'compute.instance.delete', + notifier_api.INFO, + usage_info) @exception.wrap_exception @checks_instance_lock @@ -354,6 +386,21 @@ class ComputeManager(manager.SchedulerDependentManager): self._update_image_id(context, instance_id, image_id) self._update_launched_at(context, instance_id) self._update_state(context, instance_id) + usage_info = dict( + tenant_id=instance_ref['project_id'], + user_id=instance_ref['user_id'], + instance_id=instance_ref['id'], + instance_type=instance_ref['instance_type']['name'], + instance_type_id=instance_ref['instance_type_id'], + display_name=instance_ref['display_name'], + created_at=str(instance_ref['created_at']), + launched_at=str(instance_ref['launched_at']) \ + if instance_ref['launched_at'] else '', + image_id=image_id) + notifier_api.notify('compute.%s' % self.host, + 'compute.instance.rebuild', + notifier_api.INFO, + usage_info) @exception.wrap_exception @checks_instance_lock @@ -501,6 +548,21 @@ class ComputeManager(manager.SchedulerDependentManager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) self.driver.destroy(instance_ref) + usage_info = dict( + tenant_id=instance_ref['project_id'], + user_id=instance_ref['user_id'], + instance_id=instance_ref['id'], + instance_type=instance_ref['instance_type']['name'], + instance_type_id=instance_ref['instance_type_id'], + display_name=instance_ref['display_name'], + created_at=str(instance_ref['created_at']), + launched_at=str(instance_ref['launched_at']) \ + if instance_ref['launched_at'] else '', + image_id=instance_ref['image_id']) + notifier_api.notify('compute.%s' % self.host, + 'compute.instance.resize.confirm', + notifier_api.INFO, + usage_info) @exception.wrap_exception @checks_instance_lock @@ -548,6 +610,21 @@ class ComputeManager(manager.SchedulerDependentManager): self.driver.revert_resize(instance_ref) self.db.migration_update(context, migration_id, {'status': 'reverted'}) + usage_info = dict( + tenant_id=instance_ref['project_id'], + user_id=instance_ref['user_id'], + instance_id=instance_ref['id'], + instance_type=instance_type['name'], + instance_type_id=instance_type['id'], + display_name=instance_ref['display_name'], + created_at=str(instance_ref['created_at']), + launched_at=str(instance_ref['launched_at']) \ + if instance_ref['launched_at'] else '', + image_id=instance_ref['image_id']) + notifier_api.notify('compute.%s' % self.host, + 'compute.instance.resize.revert', + notifier_api.INFO, + usage_info) @exception.wrap_exception @checks_instance_lock @@ -584,6 +661,23 @@ class ComputeManager(manager.SchedulerDependentManager): 'migration_id': migration_ref['id'], 'instance_id': instance_id, }, }) + usage_info = dict( + tenant_id=instance_ref['project_id'], + user_id=instance_ref['user_id'], + instance_id=instance_ref['id'], + instance_type=instance_ref['instance_type']['name'], + instance_type_id=instance_ref['instance_type_id'], + new_instance_type=instance_type['name'], + new_instance_type_id=instance_type['id'], + display_name=instance_ref['display_name'], + created_at=str(instance_ref['created_at']), + launched_at=str(instance_ref['launched_at']) \ + if instance_ref['launched_at'] else '', + image_id=instance_ref['image_id']) + notifier_api.notify('compute.%s' % self.host, + 'compute.instance.resize.prep', + notifier_api.INFO, + usage_info) @exception.wrap_exception @checks_instance_lock diff --git a/nova/db/api.py b/nova/db/api.py index ef8aa1143..aca403856 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -422,6 +422,11 @@ def instance_get_all(context): return IMPL.instance_get_all(context) +def instance_get_active_by_window(context, begin, end=None): + """Get instances active during a certain time window.""" + return IMPL.instance_get_active_by_window(context, begin, end) + + def instance_get_all_by_user(context, user_id): """Get all instances.""" return IMPL.instance_get_all_by_user(context, user_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 3681f30db..873cfe4d1 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -878,6 +878,24 @@ def instance_get_all(context): all() +@require_admin_context +def instance_get_active_by_window(context, begin, end=None): + """Return instances that were continuously active over the given window""" + session = get_session() + query = session.query(models.Instance).\ + options(joinedload_all('fixed_ip.floating_ips')).\ + options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ + filter(models.Instance.launched_at < begin) + if end: + query = query.filter(or_(models.Instance.terminated_at == None, + models.Instance.terminated_at > end)) + else: + query = query.filter(models.Instance.terminated_at == None) + return query.all() + + @require_admin_context def instance_get_all_by_user(context, user_id): session = get_session() diff --git a/nova/notifier/test_notifier.py b/nova/notifier/test_notifier.py new file mode 100644 index 000000000..d43f43e48 --- /dev/null +++ b/nova/notifier/test_notifier.py @@ -0,0 +1,28 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from nova import flags +from nova import log as logging + +FLAGS = flags.FLAGS + +NOTIFICATIONS = [] + + +def notify(message): + """Test notifier, stores notifications in memory for unittests.""" + NOTIFICATIONS.append(message) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 55e7ae0c4..30a65a4b1 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -38,6 +38,7 @@ from nova.compute import manager as compute_manager from nova.compute import power_state from nova.db.sqlalchemy import models from nova.image import local +from nova.notifier import test_notifier LOG = logging.getLogger('nova.tests.compute') FLAGS = flags.FLAGS @@ -63,6 +64,7 @@ class ComputeTestCase(test.TestCase): super(ComputeTestCase, self).setUp() self.flags(connection_type='fake', stub_network=True, + notification_driver='nova.notifier.test_notifier', network_manager='nova.network.manager.FlatManager') self.compute = utils.import_object(FLAGS.compute_manager) self.compute_api = compute.API() @@ -70,6 +72,7 @@ class ComputeTestCase(test.TestCase): self.user = self.manager.create_user('fake', 'fake', 'fake') self.project = self.manager.create_project('fake', 'fake', 'fake') self.context = context.RequestContext('fake', 'fake', False) + test_notifier.NOTIFICATIONS = [] def fake_show(meh, context, id): return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} @@ -305,6 +308,50 @@ class ComputeTestCase(test.TestCase): self.assert_(console) self.compute.terminate_instance(self.context, instance_id) + def test_run_instance_usage_notification(self): + """Ensure run instance generates apropriate usage notification""" + instance_id = self._create_instance() + self.compute.run_instance(self.context, instance_id) + self.assertEquals(len(test_notifier.NOTIFICATIONS), 1) + msg = test_notifier.NOTIFICATIONS[0] + self.assertEquals(msg['priority'], 'INFO') + self.assertEquals(msg['event_type'], 'compute.instance.create') + payload = msg['payload'] + self.assertEquals(payload['tenant_id'], self.project.id) + self.assertEquals(payload['user_id'], self.user.id) + self.assertEquals(payload['instance_id'], instance_id) + self.assertEquals(payload['instance_type'], 'm1.tiny') + type_id = instance_types.get_instance_type_by_name('m1.tiny')['id'] + self.assertEquals(str(payload['instance_type_id']), str(type_id)) + self.assertTrue('display_name' in payload) + self.assertTrue('created_at' in payload) + self.assertTrue('launched_at' in payload) + self.assertEquals(payload['image_id'], '1') + self.compute.terminate_instance(self.context, instance_id) + + def test_terminate_usage_notification(self): + """Ensure terminate_instance generates apropriate usage notification""" + instance_id = self._create_instance() + self.compute.run_instance(self.context, instance_id) + test_notifier.NOTIFICATIONS = [] + self.compute.terminate_instance(self.context, instance_id) + + self.assertEquals(len(test_notifier.NOTIFICATIONS), 1) + msg = test_notifier.NOTIFICATIONS[0] + self.assertEquals(msg['priority'], 'INFO') + self.assertEquals(msg['event_type'], 'compute.instance.delete') + payload = msg['payload'] + self.assertEquals(payload['tenant_id'], self.project.id) + self.assertEquals(payload['user_id'], self.user.id) + self.assertEquals(payload['instance_id'], instance_id) + self.assertEquals(payload['instance_type'], 'm1.tiny') + type_id = instance_types.get_instance_type_by_name('m1.tiny')['id'] + self.assertEquals(str(payload['instance_type_id']), str(type_id)) + self.assertTrue('display_name' in payload) + self.assertTrue('created_at' in payload) + self.assertTrue('launched_at' in payload) + self.assertEquals(payload['image_id'], '1') + def test_run_instance_existing(self): """Ensure failure when running an instance that already exists""" instance_id = self._create_instance() @@ -334,6 +381,36 @@ class ComputeTestCase(test.TestCase): self.compute.terminate_instance(self.context, instance_id) + def test_resize_instance_notification(self): + """Ensure instance can be migrated/resized""" + instance_id = self._create_instance() + context = self.context.elevated() + + self.compute.run_instance(self.context, instance_id) + test_notifier.NOTIFICATIONS = [] + + db.instance_update(self.context, instance_id, {'host': 'foo'}) + self.compute.prep_resize(context, instance_id, 1) + migration_ref = db.migration_get_by_instance_and_status(context, + instance_id, 'pre-migrating') + + self.assertEquals(len(test_notifier.NOTIFICATIONS), 1) + msg = test_notifier.NOTIFICATIONS[0] + self.assertEquals(msg['priority'], 'INFO') + self.assertEquals(msg['event_type'], 'compute.instance.resize.prep') + payload = msg['payload'] + self.assertEquals(payload['tenant_id'], self.project.id) + self.assertEquals(payload['user_id'], self.user.id) + self.assertEquals(payload['instance_id'], instance_id) + self.assertEquals(payload['instance_type'], 'm1.tiny') + type_id = instance_types.get_instance_type_by_name('m1.tiny')['id'] + self.assertEquals(str(payload['instance_type_id']), str(type_id)) + self.assertTrue('display_name' in payload) + self.assertTrue('created_at' in payload) + self.assertTrue('launched_at' in payload) + self.assertEquals(payload['image_id'], '1') + self.compute.terminate_instance(context, instance_id) + def test_resize_instance(self): """Ensure instance can be migrated/resized""" instance_id = self._create_instance() -- cgit From e2c66d0e96467d510d01a5c5f60a56e8252dce5b Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Tue, 28 Jun 2011 09:08:35 +0000 Subject: Fix pep8 nits in audit script --- bin/instance-usage-audit | 53 ++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/bin/instance-usage-audit b/bin/instance-usage-audit index 1124cf550..cef9b464b 100755 --- a/bin/instance-usage-audit +++ b/bin/instance-usage-audit @@ -17,22 +17,22 @@ # under the License. """Cron script to generate usage notifications for instances neither created - nor destroyed in a given time period. - + nor destroyed in a given time period. + Together with the notifications generated by compute on instance - create/delete/resize, over that ime period, this allows an external - system consuming usage notification feeds to calculate instance usage + create/delete/resize, over that ime period, this allows an external + system consuming usage notification feeds to calculate instance usage for each tenant. Time periods are specified like so: - [mdy] + [mdy] 1m = previous month. If the script is run April 1, it will generate usages for March 1 thry March 31. 3m = 3 previous months. - 90d = previous 90 days. - 1y = previous year. If run on Jan 1, it generates usages for - Jan 1 thru Dec 31 of the previous year. + 90d = previous 90 days. + 1y = previous year. If run on Jan 1, it generates usages for + Jan 1 thru Dec 31 of the previous year. """ import datetime @@ -65,6 +65,7 @@ FLAGS = flags.FLAGS flags.DEFINE_string('instance_usage_audit_period', '1m', 'time period to generate instance usages for.') + def time_period(period): today = datetime.date.today() unit = period[-1] @@ -72,27 +73,27 @@ def time_period(period): raise ValueError('Time period must be m, d, or y') n = int(period[:-1]) if unit == 'm': - year = today.year - (n//12) - n = n % 12 - if n >= today.month: - year -= 1 - month = 12 + (today.month - n) - else: - month = today.month - n - begin = datetime.datetime(day=1, month=month, year=year) - end = datetime.datetime(day=1, month=today.month, year=today.year) + year = today.year - (n // 12) + n = n % 12 + if n >= today.month: + year -= 1 + month = 12 + (today.month - n) + else: + month = today.month - n + begin = datetime.datetime(day=1, month=month, year=year) + end = datetime.datetime(day=1, month=today.month, year=today.year) elif unit == 'y': - begin = datetime.datetime(day=1, month=1, year=today.year - n) - end = datetime.datetime(day=1, month=1, year=today.year) - + begin = datetime.datetime(day=1, month=1, year=today.year - n) + end = datetime.datetime(day=1, month=1, year=today.year) + elif unit == 'd': - b = today - datetime.timedelta(days=n) - begin = datetime.datetime(day=b.day, month=b.month, year=b.year) - end = datetime.datetime(day=today.day, - month=today.month, + b = today - datetime.timedelta(days=n) + begin = datetime.datetime(day=b.day, month=b.month, year=b.year) + end = datetime.datetime(day=today.day, + month=today.month, year=today.year) - + return (begin, end) if __name__ == '__main__': @@ -123,5 +124,3 @@ if __name__ == '__main__': 'compute.instance.exists', notifier_api.INFO, usage_info) - - -- cgit From f6390090ff48258078113b2e6d9dd5fbf49bea3a Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 28 Jun 2011 10:39:04 -0400 Subject: I accidently the whole unittest2 --- nova/tests/test_service.py | 1 - nova/tests/test_wsgi.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py index 350dc62ee..f45f76b73 100644 --- a/nova/tests/test_service.py +++ b/nova/tests/test_service.py @@ -21,7 +21,6 @@ Unit Tests for remote procedure calls using queue """ import mox -import unittest2 as unittest from nova import context from nova import db diff --git a/nova/tests/test_wsgi.py b/nova/tests/test_wsgi.py index 010fb819e..b71e8d418 100644 --- a/nova/tests/test_wsgi.py +++ b/nova/tests/test_wsgi.py @@ -21,7 +21,7 @@ import os.path import tempfile -import unittest2 as unittest +import unittest import nova.exception import nova.test -- cgit From 4ec4ec7e4008adabf051651e3c55137c9954139f Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 28 Jun 2011 10:43:25 -0400 Subject: pep8 fix --- nova/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/utils.py b/nova/utils.py index caa1df22a..918541224 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -790,4 +790,3 @@ class Bootstrapper(object): for key in FLAGS: value = FLAGS.get(key, None) logging.audit(_("%(key)s : %(value)s" % locals())) - -- cgit From 18ef175d766794c8a1a9b3e8c5d6b4f18232696c Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Tue, 28 Jun 2011 18:52:22 +0400 Subject: Prevent test case from ruining other tests. Make it work in earlier python versions. --- nova/tests/test_auth.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index 4aebbe940..71e0d17c9 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -373,9 +373,10 @@ class AuthManagerLdapTestCase(_AuthManagerBaseTestCase): def test_reconnect_on_server_failure(self): self.manager.get_users() fakeldap.server_fail = True - with self.assertRaises(fakeldap.SERVER_DOWN): - self.manager.get_users() - fakeldap.server_fail = False + try: + self.assertRaises(fakeldap.SERVER_DOWN, self.manager.get_users) + finally: + fakeldap.server_fail = False self.manager.get_users() -- cgit From 4c1d05d27f207e71546f20c4e603839afc232b5a Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Tue, 28 Jun 2011 15:21:08 +0000 Subject: Fix issues due to renming of imange_id attrib. --- bin/instance-usage-audit | 2 +- nova/compute/manager.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/instance-usage-audit b/bin/instance-usage-audit index cef9b464b..1fa2c2186 100755 --- a/bin/instance-usage-audit +++ b/bin/instance-usage-audit @@ -117,7 +117,7 @@ if __name__ == '__main__': created_at=str(instance_ref['created_at']), launched_at=str(instance_ref['launched_at']) \ if instance_ref['launched_at'] else '', - image_id=instance_ref['image_id'], + image_ref=instance_ref['image_ref'], audit_period_begining=str(begin), audit_period_ending=str(end)) notifier_api.notify('compute.%s' % FLAGS.host, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index d081937bd..2c4f500f0 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -355,7 +355,7 @@ class ComputeManager(manager.SchedulerDependentManager): created_at=str(instance_ref['created_at']), launched_at=str(instance_ref['launched_at']) \ if instance_ref['launched_at'] else '', - image_id=instance_ref['image_id']) + image_ref=instance_ref['image_ref']) notifier_api.notify('compute.%s' % self.host, 'compute.instance.create', notifier_api.INFO, @@ -451,7 +451,7 @@ class ComputeManager(manager.SchedulerDependentManager): created_at=str(instance_ref['created_at']), launched_at=str(instance_ref['launched_at']) \ if instance_ref['launched_at'] else '', - image_id=instance_ref['image_id']) + image_ref=instance_ref['image_ref']) notifier_api.notify('compute.%s' % self.host, 'compute.instance.delete', notifier_api.INFO, @@ -502,7 +502,7 @@ class ComputeManager(manager.SchedulerDependentManager): created_at=str(instance_ref['created_at']), launched_at=str(instance_ref['launched_at']) \ if instance_ref['launched_at'] else '', - image_id=image_id) + image_ref=image_ref) notifier_api.notify('compute.%s' % self.host, 'compute.instance.rebuild', notifier_api.INFO, @@ -694,7 +694,7 @@ class ComputeManager(manager.SchedulerDependentManager): created_at=str(instance_ref['created_at']), launched_at=str(instance_ref['launched_at']) \ if instance_ref['launched_at'] else '', - image_id=instance_ref['image_id']) + image_ref=instance_ref['image_ref']) notifier_api.notify('compute.%s' % self.host, 'compute.instance.resize.confirm', notifier_api.INFO, @@ -756,7 +756,7 @@ class ComputeManager(manager.SchedulerDependentManager): created_at=str(instance_ref['created_at']), launched_at=str(instance_ref['launched_at']) \ if instance_ref['launched_at'] else '', - image_id=instance_ref['image_id']) + image_ref=instance_ref['image_ref']) notifier_api.notify('compute.%s' % self.host, 'compute.instance.resize.revert', notifier_api.INFO, @@ -809,7 +809,7 @@ class ComputeManager(manager.SchedulerDependentManager): created_at=str(instance_ref['created_at']), launched_at=str(instance_ref['launched_at']) \ if instance_ref['launched_at'] else '', - image_id=instance_ref['image_id']) + image_ref=instance_ref['image_ref']) notifier_api.notify('compute.%s' % self.host, 'compute.instance.resize.prep', notifier_api.INFO, -- cgit From 66b2fef4b294c7a351cc5815632da520c6ee811b Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Tue, 28 Jun 2011 15:50:07 +0000 Subject: Fix yet more merge-skew. --- nova/compute/manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 2c4f500f0..ea5734ebd 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -441,6 +441,8 @@ class ComputeManager(manager.SchedulerDependentManager): # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) usage_info = dict( tenant_id=instance_ref['project_id'], user_id=instance_ref['user_id'], -- cgit From c69fc237f3628d579a35af1f7bf3fbb4adeb81b7 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Tue, 28 Jun 2011 15:59:44 +0000 Subject: Fix thinko in previous fix :P --- nova/compute/manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ea5734ebd..0a0ebd768 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -438,11 +438,10 @@ class ComputeManager(manager.SchedulerDependentManager): def terminate_instance(self, context, instance_id): """Terminate an instance on this host.""" self._shutdown_instance(context, instance_id, 'Terminating') + instance_ref = self.db.instance_get(context.elevated(), instance_id) # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) - context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) usage_info = dict( tenant_id=instance_ref['project_id'], user_id=instance_ref['user_id'], -- cgit From 24835b0348a9a6d8bd4e40107990d1abb41538c2 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Tue, 28 Jun 2011 16:08:27 +0000 Subject: Fix merge issue in compute unittest. --- nova/tests/test_compute.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index e94a62679..4d42b1fdf 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -348,7 +348,7 @@ class ComputeTestCase(test.TestCase): self.assertTrue('display_name' in payload) self.assertTrue('created_at' in payload) self.assertTrue('launched_at' in payload) - self.assertEquals(payload['image_id'], '1') + self.assertEquals(payload['image_ref'], '1') self.compute.terminate_instance(self.context, instance_id) def test_terminate_usage_notification(self): @@ -372,7 +372,7 @@ class ComputeTestCase(test.TestCase): self.assertTrue('display_name' in payload) self.assertTrue('created_at' in payload) self.assertTrue('launched_at' in payload) - self.assertEquals(payload['image_id'], '1') + self.assertEquals(payload['image_ref'], '1') def test_run_instance_existing(self): """Ensure failure when running an instance that already exists""" @@ -452,7 +452,7 @@ class ComputeTestCase(test.TestCase): self.assertTrue('display_name' in payload) self.assertTrue('created_at' in payload) self.assertTrue('launched_at' in payload) - self.assertEquals(payload['image_id'], '1') + self.assertEquals(payload['image_ref'], '1') self.compute.terminate_instance(context, instance_id) def test_resize_instance(self): -- cgit From 498f2d671573fc19d551516f7ead5da8d052ee18 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Tue, 28 Jun 2011 20:37:05 +0000 Subject: Refactored usage generation --- bin/instance-usage-audit | 16 ++-------- nova/compute/manager.py | 77 ++++++------------------------------------------ nova/utils.py | 16 ++++++++++ 3 files changed, 28 insertions(+), 81 deletions(-) diff --git a/bin/instance-usage-audit b/bin/instance-usage-audit index 1fa2c2186..a06c6b1b3 100755 --- a/bin/instance-usage-audit +++ b/bin/instance-usage-audit @@ -107,19 +107,9 @@ if __name__ == '__main__': end) print "%s instances" % len(instances) for instance_ref in instances: - usage_info = dict( - tenant_id=instance_ref['project_id'], - user_id=instance_ref['user_id'], - instance_id=instance_ref['id'], - instance_type=instance_ref['instance_type']['name'], - instance_type_id=instance_ref['instance_type_id'], - display_name=instance_ref['display_name'], - created_at=str(instance_ref['created_at']), - launched_at=str(instance_ref['launched_at']) \ - if instance_ref['launched_at'] else '', - image_ref=instance_ref['image_ref'], - audit_period_begining=str(begin), - audit_period_ending=str(end)) + usage_info = utils.usage_from_instance(instance_ref, + audit_period_begining=str(begin), + audit_period_ending=str(end)) notifier_api.notify('compute.%s' % FLAGS.host, 'compute.instance.exists', notifier_api.INFO, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0a0ebd768..98e02f5b2 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -345,17 +345,7 @@ class ComputeManager(manager.SchedulerDependentManager): self._update_launched_at(context, instance_id) self._update_state(context, instance_id) - usage_info = dict( - tenant_id=instance_ref['project_id'], - user_id=instance_ref['user_id'], - instance_id=instance_ref['id'], - instance_type=instance_ref['instance_type']['name'], - instance_type_id=instance_ref['instance_type_id'], - display_name=instance_ref['display_name'], - created_at=str(instance_ref['created_at']), - launched_at=str(instance_ref['launched_at']) \ - if instance_ref['launched_at'] else '', - image_ref=instance_ref['image_ref']) + usage_info = utils.usage_from_instance(instance_ref) notifier_api.notify('compute.%s' % self.host, 'compute.instance.create', notifier_api.INFO, @@ -442,17 +432,7 @@ class ComputeManager(manager.SchedulerDependentManager): # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) - usage_info = dict( - tenant_id=instance_ref['project_id'], - user_id=instance_ref['user_id'], - instance_id=instance_ref['id'], - instance_type=instance_ref['instance_type']['name'], - instance_type_id=instance_ref['instance_type_id'], - display_name=instance_ref['display_name'], - created_at=str(instance_ref['created_at']), - launched_at=str(instance_ref['launched_at']) \ - if instance_ref['launched_at'] else '', - image_ref=instance_ref['image_ref']) + usage_info = utils.usage_from_instance(instance_ref) notifier_api.notify('compute.%s' % self.host, 'compute.instance.delete', notifier_api.INFO, @@ -493,17 +473,8 @@ class ComputeManager(manager.SchedulerDependentManager): self._update_image_ref(context, instance_id, image_ref) self._update_launched_at(context, instance_id) self._update_state(context, instance_id) - usage_info = dict( - tenant_id=instance_ref['project_id'], - user_id=instance_ref['user_id'], - instance_id=instance_ref['id'], - instance_type=instance_ref['instance_type']['name'], - instance_type_id=instance_ref['instance_type_id'], - display_name=instance_ref['display_name'], - created_at=str(instance_ref['created_at']), - launched_at=str(instance_ref['launched_at']) \ - if instance_ref['launched_at'] else '', - image_ref=image_ref) + usage_info = utils.usage_from_instance(instance_ref, + image_ref=image_ref) notifier_api.notify('compute.%s' % self.host, 'compute.instance.rebuild', notifier_api.INFO, @@ -685,17 +656,7 @@ class ComputeManager(manager.SchedulerDependentManager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) self.driver.destroy(instance_ref) - usage_info = dict( - tenant_id=instance_ref['project_id'], - user_id=instance_ref['user_id'], - instance_id=instance_ref['id'], - instance_type=instance_ref['instance_type']['name'], - instance_type_id=instance_ref['instance_type_id'], - display_name=instance_ref['display_name'], - created_at=str(instance_ref['created_at']), - launched_at=str(instance_ref['launched_at']) \ - if instance_ref['launched_at'] else '', - image_ref=instance_ref['image_ref']) + usage_info = utils.usage_from_instance(instance_ref) notifier_api.notify('compute.%s' % self.host, 'compute.instance.resize.confirm', notifier_api.INFO, @@ -747,17 +708,7 @@ class ComputeManager(manager.SchedulerDependentManager): self.driver.revert_resize(instance_ref) self.db.migration_update(context, migration_id, {'status': 'reverted'}) - usage_info = dict( - tenant_id=instance_ref['project_id'], - user_id=instance_ref['user_id'], - instance_id=instance_ref['id'], - instance_type=instance_type['name'], - instance_type_id=instance_type['id'], - display_name=instance_ref['display_name'], - created_at=str(instance_ref['created_at']), - launched_at=str(instance_ref['launched_at']) \ - if instance_ref['launched_at'] else '', - image_ref=instance_ref['image_ref']) + usage_info = utils.usage_from_instance(instance_ref) notifier_api.notify('compute.%s' % self.host, 'compute.instance.resize.revert', notifier_api.INFO, @@ -798,19 +749,9 @@ class ComputeManager(manager.SchedulerDependentManager): 'migration_id': migration_ref['id'], 'instance_id': instance_id, }, }) - usage_info = dict( - tenant_id=instance_ref['project_id'], - user_id=instance_ref['user_id'], - instance_id=instance_ref['id'], - instance_type=instance_ref['instance_type']['name'], - instance_type_id=instance_ref['instance_type_id'], - new_instance_type=instance_type['name'], - new_instance_type_id=instance_type['id'], - display_name=instance_ref['display_name'], - created_at=str(instance_ref['created_at']), - launched_at=str(instance_ref['launched_at']) \ - if instance_ref['launched_at'] else '', - image_ref=instance_ref['image_ref']) + usage_info = utils.usage_from_instance(instance_ref, + new_instance_type=instance_type['name'], + new_instance_type_id=instance_type['id']) notifier_api.notify('compute.%s' % self.host, 'compute.instance.resize.prep', notifier_api.INFO, diff --git a/nova/utils.py b/nova/utils.py index 6d8324e5b..aee2715ba 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -279,6 +279,22 @@ EASIER_PASSWORD_SYMBOLS = ('23456789' # Removed: 0, 1 'ABCDEFGHJKLMNPQRSTUVWXYZ') # Removed: I, O +def usage_from_instance(instance_ref, **kw): + usage_info = dict( + tenant_id=instance_ref['project_id'], + user_id=instance_ref['user_id'], + instance_id=instance_ref['id'], + instance_type=instance_ref['instance_type']['name'], + instance_type_id=instance_ref['instance_type_id'], + display_name=instance_ref['display_name'], + created_at=str(instance_ref['created_at']), + launched_at=str(instance_ref['launched_at']) \ + if instance_ref['launched_at'] else '', + image_ref=instance_ref['image_ref']) + usage_info.update(kw) + return usage_info + + def generate_password(length=20, symbols=DEFAULT_PASSWORD_SYMBOLS): """Generate a random password from the supplied symbols. -- cgit From 834b1741b4cd5e42393a8947a5c1fea80c625ee2 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 28 Jun 2011 17:26:08 -0500 Subject: Use milestone cut. --- bin/nova-api | 6 ++++++ .../migrate_repo/versions/027_add_provider_firewall_rules.py | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index ea99a1b48..fff67251f 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -23,8 +23,14 @@ Starts both the EC2 and OpenStack APIs in separate processes. """ +import os import sys +possible_topdir = os.path.normpath(os.path.join(os.path.abspath( + sys.argv[0]), os.pardir, os.pardir)) +if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): + sys.path.insert(0, possible_topdir) + import nova.service import nova.utils diff --git a/nova/db/sqlalchemy/migrate_repo/versions/027_add_provider_firewall_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/027_add_provider_firewall_rules.py index 5aa30f7a8..cb3c73170 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/027_add_provider_firewall_rules.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/027_add_provider_firewall_rules.py @@ -58,8 +58,7 @@ provider_fw_rules = Table('provider_fw_rules', meta, Column('to_port', Integer()), Column('cidr', String(length=255, convert_unicode=False, assert_unicode=None, - unicode_error=None, _warn_on_bytestring=False)) - ) + unicode_error=None, _warn_on_bytestring=False))) def upgrade(migrate_engine): -- cgit From 68b313077578870908ebcc5b668df67ce921929a Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 29 Jun 2011 16:08:34 +0000 Subject: check_domid_changes is superfluous right now since it's only used when timeout is used. So simplify code a little bit --- nova/virt/xenapi/vmops.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 1e908f513..22ef0eb67 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -258,8 +258,7 @@ class VMOps(object): # need to be more patient than normal as well as watch for # domid changes version = self.get_agent_version(instance, - timeout=FLAGS.windows_version_timeout, - check_domid_changes=True) + timeout=FLAGS.windows_version_timeout) else: version = self.get_agent_version(instance) if not version: @@ -516,8 +515,7 @@ class VMOps(object): task = self._session.call_xenapi('Async.VM.clean_reboot', vm_ref) self._session.wait_for_task(task, instance.id) - def get_agent_version(self, instance, timeout=None, - check_domid_changes=False): + def get_agent_version(self, instance, timeout=None): """Get the version of the agent running on the VM instance.""" def _call(): @@ -543,14 +541,13 @@ class VMOps(object): if ret: return ret - if check_domid_changes: - vm_rec = self._session.get_xenapi().VM.get_record(vm_ref) - if vm_rec['domid'] != domid: - LOG.info(_('domid changed from %(olddomid)s to ' - '%(newdomid)s') % { - 'olddomid': domid, - 'newdomid': vm_rec['domid']}) - domid = vm_rec['domid'] + vm_rec = self._session.get_xenapi().VM.get_record(vm_ref) + if vm_rec['domid'] != domid: + LOG.info(_('domid changed from %(olddomid)s to ' + '%(newdomid)s') % { + 'olddomid': domid, + 'newdomid': vm_rec['domid']}) + domid = vm_rec['domid'] else: return _call() -- cgit From c1e799795e9634f4b56aaeb76c4a9553da3846e2 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 29 Jun 2011 21:39:43 +0000 Subject: Rename one use of timeout to expiration to make the purpose clearer --- nova/virt/xenapi/vmops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 22ef0eb67..53d2d2cec 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -535,8 +535,8 @@ class VMOps(object): domid = vm_rec['domid'] - timeout = time.time() + timeout - while time.time() < timeout: + expiration = time.time() + timeout + while time.time() < expiration: ret = _call() if ret: return ret -- cgit