From e75b8f9bb05bc539500b88ebba7a98903bec0ba9 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Thu, 2 Dec 2010 11:40:44 +0100 Subject: Add a simple abstraction for firewalls. Some might say I should have done this from the start. They'd be absolutely correct. --- nova/virt/libvirt_conn.py | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 18085089f..0870a00fb 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -104,6 +104,8 @@ flags.DEFINE_string('libvirt_uri', flags.DEFINE_bool('allow_project_net_traffic', True, 'Whether to allow in project network traffic') +flags.DEFINE_bool('firewall_driver', None, + 'Firewall driver (defaults to nwfilter)') def get_connection(read_only): @@ -128,6 +130,12 @@ class LibvirtConnection(object): self.rescue_xml = open(rescue_file).read() self._wrapped_conn = None self.read_only = read_only + if not FLAGS.firewall_driver: + # This is weird looking, but NWFilter is libvirt specific + # and requires more cooperation between the two. + self.firewall_driver = NWFilterFirewall(self._conn) + else: + self.firewall_driver = utils.import_object(FLAGS.firewall_driver) @property def _conn(self): @@ -344,11 +352,12 @@ class LibvirtConnection(object): instance['id'], power_state.NOSTATE, 'launching') - yield NWFilterFirewall(self._conn).\ - setup_nwfilters_for_instance(instance) + + yield self.firewall_driver.prepare_instance_filter(instance) yield self._create_image(instance, xml) yield self._conn.createXML(xml, 0) logging.debug("instance %s: is running", instance['name']) + yield self.firewall_driver.apply_instance_filter(instance) local_d = defer.Deferred() timer = task.LoopingCall(f=None) @@ -645,11 +654,35 @@ class LibvirtConnection(object): return domain.interfaceStats(interface) def refresh_security_group(self, security_group_id): - fw = NWFilterFirewall(self._conn) - fw.ensure_security_group_filter(security_group_id) + self.firewall_driver.refresh_security_group(security_group_id) + + +class FirewallDriver(object): + def prepare_instance_filter(self, instance): + """Prepare filters for the instance. + + At this point, the instance isn't running yet.""" + raise NotImplementedError() + + def apply_instance_filter(self, instance): + """Apply instance filter. + + Once this method returns, the instance should be firewalled + appropriately. This method should as far as possible be a + no-op. It's vastly preferred to get everything set up in + prepare_instance_filter. + """ + raise NotImplementedError() + + def refresh_security_group(security_group_id): + """Refresh security group from data store + + Gets called when changes have been made to the security + group.""" + raise NotImplementedError() -class NWFilterFirewall(object): +class NWFilterFirewall(FirewallDriver): """ This class implements a network filtering mechanism versatile enough for EC2 style Security Group filtering by leveraging @@ -767,7 +800,7 @@ class NWFilterFirewall(object): return str(net.net()), str(net.netmask()) @defer.inlineCallbacks - def setup_nwfilters_for_instance(self, instance): + def prepare_instance_filter(self, instance): """ Creates an NWFilter for the given instance. In the process, it makes sure the filters for the security groups as well as @@ -795,7 +828,7 @@ class NWFilterFirewall(object): instance['project_id'] for security_group in instance.security_groups: - yield self.ensure_security_group_filter(security_group['id']) + yield self.refresh_security_group(security_group['id']) nwfilter_xml += " \n" % \ security_group['id'] -- cgit From 16c440c5b598dab51ce4bd37c48f02f3da87c092 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Thu, 2 Dec 2010 16:21:31 +0100 Subject: Refactor nwfilter code somewhat. For iptables based firewalls, I still want to leave it to nwfilter to protect against arp, mac, and ip spoofing, so it needed a bit of a split. --- nova/tests/virt_unittest.py | 8 ++- nova/virt/libvirt_conn.py | 157 ++++++++++++++++++++++++++++++-------------- 2 files changed, 112 insertions(+), 53 deletions(-) diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index d49383fb7..4bbf2b50b 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -89,7 +89,7 @@ class LibvirtConnTestCase(test.TrialTestCase): for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): FLAGS.libvirt_type = libvirt_type - conn = libvirt_conn.LibvirtConnection(True) + conn = libvirt_conn.get_connection(True) uri, _template, _rescue = conn.get_uri_and_templates() self.assertEquals(uri, expected_uri) @@ -130,6 +130,8 @@ class NWFilterTestCase(test.TrialTestCase): class Mock(object): pass + #def __call__(self, *args, **kwargs): + # return self.manager = manager.AuthManager() self.user = self.manager.create_user('fake', 'fake', 'fake', @@ -139,7 +141,7 @@ class NWFilterTestCase(test.TrialTestCase): self.fake_libvirt_connection = Mock() - self.fw = libvirt_conn.NWFilterFirewall(self.fake_libvirt_connection) + self.fw = libvirt_conn.NWFilterFirewall(lambda:self.fake_libvirt_connection) def tearDown(self): self.manager.delete_project(self.project) @@ -252,7 +254,7 @@ class NWFilterTestCase(test.TrialTestCase): self.security_group.id) instance = db.instance_get(self.context, inst_id) - d = self.fw.setup_nwfilters_for_instance(instance) + d = self.fw.prepare_instance_filter(instance) d.addCallback(_ensure_all_called) d.addCallback(lambda _: self.teardown_security_group()) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 0870a00fb..a0149c5ca 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -130,20 +130,22 @@ class LibvirtConnection(object): self.rescue_xml = open(rescue_file).read() self._wrapped_conn = None self.read_only = read_only + + self.nwfilter = NWFilterFirewall(self._get_connection) + if not FLAGS.firewall_driver: - # This is weird looking, but NWFilter is libvirt specific - # and requires more cooperation between the two. - self.firewall_driver = NWFilterFirewall(self._conn) + self.firewall_driver = self.nwfilter + self.nwfilter.handle_security_groups = True else: self.firewall_driver = utils.import_object(FLAGS.firewall_driver) - @property - def _conn(self): + def _get_connection(self): if not self._wrapped_conn or not self._test_connection(): logging.debug('Connecting to libvirt: %s' % self.libvirt_uri) self._wrapped_conn = self._connect(self.libvirt_uri, self.read_only) return self._wrapped_conn + _conn = property(_get_connection) def _test_connection(self): try: @@ -353,6 +355,7 @@ class LibvirtConnection(object): power_state.NOSTATE, 'launching') + yield self.nwfilter.setup_basic_filtering(instance) yield self.firewall_driver.prepare_instance_filter(instance) yield self._create_image(instance, xml) yield self._conn.createXML(xml, 0) @@ -689,6 +692,9 @@ class NWFilterFirewall(FirewallDriver): libvirt's nwfilter. First, all instances get a filter ("nova-base-filter") applied. + This filter provides some basic security such as protection against + MAC spoofing, IP spoofing, and ARP spoofing. + This filter drops all incoming ipv4 and ipv6 connections. Outgoing connections are never blocked. @@ -722,44 +728,80 @@ class NWFilterFirewall(FirewallDriver): (*) This sentence brought to you by the redundancy department of redundancy. + """ def __init__(self, get_connection): - self._conn = get_connection - - nova_base_filter = ''' - 26717364-50cf-42d1-8185-29bf893ab110 - - - - - - - - ''' - - nova_dhcp_filter = ''' - 891e4787-e5c0-d59b-cbd6-41bc3c6b36fc - - - - - - - ''' + self._libvirt_get_connection = get_connection + self.static_filters_configured = False + + def _get_connection(self): + return self._libvirt_get_connection() + _conn = property(_get_connection) + + def nova_dhcp_filter(self): + """The standard allow-dhcp-server filter is an one, so it uses + ebtables to allow traffic through. Without a corresponding rule in + iptables, it'll get blocked anyway.""" + + return ''' + 891e4787-e5c0-d59b-cbd6-41bc3c6b36fc + + + + + + + ''' + + def setup_basic_filtering(self, instance): + """Set up basic filtering (MAC, IP, and ARP spoofing protection)""" + + if self.handle_security_groups: + # No point in setting up a filter set that we'll be overriding + # anyway. + return + + self._ensure_static_filters() + + instance_filter_name = self._instance_filter_name(instance) + self._define_filter(self._filter_container(instance_filter_name, + ['nova-base'])) + + @defer.inlineCallbacks + def _ensure_static_filters(self): + if self.static_filters_configured: + return + + yield self._define_filter(self._filter_container('nova-base', + ['no-mac-spoofing', + 'no-ip-spoofing', + 'no-arp-spoofing', + 'allow-dhcp-server'])) + yield self._define_filter(self.nova_base_ipv4_filter) + yield self._define_filter(self.nova_base_ipv6_filter) + yield self._define_filter(self.nova_dhcp_filter) + + self.static_filters_configured = True + + def _filter_container(self, name, filters): + xml = '''%s''' % ( + name, + ''.join(["" % (f,) for f in filters])) + return xml def nova_base_ipv4_filter(self): retval = "" for protocol in ['tcp', 'udp', 'icmp']: for direction, action, priority in [('out', 'accept', 399), - ('inout', 'drop', 400)]: + ('in', 'drop', 400)]: retval += """ <%s /> """ % (action, direction, @@ -771,7 +813,7 @@ class NWFilterFirewall(FirewallDriver): retval = "" for protocol in ['tcp', 'udp', 'icmp']: for direction, action, priority in [('out', 'accept', 399), - ('inout', 'drop', 400)]: + ('in', 'drop', 400)]: retval += """ <%s-ipv6 /> """ % (action, direction, @@ -799,6 +841,11 @@ class NWFilterFirewall(FirewallDriver): net = IPy.IP(cidr) return str(net.net()), str(net.netmask()) + def setup_security_groups_filtering(self, instance): + """Set up basic filtering (MAC, IP, and ARP spoofing protection) + as well as security groups filtering.""" + + @defer.inlineCallbacks def prepare_instance_filter(self, instance): """ @@ -807,37 +854,43 @@ class NWFilterFirewall(FirewallDriver): the base filter are all in place. """ - yield self._define_filter(self.nova_base_ipv4_filter) - yield self._define_filter(self.nova_base_ipv6_filter) - yield self._define_filter(self.nova_dhcp_filter) - yield self._define_filter(self.nova_base_filter) + yield self._ensure_static_filters() - nwfilter_xml = "\n" \ - " \n" % \ - instance['name'] + instance_filter_name = self._instance_filter_name(instance) + instance_secgroup_filter_name = '%s-secgroup' % (instance_filter_name,) + instance_filter_children = ['nova-base', instance_secgroup_filter_name] + instance_secgroup_filter_children = ['nova-base-ipv4', 'nova-base-ipv6', + 'nova-allow-dhcp-server'] if FLAGS.allow_project_net_traffic: network_ref = db.project_get_network(context.get_admin_context(), instance['project_id']) net, mask = self._get_net_and_mask(network_ref['cidr']) + project_filter = self.nova_project_filter(instance['project_id'], net, mask) yield self._define_filter(project_filter) - nwfilter_xml += " \n" % \ - instance['project_id'] + instance_secgroup_filter_children += [('nova-project-%s' % + instance['project_id'])] for security_group in instance.security_groups: yield self.refresh_security_group(security_group['id']) - nwfilter_xml += " \n" % \ - security_group['id'] - nwfilter_xml += "" + instance_secgroup_filter_children += [('nova-secgroup-%s' % + security_group['id'])] + + yield self._define_filter( + self._filter_container(instance_secgroup_filter_name, + instance_secgroup_filter_children)) + + yield self._define_filter( + self._filter_container(instance_filter_name, + instance_filter_children)) - yield self._define_filter(nwfilter_xml) return - def ensure_security_group_filter(self, security_group_id): + def refresh_security_group(self, security_group_id): return self._define_filter( self.security_group_to_nwfilter_xml(security_group_id)) @@ -868,3 +921,7 @@ class NWFilterFirewall(FirewallDriver): xml = "%s" % \ (security_group_id, rule_xml,) return xml + + def _instance_filter_name(self, instance): + return 'nova-instance-%s' % instance['name'] + -- cgit From cf21683d741165d2cf0798b7dc9968daa311fafc Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 6 Dec 2010 22:19:29 +0100 Subject: Add iptables based security groups implementation. --- nova/db/sqlalchemy/api.py | 20 ++++++ nova/network/linux_net.py | 2 + nova/tests/virt_unittest.py | 121 +++++++++++++++++++++++++++++--- nova/virt/libvirt_conn.py | 165 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 290 insertions(+), 18 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index afa55fc03..21b991548 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -574,12 +574,14 @@ def instance_get(context, instance_id, session=None): if is_admin_context(context): result = session.query(models.Instance).\ options(joinedload('security_groups')).\ + options(joinedload_all('security_groups.rules')).\ filter_by(id=instance_id).\ filter_by(deleted=can_read_deleted(context)).\ first() elif is_user_context(context): result = session.query(models.Instance).\ options(joinedload('security_groups')).\ + options(joinedload_all('security_groups.rules')).\ filter_by(project_id=context.project_id).\ filter_by(id=instance_id).\ filter_by(deleted=False).\ @@ -1505,6 +1507,24 @@ def security_group_rule_get(context, security_group_rule_id, session=None): return result +@require_context +def security_group_rule_get_by_security_group(context, security_group_id, session=None): + if not session: + session = get_session() + if is_admin_context(context): + result = session.query(models.SecurityGroupIngressRule).\ + filter_by(deleted=can_read_deleted(context)).\ + filter_by(parent_group_id=security_group_id).\ + all() + else: + # TODO(vish): Join to group and check for project_id + result = session.query(models.SecurityGroupIngressRule).\ + filter_by(deleted=False).\ + filter_by(parent_group_id=security_group_id).\ + all() + return result + + @require_context def security_group_rule_create(context, values): security_group_rule_ref = models.SecurityGroupIngressRule() diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 7b00e65d4..3803f886e 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -160,6 +160,8 @@ def ensure_bridge(bridge, interface, net_attrs=None): _execute("sudo ifconfig %s up" % bridge) _confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge) _confirm_rule("FORWARD", "--out-interface %s -j ACCEPT" % bridge) + _execute("sudo iptables -N nova-local", check_exit_code=False) + _confirm_rule("FORWARD", "-j nova-local") def get_dhcp_hosts(context, network_id): diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index 4bbf2b50b..6c0f379da 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -43,15 +43,14 @@ class LibvirtConnTestCase(test.TrialTestCase): def test_get_uri_and_template(self): ip = '10.11.12.13' - instance = {'internal_id': 1, - 'memory_kb': '1024000', - 'basepath': '/some/path', - 'bridge_name': 'br100', - 'mac_address': '02:12:34:46:56:67', - 'vcpus': 2, - 'project_id': 'fake', - 'bridge': 'br101', - 'instance_type': 'm1.small'} + instance = { 'memory_kb': '1024000', + 'basepath': '/some/path', + 'bridge_name': 'br100', + 'mac_address': '02:12:34:46:56:67', + 'vcpus': 2, + 'project_id': 'fake', + 'bridge': 'br101', + 'instance_type': 'm1.small'} user_context = context.RequestContext(project=self.project, user=self.user) @@ -123,6 +122,108 @@ class LibvirtConnTestCase(test.TrialTestCase): self.manager.delete_user(self.user) +class IptablesFirewallTestCase(test.TrialTestCase): + def setUp(self): + super(IptablesFirewallTestCase, self).setUp() + + self.manager = manager.AuthManager() + self.user = self.manager.create_user('fake', 'fake', 'fake', + admin=True) + 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() + + def tearDown(self): + self.manager.delete_project(self.project) + self.manager.delete_user(self.user) + super(IptablesFirewallTestCase, self).tearDown() + + def _p(self, *args, **kwargs): + if 'iptables-restore' in args: + print ' '.join(args), kwargs['stdin'] + if 'iptables-save' in args: + return + in_rules = ['# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010', + '*filter', + ':INPUT ACCEPT [969615:281627771]', + ':FORWARD ACCEPT [0:0]', + ':OUTPUT ACCEPT [915599:63811649]', + ':nova-block-ipv4 - [0:0]', + '-A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT ', + '-A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT ', + '-A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT ', + '-A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ', + '-A FORWARD -d 192.168.122.0/24 -o virbr0 -m state --state RELATED,ESTABLISHED -j ACCEPT ', + '-A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT ', + '-A FORWARD -i virbr0 -o virbr0 -j ACCEPT ', + '-A FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable ', + '-A FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable ', + 'COMMIT', + '# Completed on Mon Dec 6 11:54:13 2010'] + + def test_static_filters(self): + self.fw.execute = self._p + instance_ref = db.instance_create(self.context, + {'user_id': 'fake', + 'project_id': 'fake'}) + ip = '10.11.12.13' + + network_ref = self.network.get_network(self.context) + + fixed_ip = {'address': ip, + 'network_id': network_ref['id']} + + admin_ctxt = context.get_admin_context() + db.fixed_ip_create(admin_ctxt, fixed_ip) + db.fixed_ip_update(admin_ctxt, ip, {'allocated': True, + 'instance_id': instance_ref['id']}) + + + secgroup = db.security_group_create(admin_ctxt, + {'user_id': 'fake', + 'project_id': 'fake', + 'name': 'testgroup', + 'description': 'test group'}) + + db.security_group_rule_create(admin_ctxt, + {'parent_group_id': secgroup['id'], + 'protocol': 'tcp', + 'from_port': 80, + 'to_port': 81, + 'cidr': '192.168.10.0/24'}) + + db.instance_add_security_group(admin_ctxt, instance_ref['id'], + secgroup['id']) + instance_ref = db.instance_get(admin_ctxt, instance_ref['id']) + + self.fw.add_instance(instance_ref) + + out_rules = self.fw.modify_rules(self.in_rules) + + in_rules = filter(lambda l: not l.startswith('#'), self.in_rules) + for rule in in_rules: + if not 'nova' in rule: + self.assertTrue(rule in out_rules, 'Rule went missing: %s' % rule) + + print '\n'.join(out_rules) + + def est_stuff(self): + self.fw.execute = self._p + cloud_controller = cloud.CloudController() + cloud_controller.create_security_group(self.context, + 'testgroup', + 'test group description') + cloud_controller.authorize_security_group_ingress(self.context, + 'testgroup', + from_port='80', + to_port='81', + ip_protocol='tcp', + cidr_ip='0.0.0.0/0') + + self.fw._apply_ruleset() + + class NWFilterTestCase(test.TrialTestCase): def setUp(self): @@ -130,8 +231,6 @@ class NWFilterTestCase(test.TrialTestCase): class Mock(object): pass - #def __call__(self, *args, **kwargs): - # return self.manager = manager.AuthManager() self.user = self.manager.create_user('fake', 'fake', 'fake', diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index a0149c5ca..495ee020d 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -104,7 +104,7 @@ flags.DEFINE_string('libvirt_uri', flags.DEFINE_bool('allow_project_net_traffic', True, 'Whether to allow in project network traffic') -flags.DEFINE_bool('firewall_driver', None, +flags.DEFINE_string('firewall_driver', 'nova.virt.libvirt_conn.IptablesFirewallDriver', 'Firewall driver (defaults to nwfilter)') @@ -677,7 +677,7 @@ class FirewallDriver(object): """ raise NotImplementedError() - def refresh_security_group(security_group_id): + def refresh_security_group(self, security_group_id): """Refresh security group from data store Gets called when changes have been made to the security @@ -734,6 +734,7 @@ class NWFilterFirewall(FirewallDriver): def __init__(self, get_connection): self._libvirt_get_connection = get_connection self.static_filters_configured = False + self.handle_security_groups = False def _get_connection(self): return self._libvirt_get_connection() @@ -763,12 +764,14 @@ class NWFilterFirewall(FirewallDriver): def setup_basic_filtering(self, instance): """Set up basic filtering (MAC, IP, and ARP spoofing protection)""" + logging.info('called setup_basic_filtering in nwfilter') if self.handle_security_groups: # No point in setting up a filter set that we'll be overriding # anyway. return + logging.info('ensuring static filters') self._ensure_static_filters() instance_filter_name = self._instance_filter_name(instance) @@ -841,10 +844,6 @@ class NWFilterFirewall(FirewallDriver): net = IPy.IP(cidr) return str(net.net()), str(net.netmask()) - def setup_security_groups_filtering(self, instance): - """Set up basic filtering (MAC, IP, and ARP spoofing protection) - as well as security groups filtering.""" - @defer.inlineCallbacks def prepare_instance_filter(self, instance): @@ -874,7 +873,7 @@ class NWFilterFirewall(FirewallDriver): instance_secgroup_filter_children += [('nova-project-%s' % instance['project_id'])] - for security_group in instance.security_groups: + for security_group in db.security_group_get_by_instance(instance['id']): yield self.refresh_security_group(security_group['id']) instance_secgroup_filter_children += [('nova-secgroup-%s' % @@ -925,3 +924,155 @@ class NWFilterFirewall(FirewallDriver): def _instance_filter_name(self, instance): return 'nova-instance-%s' % instance['name'] + +class IptablesFirewallDriver(FirewallDriver): + def __init__(self, execute=None): + self.execute = execute or utils.execute + self.instances = set() + + def apply_instance_filter(self, instance): + """No-op. Everything is done in prepare_instance_filter""" + pass + + def remove_instance(self, instance): + self.instances.remove(instance) + + def add_instance(self, instance): + self.instances.add(instance) + + def prepare_instance_filter(self, instance): + self.add_instance(instance) + self.apply_ruleset() + + def apply_ruleset(self): + current_filter, _ = self.execute('sudo iptables-save -t filter') + current_lines = current_filter.split('\n') + new_filter = self.modify_rules(current_lines) + self.execute('sudo iptables-restore', + process_input='\n'.join(new_filter)) + + def modify_rules(self, current_lines): + ctxt = context.get_admin_context() + # Remove any trace of nova rules. + new_filter = filter(lambda l: 'nova-' not in l, current_lines) + + seen_chains = False + for rules_index in range(len(new_filter)): + if not seen_chains: + if new_filter[rules_index].startswith(':'): + seen_chains = True + elif seen_chains == 1: + if not new_filter[rules_index].startswith(':'): + break + + + our_chains = [':nova-ipv4-fallback - [0:0]'] + our_rules = ['-A nova-ipv4-fallback -j DROP'] + + our_chains += [':nova-local - [0:0]'] + our_rules += ['-A FORWARD -j nova-local'] + + security_groups = set() + # Add our chains + # First, we add instance chains and rules + for instance in self.instances: + chain_name = self._instance_chain_name(instance) + ip_address = self._ip_for_instance(instance) + + our_chains += [':%s - [0:0]' % chain_name] + + # Jump to the per-instance chain + our_rules += ['-A nova-local -d %s -j %s' % (ip_address, + chain_name)] + + # Always drop invalid packets + our_rules += ['-A %s -m state --state ' + 'INVALID -j DROP' % (chain_name,)] + + # Allow established connections + our_rules += ['-A %s -m state --state ' + 'ESTABLISHED,RELATED -j ACCEPT' % (chain_name,)] + + # Jump to each security group chain in turn + for security_group in \ + db.security_group_get_by_instance(ctxt, + instance['id']): + security_groups.add(security_group) + + sg_chain_name = self._security_group_chain_name(security_group) + + our_rules += ['-A %s -j %s' % (chain_name, sg_chain_name)] + + # Allow DHCP responses + dhcp_server = self._dhcp_server_for_instance(instance) + our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' % (chain_name, dhcp_server)] + + # If nothing matches, jump to the fallback chain + our_rules += ['-A %s -j nova-ipv4-fallback' % (chain_name,)] + + + # then, security group chains and rules + for security_group in security_groups: + chain_name = self._security_group_chain_name(security_group) + our_chains += [':%s - [0:0]' % chain_name] + + rules = \ + db.security_group_rule_get_by_security_group(ctxt, + security_group['id']) + + for rule in rules: + logging.info('%r', rule) + args = ['-A', chain_name, '-p', rule.protocol] + + if rule.cidr: + args += ['-s', rule.cidr] + else: + # Something about ipsets + pass + + 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: + args += ['-m', 'icmp', '--icmp_type', icmp_type_arg] + + args += ['-j ACCEPT'] + our_rules += [' '.join(args)] + + new_filter[rules_index:rules_index] = our_rules + new_filter[rules_index:rules_index] = our_chains + logging.info('new_filter: %s', '\n'.join(new_filter)) + return new_filter + + def refresh_security_group(self, security_group): + self.apply_ruleset() + + def _security_group_chain_name(self, security_group): + return 'nova-sg-%s' % (security_group['id'],) + + def _instance_chain_name(self, instance): + return 'nova-inst-%s' % (instance['id'],) + + def _ip_for_instance(self, instance): + return db.instance_get_fixed_address(context.get_admin_context(), + instance['id']) + + def _dhcp_server_for_instance(self, instance): + network = db.project_get_network(context.get_admin_context(), + instance['project_id']) + return network['gateway'] -- cgit From e1e4e639bf24dab49676f619fbb358c91cca3023 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 6 Dec 2010 22:20:05 +0100 Subject: Remove dead test code. --- nova/tests/virt_unittest.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index 6c0f379da..d725c2ce2 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -200,7 +200,7 @@ class IptablesFirewallTestCase(test.TrialTestCase): self.fw.add_instance(instance_ref) out_rules = self.fw.modify_rules(self.in_rules) - + in_rules = filter(lambda l: not l.startswith('#'), self.in_rules) for rule in in_rules: if not 'nova' in rule: @@ -208,21 +208,6 @@ class IptablesFirewallTestCase(test.TrialTestCase): print '\n'.join(out_rules) - def est_stuff(self): - self.fw.execute = self._p - cloud_controller = cloud.CloudController() - cloud_controller.create_security_group(self.context, - 'testgroup', - 'test group description') - cloud_controller.authorize_security_group_ingress(self.context, - 'testgroup', - from_port='80', - to_port='81', - ip_protocol='tcp', - cidr_ip='0.0.0.0/0') - - self.fw._apply_ruleset() - class NWFilterTestCase(test.TrialTestCase): -- cgit From 916f23e63add6167aef40931d6f564c685c6aefd Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Thu, 9 Dec 2010 14:15:38 +0100 Subject: Ignore security group rules that reference foreign security groups. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 495ee020d..2b5969ce1 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1028,7 +1028,7 @@ class IptablesFirewallDriver(FirewallDriver): args += ['-s', rule.cidr] else: # Something about ipsets - pass + continue if rule.protocol in ['udp', 'tcp']: if rule.from_port == rule.to_port: -- cgit From 8db57c605d59f492eaba68d134275a348c525640 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 13 Dec 2010 09:49:13 +0100 Subject: Elaborate a bit on ipsets comment. --- nova/virt/libvirt_conn.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 2b5969ce1..a123f7671 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -861,9 +861,10 @@ class NWFilterFirewall(FirewallDriver): instance_secgroup_filter_children = ['nova-base-ipv4', 'nova-base-ipv6', 'nova-allow-dhcp-server'] + ctxt = context.get_admin_context() + if FLAGS.allow_project_net_traffic: - network_ref = db.project_get_network(context.get_admin_context(), - instance['project_id']) + network_ref = db.project_get_network(ctxt, instance['project_id']) net, mask = self._get_net_and_mask(network_ref['cidr']) project_filter = self.nova_project_filter(instance['project_id'], @@ -873,7 +874,8 @@ class NWFilterFirewall(FirewallDriver): instance_secgroup_filter_children += [('nova-project-%s' % instance['project_id'])] - for security_group in db.security_group_get_by_instance(instance['id']): + for security_group in db.security_group_get_by_instance(ctxt, + instance['id']): yield self.refresh_security_group(security_group['id']) instance_secgroup_filter_children += [('nova-secgroup-%s' % @@ -1027,7 +1029,8 @@ class IptablesFirewallDriver(FirewallDriver): if rule.cidr: args += ['-s', rule.cidr] else: - # Something about ipsets + # Eventually, a mechanism to grant access for security + # groups will turn up here. It'll use ipsets. continue if rule.protocol in ['udp', 'tcp']: -- cgit From be9a3cd7e17edac4032c8ae554f75d725b0ad54a Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 13 Dec 2010 16:42:35 +0100 Subject: Move security group refresh logic into ComputeAPI. Add a trigger_security_group_members_refresh to ComputeAPI which finds the hosts that have instances that have security groups that reference a security group in which a new instance has just been placed, and sends a refresh_security_group_members to each of them. --- nova/api/ec2/cloud.py | 15 ++++-------- nova/compute/api.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++ nova/compute/manager.py | 16 ++++++++++--- nova/db/api.py | 7 ++++++ nova/db/sqlalchemy/api.py | 19 +++++++++++++++ nova/virt/libvirt_conn.py | 32 ++++++++++++++++++------- 6 files changed, 127 insertions(+), 23 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 05f8c3d0b..2694b8b00 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -130,15 +130,6 @@ class CloudController(object): result[key] = [line] return result - def _trigger_refresh_security_group(self, context, security_group): - nodes = set([instance['host'] for instance in security_group.instances - if instance['host'] is not None]) - for node in nodes: - rpc.cast(context, - '%s.%s' % (FLAGS.compute_topic, node), - {"method": "refresh_security_group", - "args": {"security_group_id": security_group.id}}) - def get_metadata(self, address): ctxt = context.get_admin_context() instance_ref = db.fixed_ip_get_instance(ctxt, address) @@ -369,7 +360,8 @@ class CloudController(object): match = False if match: db.security_group_rule_destroy(context, rule['id']) - self._trigger_refresh_security_group(context, security_group) + self.compute_api.trigger_security_group_rules_refresh(context, + security_group['id']) return True raise exception.ApiError("No rule for the specified parameters.") @@ -392,7 +384,8 @@ class CloudController(object): security_group_rule = db.security_group_rule_create(context, values) - self._trigger_refresh_security_group(context, security_group) + self.compute_api.trigger_security_group_rules_refresh(context, + security_group['id']) return True diff --git a/nova/compute/api.py b/nova/compute/api.py index 8e0efa4cc..27010d513 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -24,6 +24,7 @@ import datetime import logging import time +from nova import context from nova import db from nova import exception from nova import flags @@ -165,6 +166,10 @@ class ComputeAPI(base.Base): "args": {"topic": FLAGS.compute_topic, "instance_id": instance_id}}) + + for group_id in security_groups: + self.trigger_security_group_members_refresh(elevated, group_id) + return instances def ensure_default_security_group(self, context): @@ -184,6 +189,62 @@ class ComputeAPI(base.Base): 'project_id': context.project_id} db.security_group_create(context, values) + + def trigger_security_group_rules_refresh(self, context, security_group_id): + """Called when a rule is added to or removed from a security_group""" + + security_group = db.security_group_get(context, security_group_id) + + hosts = set() + for instance in security_group['instances']: + if instance['host'] is not None: + hosts.add(instance['host']) + + for host in hosts: + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "refresh_security_group", + "args": {"security_group_id": security_group.id}}) + + + def trigger_security_group_members_refresh(self, context, group_id): + """Called when a security group gains a new or loses a member + + Sends an update request to each compute node for whom this is + relevant.""" + + # First, we get the security group rules that reference this group as + # the grantee.. + security_group_rules = \ + db.security_group_rule_get_by_security_group_grantee(context, + group_id) + + # ..then we distill the security groups to which they belong.. + security_groups = set() + for rule in security_group_rules: + security_groups.add(rule['parent_group_id']) + + # ..then we find the instances that are members of these groups.. + instances = set() + for security_group in security_groups: + for instance in security_group['instances']: + instances.add(instance['id']) + + # ...then we find the hosts where they live... + hosts = set() + for instance in instances: + if instance['host']: + hosts.add(instance['host']) + + # ...and finally we tell these nodes to refresh their view of this + # particular security group. + for host in hosts: + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "refresh_security_group_members", + "args": {"security_group_id": group_id}}) + + def update_instance(self, context, instance_id, **kwargs): """Updates the instance in the datastore. diff --git a/nova/compute/manager.py b/nova/compute/manager.py index dd8d41129..ee449c819 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -80,9 +80,19 @@ class ComputeManager(manager.Manager): @defer.inlineCallbacks @exception.wrap_exception - def refresh_security_group(self, context, security_group_id, **_kwargs): - """This call passes stright through to the virtualization driver.""" - yield self.driver.refresh_security_group(security_group_id) + def refresh_security_group_rules(self, context, + security_group_id, **_kwargs): + """This call passes straight through to the virtualization driver.""" + yield self.driver.refresh_security_group_rules(security_group_id) + + + @defer.inlineCallbacks + @exception.wrap_exception + def refresh_security_group_members(self, context, + security_group_id, **_kwargs): + """This call passes straight through to the virtualization driver.""" + yield self.driver.refresh_security_group_members(security_group_id) + @defer.inlineCallbacks @exception.wrap_exception diff --git a/nova/db/api.py b/nova/db/api.py index 8f9dc2443..6fa80c247 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -711,6 +711,13 @@ def security_group_rule_get_by_security_group(context, security_group_id): security_group_id) +def security_group_rule_get_by_security_group_grantee(context, + security_group_id): + """Get all rules that grant access to the given security group.""" + return IMPL.security_group_rule_get_by_security_group_grantee(context, + security_group_id) + + def security_group_rule_destroy(context, security_group_rule_id): """Deletes a security group rule.""" return IMPL.security_group_rule_destroy(context, security_group_rule_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 5214dd62b..deb248f82 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1532,6 +1532,25 @@ def security_group_rule_get_by_security_group(context, security_group_id, sessio return result +@require_context +def security_group_rule_get_by_security_group_grantee(context, + security_group_id, + session=None): + if not session: + session = get_session() + if is_admin_context(context): + result = session.query(models.SecurityGroupIngressRule).\ + filter_by(deleted=can_read_deleted(context)).\ + filter_by(group_id=security_group_id).\ + all() + else: + result = session.query(models.SecurityGroupIngressRule).\ + filter_by(deleted=False).\ + filter_by(group_id=security_group_id).\ + all() + return result + + @require_context def security_group_rule_create(context, values): security_group_rule_ref = models.SecurityGroupIngressRule() diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index a123f7671..da566c33b 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -656,8 +656,11 @@ class LibvirtConnection(object): domain = self._conn.lookupByName(instance_name) return domain.interfaceStats(interface) - def refresh_security_group(self, security_group_id): - self.firewall_driver.refresh_security_group(security_group_id) + def refresh_security_group_rules(self, security_group_id): + self.firewall_driver.refresh_security_group_rules(security_group_id) + + def refresh_security_group_members(self, security_group_id): + self.firewall_driver.refresh_security_group_members(security_group_id) class FirewallDriver(object): @@ -677,11 +680,19 @@ class FirewallDriver(object): """ raise NotImplementedError() - def refresh_security_group(self, security_group_id): - """Refresh security group from data store + def refresh_security_group_rules(self, security_group_id): + """Refresh security group rules from data store - Gets called when changes have been made to the security - group.""" + Gets called when a rule has been added to or removed from + the security group.""" + raise NotImplementedError() + + + def refresh_security_group_members(self, security_group_id): + """Refresh security group members from data store + + Gets called when an instance gets added to or removed from + the security group.""" raise NotImplementedError() @@ -876,7 +887,7 @@ class NWFilterFirewall(FirewallDriver): for security_group in db.security_group_get_by_instance(ctxt, instance['id']): - yield self.refresh_security_group(security_group['id']) + yield self.refresh_security_group_rules(security_group['id']) instance_secgroup_filter_children += [('nova-secgroup-%s' % security_group['id'])] @@ -891,7 +902,7 @@ class NWFilterFirewall(FirewallDriver): return - def refresh_security_group(self, security_group_id): + def refresh_security_group_rules(self, security_group_id): return self._define_filter( self.security_group_to_nwfilter_xml(security_group_id)) @@ -1062,7 +1073,10 @@ class IptablesFirewallDriver(FirewallDriver): logging.info('new_filter: %s', '\n'.join(new_filter)) return new_filter - def refresh_security_group(self, security_group): + def refresh_security_group_members(self, security_group): + pass + + def refresh_security_group_rules(self, security_group): self.apply_ruleset() def _security_group_chain_name(self, security_group): -- cgit From 1539df7429a235ba2fefe3f65422fe94b248ac08 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 15 Dec 2010 14:03:19 +0100 Subject: refresh_security_group renamed to refresh_security_group_rules --- 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 27010d513..686c1eb0a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -203,7 +203,7 @@ class ComputeAPI(base.Base): for host in hosts: rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "refresh_security_group", + {"method": "refresh_security_group_rules", "args": {"security_group_id": security_group.id}}) -- cgit From b420a3daa5f1b827f49e5d6557aaa0f8d396b81b Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 15 Dec 2010 14:04:06 +0100 Subject: Lots of PEP-8 work. --- nova/api/ec2/cloud.py | 4 +-- nova/compute/api.py | 8 ++---- nova/compute/manager.py | 2 -- nova/db/api.py | 2 +- nova/db/sqlalchemy/api.py | 3 +- nova/tests/virt_unittest.py | 67 ++++++++++++++++++++++++--------------------- nova/virt/libvirt_conn.py | 29 ++++++++++---------- 7 files changed, 57 insertions(+), 58 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 74be6d05b..018139634 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -361,7 +361,7 @@ class CloudController(object): if match: db.security_group_rule_destroy(context, rule['id']) self.compute_api.trigger_security_group_rules_refresh(context, - security_group['id']) + security_group['id']) return True raise exception.ApiError("No rule for the specified parameters.") @@ -385,7 +385,7 @@ class CloudController(object): security_group_rule = db.security_group_rule_create(context, values) self.compute_api.trigger_security_group_rules_refresh(context, - security_group['id']) + security_group['id']) return True diff --git a/nova/compute/api.py b/nova/compute/api.py index 686c1eb0a..7c91792e3 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -166,7 +166,6 @@ class ComputeAPI(base.Base): "args": {"topic": FLAGS.compute_topic, "instance_id": instance_id}}) - for group_id in security_groups: self.trigger_security_group_members_refresh(elevated, group_id) @@ -189,7 +188,6 @@ class ComputeAPI(base.Base): 'project_id': context.project_id} db.security_group_create(context, values) - def trigger_security_group_rules_refresh(self, context, security_group_id): """Called when a rule is added to or removed from a security_group""" @@ -206,10 +204,9 @@ class ComputeAPI(base.Base): {"method": "refresh_security_group_rules", "args": {"security_group_id": security_group.id}}) - def trigger_security_group_members_refresh(self, context, group_id): """Called when a security group gains a new or loses a member - + Sends an update request to each compute node for whom this is relevant.""" @@ -223,7 +220,7 @@ class ComputeAPI(base.Base): security_groups = set() for rule in security_group_rules: security_groups.add(rule['parent_group_id']) - + # ..then we find the instances that are members of these groups.. instances = set() for security_group in security_groups: @@ -244,7 +241,6 @@ class ComputeAPI(base.Base): {"method": "refresh_security_group_members", "args": {"security_group_id": group_id}}) - def update_instance(self, context, instance_id, **kwargs): """Updates the instance in the datastore. diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ee449c819..f039bca2e 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -85,7 +85,6 @@ class ComputeManager(manager.Manager): """This call passes straight through to the virtualization driver.""" yield self.driver.refresh_security_group_rules(security_group_id) - @defer.inlineCallbacks @exception.wrap_exception def refresh_security_group_members(self, context, @@ -93,7 +92,6 @@ class ComputeManager(manager.Manager): """This call passes straight through to the virtualization driver.""" yield self.driver.refresh_security_group_members(security_group_id) - @defer.inlineCallbacks @exception.wrap_exception def run_instance(self, context, instance_id, **_kwargs): diff --git a/nova/db/api.py b/nova/db/api.py index 6fa80c247..67796c246 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -715,7 +715,7 @@ def security_group_rule_get_by_security_group_grantee(context, security_group_id): """Get all rules that grant access to the given security group.""" return IMPL.security_group_rule_get_by_security_group_grantee(context, - security_group_id) + security_group_id) def security_group_rule_destroy(context, security_group_rule_id): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index deb248f82..4e3ef5771 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1515,7 +1515,8 @@ def security_group_rule_get(context, security_group_rule_id, session=None): @require_context -def security_group_rule_get_by_security_group(context, security_group_id, session=None): +def security_group_rule_get_by_security_group(context, security_group_id, + session=None): if not session: session = get_session() if is_admin_context(context): diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index d725c2ce2..1d6241fba 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -43,14 +43,14 @@ class LibvirtConnTestCase(test.TrialTestCase): def test_get_uri_and_template(self): ip = '10.11.12.13' - instance = { 'memory_kb': '1024000', - 'basepath': '/some/path', - 'bridge_name': 'br100', - 'mac_address': '02:12:34:46:56:67', - 'vcpus': 2, - 'project_id': 'fake', - 'bridge': 'br101', - 'instance_type': 'm1.small'} + instance = {'memory_kb': '1024000', + 'basepath': '/some/path', + 'bridge_name': 'br100', + 'mac_address': '02:12:34:46:56:67', + 'vcpus': 2, + 'project_id': 'fake', + 'bridge': 'br101', + 'instance_type': 'm1.small'} user_context = context.RequestContext(project=self.project, user=self.user) @@ -125,7 +125,7 @@ class LibvirtConnTestCase(test.TrialTestCase): class IptablesFirewallTestCase(test.TrialTestCase): def setUp(self): super(IptablesFirewallTestCase, self).setUp() - + self.manager = manager.AuthManager() self.user = self.manager.create_user('fake', 'fake', 'fake', admin=True) @@ -141,26 +141,30 @@ class IptablesFirewallTestCase(test.TrialTestCase): def _p(self, *args, **kwargs): if 'iptables-restore' in args: - print ' '.join(args), kwargs['stdin'] + print ' '.join(args), kwargs['stdin'] if 'iptables-save' in args: - return - in_rules = ['# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010', - '*filter', - ':INPUT ACCEPT [969615:281627771]', - ':FORWARD ACCEPT [0:0]', - ':OUTPUT ACCEPT [915599:63811649]', - ':nova-block-ipv4 - [0:0]', - '-A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT ', - '-A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT ', - '-A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT ', - '-A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ', - '-A FORWARD -d 192.168.122.0/24 -o virbr0 -m state --state RELATED,ESTABLISHED -j ACCEPT ', - '-A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT ', - '-A FORWARD -i virbr0 -o virbr0 -j ACCEPT ', - '-A FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable ', - '-A FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable ', - 'COMMIT', - '# Completed on Mon Dec 6 11:54:13 2010'] + return + + in_rules = [ + '# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010', + '*filter', + ':INPUT ACCEPT [969615:281627771]', + ':FORWARD ACCEPT [0:0]', + ':OUTPUT ACCEPT [915599:63811649]', + ':nova-block-ipv4 - [0:0]', + '-A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT ', + '-A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT ', + '-A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT ', + '-A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ', + '-A FORWARD -d 192.168.122.0/24 -o virbr0 -m state --state RELATED' + ',ESTABLISHED -j ACCEPT ', + '-A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT ', + '-A FORWARD -i virbr0 -o virbr0 -j ACCEPT ', + '-A FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable ', + '-A FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable ', + 'COMMIT', + '# Completed on Mon Dec 6 11:54:13 2010' + ] def test_static_filters(self): self.fw.execute = self._p @@ -179,7 +183,6 @@ class IptablesFirewallTestCase(test.TrialTestCase): db.fixed_ip_update(admin_ctxt, ip, {'allocated': True, 'instance_id': instance_ref['id']}) - secgroup = db.security_group_create(admin_ctxt, {'user_id': 'fake', 'project_id': 'fake', @@ -204,7 +207,8 @@ class IptablesFirewallTestCase(test.TrialTestCase): in_rules = filter(lambda l: not l.startswith('#'), self.in_rules) for rule in in_rules: if not 'nova' in rule: - self.assertTrue(rule in out_rules, 'Rule went missing: %s' % rule) + self.assertTrue(rule in out_rules, + 'Rule went missing: %s' % rule) print '\n'.join(out_rules) @@ -225,7 +229,8 @@ class NWFilterTestCase(test.TrialTestCase): self.fake_libvirt_connection = Mock() - self.fw = libvirt_conn.NWFilterFirewall(lambda:self.fake_libvirt_connection) + self.fw = libvirt_conn.NWFilterFirewall( + 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 da566c33b..e55638224 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -104,8 +104,9 @@ flags.DEFINE_string('libvirt_uri', flags.DEFINE_bool('allow_project_net_traffic', True, 'Whether to allow in project network traffic') -flags.DEFINE_string('firewall_driver', 'nova.virt.libvirt_conn.IptablesFirewallDriver', - 'Firewall driver (defaults to nwfilter)') +flags.DEFINE_string('firewall_driver', + 'nova.virt.libvirt_conn.IptablesFirewallDriver', + 'Firewall driver (defaults to nwfilter)') def get_connection(read_only): @@ -687,7 +688,6 @@ class FirewallDriver(object): the security group.""" raise NotImplementedError() - def refresh_security_group_members(self, security_group_id): """Refresh security group members from data store @@ -855,7 +855,6 @@ class NWFilterFirewall(FirewallDriver): net = IPy.IP(cidr) return str(net.net()), str(net.netmask()) - @defer.inlineCallbacks def prepare_instance_filter(self, instance): """ @@ -869,8 +868,9 @@ class NWFilterFirewall(FirewallDriver): instance_filter_name = self._instance_filter_name(instance) instance_secgroup_filter_name = '%s-secgroup' % (instance_filter_name,) instance_filter_children = ['nova-base', instance_secgroup_filter_name] - instance_secgroup_filter_children = ['nova-base-ipv4', 'nova-base-ipv6', - 'nova-allow-dhcp-server'] + instance_secgroup_filter_children = ['nova-base-ipv4', + 'nova-base-ipv6', + 'nova-allow-dhcp-server'] ctxt = context.get_admin_context() @@ -883,14 +883,14 @@ class NWFilterFirewall(FirewallDriver): yield self._define_filter(project_filter) instance_secgroup_filter_children += [('nova-project-%s' % - instance['project_id'])] + instance['project_id'])] for security_group in db.security_group_get_by_instance(ctxt, - instance['id']): + instance['id']): yield self.refresh_security_group_rules(security_group['id']) instance_secgroup_filter_children += [('nova-secgroup-%s' % - security_group['id'])] + security_group['id'])] yield self._define_filter( self._filter_container(instance_secgroup_filter_name, @@ -978,12 +978,11 @@ class IptablesFirewallDriver(FirewallDriver): if not new_filter[rules_index].startswith(':'): break - our_chains = [':nova-ipv4-fallback - [0:0]'] - our_rules = ['-A nova-ipv4-fallback -j DROP'] + our_rules = ['-A nova-ipv4-fallback -j DROP'] our_chains += [':nova-local - [0:0]'] - our_rules += ['-A FORWARD -j nova-local'] + our_rules += ['-A FORWARD -j nova-local'] security_groups = set() # Add our chains @@ -1018,12 +1017,12 @@ class IptablesFirewallDriver(FirewallDriver): # Allow DHCP responses dhcp_server = self._dhcp_server_for_instance(instance) - our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' % (chain_name, dhcp_server)] + our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' % + (chain_name, dhcp_server)] # If nothing matches, jump to the fallback chain our_rules += ['-A %s -j nova-ipv4-fallback' % (chain_name,)] - # then, security group chains and rules for security_group in security_groups: chain_name = self._security_group_chain_name(security_group) @@ -1031,7 +1030,7 @@ class IptablesFirewallDriver(FirewallDriver): rules = \ db.security_group_rule_get_by_security_group(ctxt, - security_group['id']) + security_group['id']) for rule in rules: logging.info('%r', rule) -- cgit From 2b26cbfd8dc5f03026dfb03eef9cd3a443edab86 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 3 Jan 2011 11:39:02 +0100 Subject: Fix a merge artifact. --- nova/virt/libvirt_conn.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 30ac11bdd..492353793 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -927,18 +927,11 @@ class NWFilterFirewall(FirewallDriver): ctxt = context.get_admin_context() if FLAGS.allow_project_net_traffic: - network_ref = db.project_get_network(ctxt, instance['project_id']) - net, mask = _get_net_and_mask(network_ref['cidr']) - - project_filter = self.nova_project_filter(instance['project_id'], - net, mask) - self._define_filter(project_filter) - - instance_secgroup_filter_children += [('nova-project-%s' % - instance['project_id'])] + instance_filter_children += ['nova-project'] for security_group in db.security_group_get_by_instance(ctxt, instance['id']): + self.refresh_security_group_rules(security_group['id']) instance_secgroup_filter_children += [('nova-secgroup-%s' % -- cgit From 4102913e33093e984aa5cbaae6666bb4c6d4312b Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 3 Jan 2011 11:39:31 +0100 Subject: Adjust test suite to the split between base firewall rules provided by nwfilter and the security group filtering. --- nova/tests/test_virt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 0e2644eff..2f418bd5d 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -259,7 +259,8 @@ class IptablesFirewallTestCase(test.TestCase): 'project_id': 'fake'}) ip = '10.11.12.13' - network_ref = self.network.get_network(self.context) + network_ref = db.project_get_network(self.context, + 'fake') fixed_ip = {'address': ip, 'network_id': network_ref['id']} @@ -428,6 +429,7 @@ class NWFilterTestCase(test.TestCase): self.security_group.id) instance = db.instance_get(self.context, inst_id) + self.fw.setup_basic_filtering(instance) self.fw.prepare_instance_filter(instance) _ensure_all_called() self.teardown_security_group() -- cgit From 2281c6b6b27777a7c9bfa75acf7679dd76fcfb4d Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 3 Jan 2011 13:06:14 +0100 Subject: Stub out init_host in libvirt driver. --- nova/virt/libvirt_conn.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 492353793..b65041caa 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -135,6 +135,9 @@ class LibvirtConnection(object): else: self.firewall_driver = utils.import_object(FLAGS.firewall_driver) + def init_host(self): + pass + def _get_connection(self): if not self._wrapped_conn or not self._test_connection(): logging.debug(_('Connecting to libvirt: %s') % self.libvirt_uri) -- cgit From bb14565d4a21084b54a4fad3c395b31b88f41680 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 3 Jan 2011 13:16:02 +0100 Subject: Move a closing bracket. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index b65041caa..e1ab2aca7 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -366,7 +366,7 @@ class LibvirtConnection(object): self.firewall_driver.prepare_instance_filter(instance) self._create_image(instance, xml) self._conn.createXML(xml, 0) - logging.debug(_("instance %s: is running", instance['name'])) + logging.debug(_("instance %s: is running"), instance['name']) self.firewall_driver.apply_instance_filter(instance) timer = utils.LoopingCall(f=None) -- cgit From 804169715c020e4c2387a1bb8aa565547c4a6a42 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 3 Jan 2011 13:54:54 +0100 Subject: Don't lie about which is the default firewall implementation. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index e1ab2aca7..8a89b162b 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -87,7 +87,7 @@ flags.DEFINE_bool('allow_project_net_traffic', 'Whether to allow in project network traffic') flags.DEFINE_string('firewall_driver', 'nova.virt.libvirt_conn.IptablesFirewallDriver', - 'Firewall driver (defaults to nwfilter)') + 'Firewall driver (defaults to iptables)') def get_connection(read_only): -- cgit From d1118830c01267082c1371ef2faad1057e7a811e Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 3 Jan 2011 13:55:44 +0100 Subject: Stop returning generators in the refresh_security_group_{rules,members} methods. --- nova/compute/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 6615ad65b..235237091 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -103,13 +103,13 @@ class ComputeManager(manager.Manager): def refresh_security_group_rules(self, context, security_group_id, **_kwargs): """This call passes straight through to the virtualization driver.""" - yield self.driver.refresh_security_group_rules(security_group_id) + return self.driver.refresh_security_group_rules(security_group_id) @exception.wrap_exception def refresh_security_group_members(self, context, security_group_id, **_kwargs): """This call passes straight through to the virtualization driver.""" - yield self.driver.refresh_security_group_members(security_group_id) + return self.driver.refresh_security_group_members(security_group_id) @exception.wrap_exception def run_instance(self, context, instance_id, **_kwargs): -- cgit From 13b1374897c59c6e59fe5542ab71b0180aa6fc00 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 6 Jan 2011 15:08:26 -0500 Subject: Track version info, and make available for logging. --- nova/log.py | 14 ++++---------- setup.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/nova/log.py b/nova/log.py index 97472b20b..aafc2e602 100644 --- a/nova/log.py +++ b/nova/log.py @@ -34,24 +34,19 @@ import logging.handlers import traceback from nova import flags -# TODO(todd): fix after version.py merge -# from nova import version +from nova import version FLAGS = flags.FLAGS -# TODO(todd): fix after version.py merge -# '(%(name)s %(nova_version)s): %(levelname)s ' flags.DEFINE_string('logging_context_format_string', - '(%(name)s): %(levelname)s ' + '(%(name)s %(nova_version)s): %(levelname)s ' '[%(request_id)s %(user)s ' '%(project)s] %(message)s', 'format string to use for log messages') -# TODO(todd): fix after version.py merge -# '(%(name)s %(nova_version)s): %(levelname)s [N/A] ' flags.DEFINE_string('logging_default_format_string', - '(%(name)s): %(levelname)s [N/A] ' + '(%(name)s %(nova_version)s): %(levelname)s [N/A] ' '%(message)s', 'format string to use for log messages') @@ -163,8 +158,7 @@ class NovaLogger(logging.Logger): extra = {} if context: extra.update(_dictify_context(context)) - # TODO(todd): fix after version.py merge - #extra.update({"nova_version": version.string_with_vcs()}) + extra.update({"nova_version": version.string_with_vcs()}) logging.Logger._log(self, level, msg, args, exc_info, extra) def addHandler(self, handler): diff --git a/setup.py b/setup.py index e00911099..6d7cecb70 100644 --- a/setup.py +++ b/setup.py @@ -25,6 +25,45 @@ from sphinx.setup_command import BuildDoc from nova.utils import parse_mailmap, str_dict_replace +NOVA_VERSION = ['2011', '1'] + +VERSIONFILE_DEFAULT_VCS_VERSION = """ +version_info = {"branch_nick": "LOCALBRANCH", "revision_id": "LOCALREVISION"} +""" + +VERSIONFILE_DATA = """ +# below this line automatically generated by setup.py + +YEAR = %r +COUNT = %r +""" % (NOVA_VERSION[0], NOVA_VERSION[1]) + + +VERSIONFILE_DATA += """ + +def string(): + return '.'.join([YEAR, COUNT]) + + +def vcs_version_string(): + return "%s:%s" % (version_info['branch_nick'], version_info['revision_id']) + + +def string_with_vcs(): + return "%s-%s" % (string(), vcs_version_string()) +""" + +with open("nova/version.py", 'w') as version_file: + if os.path.isdir('.bzr'): + vcs_cmd = subprocess.Popen(["bzr", "version-info", "--python"], + stdout=subprocess.PIPE) + vcsversion = vcs_cmd.communicate()[0] + version_file.write(vcsversion) + else: + version_file.write(VERSIONFILE_DEFAULT_VCS_VERSION) + version_file.write(VERSIONFILE_DATA) + + class local_BuildDoc(BuildDoc): def run(self): for builder in ['html', 'man']: -- cgit From f3ea4d876fe0d62dcf63cfdcaf7657949cc4dbcf Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 6 Jan 2011 15:20:04 -0500 Subject: Add default version file for developers. --- nova/version.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 nova/version.py diff --git a/nova/version.py b/nova/version.py new file mode 100644 index 000000000..fc14b8401 --- /dev/null +++ b/nova/version.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +"""This file is automatically generated by generate_version_info +It uses the current working tree to determine the revision. +So don't edit it. :) +""" + +version_info = {'branch_nick': u'LOCALBRANCH', 'revision_id': 'LOCALREVISION', + 'revno': 0} + +revisions = {} + +file_revisions = {} + + +if __name__ == '__main__': + print 'revision: %(revno)d' % version_info + print 'nick: %(branch_nick)s' % version_info + print 'revision id: %(revision_id)s' % version_info + +# below this line automatically generated by setup.py + +YEAR = '2011' +COUNT = '1-dev' + + +def string(): + return '.'.join([YEAR, COUNT]) + + +def vcs_version_string(): + return "%s:%s" % (version_info['branch_nick'], version_info['revision_id']) + + +def string_with_vcs(): + return "%s-%s" % (string(), vcs_version_string()) -- cgit From 8cdfdd14a03e1356cda4fcbdfbcc528bc7f397bd Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 6 Jan 2011 15:29:38 -0500 Subject: Let documentation get version from nova/version.py as well. --- doc/source/conf.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 8f1b370cc..61b3749d0 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -60,10 +60,12 @@ copyright = u'2010, United States Government as represented by the Administrator # |version| and |release|, also used in various other places throughout the # built documents. # -# The short X.Y version. -version = '2011.1' +import re +from nova import version as nova_version # The full version, including alpha/beta/rc tags. -release = '2011.1-prerelease' +release = nova_version.string() +# The short X.Y version. +version = re.sub(r'-dev$', '', nova_version.string()) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -- cgit From 19ffc1275814a6c00f6ff19dd0c03060143d097a Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 7 Jan 2011 12:08:22 +0100 Subject: Remove redundant import of nova.context. Use db instance attribute rather than module directly. --- nova/compute/api.py | 6 +++--- nova/db/sqlalchemy/api.py | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 2c2937f48..0d04d344c 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -24,7 +24,6 @@ import datetime import logging import time -from nova import context from nova import db from nova import exception from nova import flags @@ -210,7 +209,7 @@ class API(base.Base): def trigger_security_group_rules_refresh(self, context, security_group_id): """Called when a rule is added to or removed from a security_group""" - security_group = db.security_group_get(context, security_group_id) + security_group = self.db.security_group_get(context, security_group_id) hosts = set() for instance in security_group['instances']: @@ -232,7 +231,8 @@ class API(base.Base): # First, we get the security group rules that reference this group as # the grantee.. security_group_rules = \ - db.security_group_rule_get_by_security_group_grantee(context, + self.db.security_group_rule_get_by_security_group_grantee( + context, group_id) # ..then we distill the security groups to which they belong.. diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 14ccc989f..eb87355b6 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -650,7 +650,6 @@ def instance_get(context, instance_id, session=None): if is_admin_context(context): result = session.query(models.Instance).\ options(joinedload_all('fixed_ip.floating_ips')).\ - options(joinedload('security_groups')).\ options(joinedload_all('security_groups.rules')).\ options(joinedload('volumes')).\ filter_by(id=instance_id).\ @@ -659,7 +658,6 @@ def instance_get(context, instance_id, session=None): elif is_user_context(context): result = session.query(models.Instance).\ options(joinedload_all('fixed_ip.floating_ips')).\ - options(joinedload('security_groups')).\ options(joinedload_all('security_groups.rules')).\ options(joinedload('volumes')).\ filter_by(project_id=context.project_id).\ -- cgit From 8b3925e4d4b97dc28bfc903483ec4793fb38fed5 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 7 Jan 2011 15:17:03 +0100 Subject: Less code generation. --- .bzrignore | 1 + doc/source/conf.py | 5 ++--- nova/version.py | 40 ++++++++++++++++++---------------------- setup.py | 38 ++++---------------------------------- 4 files changed, 25 insertions(+), 59 deletions(-) diff --git a/.bzrignore b/.bzrignore index d81a7d829..b271561a3 100644 --- a/.bzrignore +++ b/.bzrignore @@ -12,3 +12,4 @@ CA/openssl.cnf CA/serial* CA/newcerts/*.pem CA/private/cakey.pem +nova/vcsversion.py diff --git a/doc/source/conf.py b/doc/source/conf.py index 61b3749d0..20e9c88af 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -60,12 +60,11 @@ copyright = u'2010, United States Government as represented by the Administrator # |version| and |release|, also used in various other places throughout the # built documents. # -import re from nova import version as nova_version # The full version, including alpha/beta/rc tags. -release = nova_version.string() +release = nova_version.version() # The short X.Y version. -version = re.sub(r'-dev$', '', nova_version.string()) +version = nova_version.canonical_version() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/nova/version.py b/nova/version.py index fc14b8401..1504a5c82 100644 --- a/nova/version.py +++ b/nova/version.py @@ -1,35 +1,31 @@ #!/usr/bin/env python -"""This file is automatically generated by generate_version_info -It uses the current working tree to determine the revision. -So don't edit it. :) -""" +try: + from nova.vcsversion import version_info +except ImportError: + version_info = {'branch_nick': u'LOCALBRANCH', + 'revision_id': 'LOCALREVISION', + 'revno': 0} -version_info = {'branch_nick': u'LOCALBRANCH', 'revision_id': 'LOCALREVISION', - 'revno': 0} +NOVA_VERSION = ['2011', '1'] +YEAR, COUNT = NOVA_VERSION -revisions = {} +FINAL = False # This becomes true at Release Candidate time -file_revisions = {} - -if __name__ == '__main__': - print 'revision: %(revno)d' % version_info - print 'nick: %(branch_nick)s' % version_info - print 'revision id: %(revision_id)s' % version_info - -# below this line automatically generated by setup.py - -YEAR = '2011' -COUNT = '1-dev' +def canonical_version_string(): + return '.'.join([YEAR, COUNT]) -def string(): - return '.'.join([YEAR, COUNT]) +def version_string(): + if FINAL: + return canonical_version_string() + else: + return '%s-dev' % (canonical_version_string(),) def vcs_version_string(): return "%s:%s" % (version_info['branch_nick'], version_info['revision_id']) -def string_with_vcs(): - return "%s-%s" % (string(), vcs_version_string()) +def version_string_with_vcs(): + return "%s-%s" % (canonical_version_string(), vcs_version_string()) diff --git a/setup.py b/setup.py index 6d7cecb70..2ccece789 100644 --- a/setup.py +++ b/setup.py @@ -24,44 +24,14 @@ from setuptools.command.sdist import sdist from sphinx.setup_command import BuildDoc from nova.utils import parse_mailmap, str_dict_replace +from nova import version -NOVA_VERSION = ['2011', '1'] - -VERSIONFILE_DEFAULT_VCS_VERSION = """ -version_info = {"branch_nick": "LOCALBRANCH", "revision_id": "LOCALREVISION"} -""" - -VERSIONFILE_DATA = """ -# below this line automatically generated by setup.py - -YEAR = %r -COUNT = %r -""" % (NOVA_VERSION[0], NOVA_VERSION[1]) - - -VERSIONFILE_DATA += """ - -def string(): - return '.'.join([YEAR, COUNT]) - - -def vcs_version_string(): - return "%s:%s" % (version_info['branch_nick'], version_info['revision_id']) - - -def string_with_vcs(): - return "%s-%s" % (string(), vcs_version_string()) -""" - -with open("nova/version.py", 'w') as version_file: - if os.path.isdir('.bzr'): +if os.path.isdir('.bzr'): + with open("nova/vcsversion.py", 'w') as version_file: vcs_cmd = subprocess.Popen(["bzr", "version-info", "--python"], stdout=subprocess.PIPE) vcsversion = vcs_cmd.communicate()[0] version_file.write(vcsversion) - else: - version_file.write(VERSIONFILE_DEFAULT_VCS_VERSION) - version_file.write(VERSIONFILE_DATA) class local_BuildDoc(BuildDoc): @@ -88,7 +58,7 @@ class local_sdist(sdist): sdist.run(self) setup(name='nova', - version='2011.1', + version=version.canonical_version(), description='cloud computing fabric controller', author='OpenStack', author_email='nova@lists.launchpad.net', -- cgit From 509c3b02f171d47ff9bc8cbbb3f0ac7cd1e888b3 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 7 Jan 2011 21:44:27 +0100 Subject: Add copyright and license info to version.py --- nova/version.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/nova/version.py b/nova/version.py index 1504a5c82..7b27acb6a 100644 --- a/nova/version.py +++ b/nova/version.py @@ -1,4 +1,19 @@ -#!/usr/bin/env python +# 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. + try: from nova.vcsversion import version_info except ImportError: -- cgit From 09a8b83c5fca2ba6ad250b0224b2297bff2306a2 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 7 Jan 2011 23:44:47 +0100 Subject: s/string_with_vcs/version_string_with_vcs/g --- nova/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/log.py b/nova/log.py index 5851bd224..c1428c051 100644 --- a/nova/log.py +++ b/nova/log.py @@ -157,7 +157,7 @@ class NovaLogger(logging.Logger): extra = {} if context: extra.update(_dictify_context(context)) - extra.update({"nova_version": version.string_with_vcs()}) + extra.update({"nova_version": version.version_string_with_vcs()}) logging.Logger._log(self, level, msg, args, exc_info, extra) def addHandler(self, handler): -- cgit From a29bba7e9f57b97085902fa97d17de32da8044cb Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Sat, 8 Jan 2011 00:02:24 +0100 Subject: s/canonical_version/canonical_version_string/g --- doc/source/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 20e9c88af..368a686c3 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -64,7 +64,7 @@ from nova import version as nova_version # The full version, including alpha/beta/rc tags. release = nova_version.version() # The short X.Y version. -version = nova_version.canonical_version() +version = nova_version.canonical_version_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 2ccece789..5b58274c6 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ class local_sdist(sdist): sdist.run(self) setup(name='nova', - version=version.canonical_version(), + version=version.canonical_version_string(), description='cloud computing fabric controller', author='OpenStack', author_email='nova@lists.launchpad.net', -- cgit From af5af6155690baf55c30f6a70c0c9f829f107802 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 7 Jan 2011 23:11:41 +0000 Subject: Now that we aren't using twisted we can vgs to check for the existence of the volume group --- nova/volume/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 477e0abf4..dcddec92a 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -80,7 +80,8 @@ class VolumeDriver(object): def check_for_setup_error(self): """Returns an error if prerequisites aren't met""" - if not os.path.isdir("/dev/%s" % FLAGS.volume_group): + out, err = self._execute("sudo vgs") + if not FLAGS.volume_group in out: raise exception.Error(_("volume group %s doesn't exist") % FLAGS.volume_group) -- cgit From d757a1a10f0cbc5a3c0f5b1427d1d526584298ce Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 7 Jan 2011 23:22:52 +0000 Subject: Return region info in the proper format. --- nova/api/ec2/cloud.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ccce83b84..b34488731 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -249,6 +249,7 @@ class CloudController(object): FLAGS.cc_host, FLAGS.cc_port, FLAGS.ec2_suffix)}] + return {'regionInfo': regions} def describe_snapshots(self, context, -- cgit From 5eb5373af5dd8f062975b4c42e12f95569f7e41b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 8 Jan 2011 10:04:22 -0800 Subject: use safer vgs call --- nova/volume/driver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/volume/driver.py b/nova/volume/driver.py index dcddec92a..6bc925f3e 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -20,7 +20,6 @@ Drivers for volumes. """ -import os import time from nova import exception @@ -80,8 +79,9 @@ class VolumeDriver(object): def check_for_setup_error(self): """Returns an error if prerequisites aren't met""" - out, err = self._execute("sudo vgs") - if not FLAGS.volume_group in out: + out, err = self._execute("sudo vgs --noheadings -o name") + volume_groups = out.split() + if not FLAGS.volume_group in volume_groups: raise exception.Error(_("volume group %s doesn't exist") % FLAGS.volume_group) -- cgit From 4a9a02575bacb493b57dd83744561a77516bd6ff Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 8 Jan 2011 16:39:12 -0800 Subject: late import module for register_models() so it doesn't create the db before flags are loaded --- nova/service.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nova/service.py b/nova/service.py index 864a42469..523c1a8d7 100644 --- a/nova/service.py +++ b/nova/service.py @@ -38,7 +38,6 @@ from nova import log as logging from nova import flags from nova import rpc from nova import utils -from nova.db.sqlalchemy import models FLAGS = flags.FLAGS @@ -209,6 +208,10 @@ class Service(object): logging.exception(_("model server went away")) try: + # NOTE(vish): This is late-loaded to make sure that the + # database is not created before flags have + # been loaded. + from nova.db.sqlalchemy import models models.register_models() except OperationalError: logging.exception(_("Data store %s is unreachable." -- cgit From 16eeac71055ffa9fe0fc7a13032da4e6397121b1 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Sat, 8 Jan 2011 17:40:06 -0800 Subject: fixed doc make process for new nova version (rev530) machanism --- doc/source/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 368a686c3..d09984b19 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -60,11 +60,11 @@ copyright = u'2010, United States Government as represented by the Administrator # |version| and |release|, also used in various other places throughout the # built documents. # -from nova import version as nova_version +import nova.version # The full version, including alpha/beta/rc tags. -release = nova_version.version() +release = nova.version.version_string() # The short X.Y version. -version = nova_version.canonical_version_string() +version = nova,version.canonical_version_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -- cgit From 346bd0e7c93757f940f133179b36657302d4296f Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Sat, 8 Jan 2011 17:46:22 -0800 Subject: typo correction --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index d09984b19..34c4ec288 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -64,7 +64,7 @@ import nova.version # The full version, including alpha/beta/rc tags. release = nova.version.version_string() # The short X.Y version. -version = nova,version.canonical_version_string() +version = nova.version.canonical_version_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -- cgit From 5424690912a6edc2a64bfbbd44120e52a85c7f48 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Sun, 9 Jan 2011 09:25:27 -0800 Subject: added myself to authors and fixed typo to follow standard --- Authors | 2 +- doc/source/conf.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Authors b/Authors index 8dfaf9557..99c493b0e 100644 --- a/Authors +++ b/Authors @@ -40,4 +40,4 @@ Trey Morris Vishvananda Ishaya Youcef Laribi Zhixue Wu - +Ken Pepple diff --git a/doc/source/conf.py b/doc/source/conf.py index 34c4ec288..996dfb0a7 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -60,11 +60,12 @@ copyright = u'2010, United States Government as represented by the Administrator # |version| and |release|, also used in various other places throughout the # built documents. # -import nova.version +from nova import version as nova_version +#import nova.version # The full version, including alpha/beta/rc tags. -release = nova.version.version_string() +release = nova_version.version_string() # The short X.Y version. -version = nova.version.canonical_version_string() +version = nova_version.canonical_version_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -- cgit From bc3d288abb6b1cae1465490b3df99a201be8bdc5 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Sun, 9 Jan 2011 11:13:19 -0800 Subject: alphbetized Authors --- Authors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authors b/Authors index 99c493b0e..47101e272 100644 --- a/Authors +++ b/Authors @@ -23,6 +23,7 @@ Jonathan Bryce Josh Kearney Joshua McKenty Justin Santa Barbara +Ken Pepple Matt Dietz Michael Gundlach Monty Taylor @@ -40,4 +41,3 @@ Trey Morris Vishvananda Ishaya Youcef Laribi Zhixue Wu -Ken Pepple -- cgit From 6d05c3e5d9112aead1db23e942f24605a3301af9 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 9 Jan 2011 23:01:10 -0800 Subject: fix describe instances + test --- nova/api/ec2/cloud.py | 14 ++++++++------ nova/tests/test_cloud.py | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b34488731..fd3141a97 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -25,7 +25,6 @@ datastore. import base64 import datetime import IPy -import re import os from nova import compute @@ -35,7 +34,6 @@ from nova import db from nova import exception from nova import flags from nova import log as logging -from nova import quota from nova import network from nova import rpc from nova import utils @@ -603,19 +601,23 @@ class CloudController(object): return [{label: x} for x in lst] def describe_instances(self, context, **kwargs): - return self._format_describe_instances(context) + return self._format_describe_instances(context, **kwargs) - def _format_describe_instances(self, context): - return {'reservationSet': self._format_instances(context)} + def _format_describe_instances(self, context, **kwargs): + return {'reservationSet': self._format_instances(context, **kwargs)} def _format_run_instances(self, context, reservation_id): i = self._format_instances(context, reservation_id=reservation_id) assert len(i) == 1 return i[0] - def _format_instances(self, context, **kwargs): + def _format_instances(self, context, instance_id=None, **kwargs): reservations = {} instances = self.compute_api.get_all(context, **kwargs) + # NOTE(vish): instance_id is an optional list of ids to filter by + if instance_id: + instance_id = [ec2_id_to_id(x) for x in instance_id] + instances = [x for x in instances if x['id'] in instance_id] for instance in instances: if not context.user.is_admin(): if instance['image_id'] == FLAGS.vpn_image_id: diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index a645ef538..b8a15c7b2 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -133,6 +133,23 @@ class CloudTestCase(test.TestCase): db.volume_destroy(self.context, vol1['id']) db.volume_destroy(self.context, vol2['id']) + def test_describe_instances(self): + """Makes sure describe_instances works and filters results.""" + inst1 = db.instance_create(self.context, {'reservation_id': 'a'}) + inst2 = db.instance_create(self.context, {'reservation_id': 'a'}) + result = self.cloud.describe_instances(self.context) + result = result['reservationSet'][0] + self.assertEqual(len(result['instancesSet']), 2) + instance_id = cloud.id_to_ec2_id(inst2['id']) + result = self.cloud.describe_instances(self.context, + instance_id=[instance_id]) + result = result['reservationSet'][0] + self.assertEqual(len(result['instancesSet']), 1) + self.assertEqual(result['instancesSet'][0]['instanceId'], + instance_id) + db.instance_destroy(self.context, inst1['id']) + db.instance_destroy(self.context, inst2['id']) + def test_console_output(self): image_id = FLAGS.default_image instance_type = FLAGS.default_instance_type @@ -141,7 +158,6 @@ class CloudTestCase(test.TestCase): 'instance_type': instance_type, 'max_count': max_count} rv = self.cloud.run_instances(self.context, **kwargs) - print rv instance_id = rv['instancesSet'][0]['instanceId'] output = self.cloud.get_console_output(context=self.context, instance_id=[instance_id]) -- cgit From c8566628d4c15bcaf16baf8fca2a31528e7eac13 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 9 Jan 2011 23:53:51 -0800 Subject: optimize to call get if instance_id is specified since most of the time people will just be requesting one id --- nova/api/ec2/cloud.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index fd3141a97..9166301c7 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -613,11 +613,12 @@ class CloudController(object): def _format_instances(self, context, instance_id=None, **kwargs): reservations = {} - instances = self.compute_api.get_all(context, **kwargs) # NOTE(vish): instance_id is an optional list of ids to filter by if instance_id: instance_id = [ec2_id_to_id(x) for x in instance_id] - instances = [x for x in instances if x['id'] in instance_id] + instances = [self.compute_api.get(context, x) for x in instance_id] + else: + instances = self.compute_api.get_all(context, **kwargs) for instance in instances: if not context.user.is_admin(): if instance['image_id'] == FLAGS.vpn_image_id: -- cgit From 15b81abbd23f033fc9e35a7d49b8f65d2ae76586 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 10 Jan 2011 11:32:17 +0100 Subject: Create LibvirtConnection directly, rather than going through libvirt_conn.get_connection. This should remove the dependency on libvirt for tests. --- nova/tests/test_virt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 2f418bd5d..59053f4d0 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -171,7 +171,7 @@ class LibvirtConnTestCase(test.TestCase): for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): FLAGS.libvirt_type = libvirt_type - conn = libvirt_conn.get_connection(True) + conn = libvirt_conn.LibvirtConnection(True) uri = conn.get_uri() self.assertEquals(uri, expected_uri) -- cgit