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(-) (limited to 'nova') 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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(+) (limited to 'nova') 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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 c7305af78049f94dedcbb55480b91a3c6d843b9f Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 4 Jan 2011 00:23:35 -0500 Subject: Apply logging changes as a giant patch to work around the cloudpipe delete + add issue in the original patch. --- nova/api/ec2/__init__.py | 92 ++++++++--- nova/api/ec2/admin.py | 38 ++++- nova/api/ec2/apirequest.py | 12 +- nova/api/ec2/cloud.py | 45 ++++-- nova/api/ec2/metadatarequesthandler.py | 7 +- nova/api/openstack/__init__.py | 13 +- nova/auth/ldapdriver.py | 7 +- nova/auth/manager.py | 62 ++++++-- nova/auth/signer.py | 15 +- nova/compute/api.py | 33 ++-- nova/compute/disk.py | 11 +- nova/compute/manager.py | 76 ++++++---- nova/compute/monitor.py | 39 ++--- nova/crypto.py | 5 +- nova/exception.py | 8 +- nova/fakerabbit.py | 20 ++- nova/flags.py | 12 +- nova/image/glance.py | 23 ++- nova/log.py | 261 ++++++++++++++++++++++++++++++++ nova/network/linux_net.py | 19 +-- nova/network/manager.py | 16 +- nova/objectstore/handler.py | 68 ++++++--- nova/rpc.py | 26 ++-- nova/scheduler/manager.py | 5 +- nova/service.py | 19 +-- nova/tests/api/openstack/test_images.py | 1 - nova/tests/objectstore_unittest.py | 2 - nova/tests/test_access.py | 1 - nova/tests/test_auth.py | 9 +- nova/tests/test_cloud.py | 18 +-- nova/tests/test_compute.py | 8 +- nova/tests/test_log.py | 107 +++++++++++++ nova/tests/test_network.py | 9 +- nova/tests/test_quota.py | 4 - nova/tests/test_rpc.py | 7 +- nova/tests/test_volume.py | 6 +- nova/twistd.py | 25 +-- nova/utils.py | 17 ++- nova/virt/connection.py | 5 +- nova/virt/libvirt_conn.py | 63 ++++---- nova/virt/xenapi/fake.py | 24 +-- nova/virt/xenapi/vm_utils.py | 68 ++++----- nova/virt/xenapi/vmops.py | 26 ++-- nova/virt/xenapi/volume_utils.py | 44 +++--- nova/virt/xenapi/volumeops.py | 31 ++-- nova/virt/xenapi_conn.py | 16 +- nova/volume/driver.py | 11 +- nova/volume/manager.py | 21 +-- nova/wsgi.py | 8 +- 49 files changed, 995 insertions(+), 468 deletions(-) create mode 100644 nova/log.py create mode 100644 nova/tests/test_log.py (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index aa3bfaeb4..4dd2e55cd 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -20,7 +20,7 @@ Starting point for routing EC2 requests. """ -import logging +import datetime import routes import webob import webob.dec @@ -29,6 +29,7 @@ import webob.exc from nova import context from nova import exception from nova import flags +from nova import log as logging from nova import wsgi from nova.api.ec2 import apirequest from nova.api.ec2 import admin @@ -37,6 +38,7 @@ from nova.auth import manager FLAGS = flags.FLAGS +LOG = logging.getLogger("nova.api") flags.DEFINE_boolean('use_forwarded_for', False, 'Treat X-Forwarded-For as the canonical remote address. ' 'Only enable this if you have a sanitizing proxy.') @@ -52,10 +54,6 @@ flags.DEFINE_list('lockout_memcached_servers', None, 'Memcached servers or None for in process cache.') -_log = logging.getLogger("api") -_log.setLevel(logging.DEBUG) - - class API(wsgi.Middleware): """Routing for all EC2 API requests.""" @@ -64,6 +62,40 @@ class API(wsgi.Middleware): if FLAGS.use_lockout: self.application = Lockout(self.application) + @webob.dec.wsgify + def __call__(self, req): + rv = req.get_response(self.application) + self.log_request_completion(rv, req) + return rv + + def log_request_completion(self, response, request): + controller = request.environ.get('ec2.controller', None) + if controller: + controller = controller.__class__.__name__ + action = request.environ.get('ec2.action', None) + ctxt = request.environ.get('ec2.context', None) + seconds = 'X' + microseconds = 'X' + if ctxt: + delta = datetime.datetime.utcnow() - \ + ctxt.timestamp + seconds = delta.seconds + microseconds = delta.microseconds + LOG.info( + "%s.%ss %s %s %s %s:%s %s [%s] %s %s", + seconds, + microseconds, + request.remote_addr, + request.method, + request.path_info, + controller, + action, + response.status_int, + request.user_agent, + request.content_type, + response.content_type, + context=ctxt) + class Lockout(wsgi.Middleware): """Lockout for x minutes on y failed auths in a z minute period. @@ -107,14 +139,13 @@ class Lockout(wsgi.Middleware): # NOTE(vish): To use incr, failures has to be a string. self.mc.set(failures_key, '1', time=FLAGS.lockout_window * 60) elif failures >= FLAGS.lockout_attempts: - _log.warn('Access key %s has had %d failed authentications' - ' and will be locked out for %d minutes.' % - (access_key, failures, FLAGS.lockout_minutes)) + LOG.warn('Access key %s has had %d failed authentications' + ' and will be locked out for %d minutes.', + access_key, failures, FLAGS.lockout_minutes) self.mc.set(failures_key, str(failures), time=FLAGS.lockout_minutes * 60) return res - class Authenticate(wsgi.Middleware): """Authenticate an EC2 request and add 'ec2.context' to WSGI environ.""" @@ -142,8 +173,9 @@ class Authenticate(wsgi.Middleware): req.method, req.host, req.path) - except exception.Error, ex: - logging.debug(_("Authentication Failure: %s") % ex) + # Be explicit for what exceptions are 403, the rest bubble as 500 + except (exception.NotFound, exception.NotAuthorized) as ex: + LOG.audit(_("Authentication Failure: %s"), str(ex)) raise webob.exc.HTTPForbidden() # Authenticated! @@ -154,6 +186,8 @@ class Authenticate(wsgi.Middleware): project=project, remote_address=remote_address) req.environ['ec2.context'] = ctxt + LOG.audit(_('Authenticated Request For %s:%s)'), user.name, + project.name, context=req.environ['ec2.context']) return self.application @@ -189,9 +223,9 @@ class Router(wsgi.Middleware): except: raise webob.exc.HTTPBadRequest() - _log.debug(_('action: %s') % action) + LOG.debug(_('action: %s'), action) for key, value in args.items(): - _log.debug(_('arg: %s\t\tval: %s') % (key, value)) + LOG.debug(_('arg: %s\t\tval: %s'), key, value) # Success! req.environ['ec2.controller'] = controller @@ -263,6 +297,8 @@ class Authorizer(wsgi.Middleware): if self._matches_any_role(context, allowed_roles): return self.application else: + LOG.audit("Unauthorized request for controller=%s and action=%s", + controller_name, action, context=context) raise webob.exc.HTTPUnauthorized() def _matches_any_role(self, context, roles): @@ -297,15 +333,24 @@ class Executor(wsgi.Application): result = None try: result = api_request.send(context, **args) + except exception.NotFound as ex: + LOG.info(_('NotFound raised: %s'), str(ex), context=context) + return self._error(req, context, type(ex).__name__, str(ex)) except exception.ApiError as ex: - + LOG.exception('ApiError raised', context=context) if ex.code: - return self._error(req, ex.code, ex.message) + return self._error(req, context, ex.code, str(ex)) else: - return self._error(req, type(ex).__name__, ex.message) - # TODO(vish): do something more useful with unknown exceptions + return self._error(req, context, type(ex).__name__, str(ex)) except Exception as ex: - return self._error(req, type(ex).__name__, str(ex)) + extra = {'environment': req.environ} + LOG.exception(_('Unexpected error raised: %s'), str(ex), + extra=extra, context=context) + return self._error(req, + context, + 'UnknownError', + _('An unknown error has occurred. ' + 'Please try your request again.')) else: resp = webob.Response() resp.status = 200 @@ -313,15 +358,16 @@ class Executor(wsgi.Application): resp.body = str(result) return resp - def _error(self, req, code, message): - logging.error("%s: %s", code, message) + def _error(self, req, context, code, message): + LOG.error("%s: %s", code, message, context=context) resp = webob.Response() resp.status = 400 resp.headers['Content-Type'] = 'text/xml' resp.body = str('\n' - '%s' - '%s' - '?' % (code, message)) + '%s' + '%s' + '%s' % + (code, message, context.request_id)) return resp diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index fac01369e..758b612e8 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -24,9 +24,13 @@ import base64 from nova import db from nova import exception +from nova import log as logging from nova.auth import manager +LOG = logging.getLogger('nova.api.ec2.admin') + + def user_dict(user, base64_file=None): """Convert the user object to a result dict""" if user: @@ -75,17 +79,18 @@ class AdminController(object): return {'userSet': [user_dict(u) for u in manager.AuthManager().get_users()]} - def register_user(self, _context, name, **_kwargs): + def register_user(self, context, name, **_kwargs): """Creates a new user, and returns generated credentials.""" + LOG.audit(_("Creating new user: %s"), name, context=context) return user_dict(manager.AuthManager().create_user(name)) - def deregister_user(self, _context, name, **_kwargs): + def deregister_user(self, context, name, **_kwargs): """Deletes a single user (NOT undoable.) Should throw an exception if the user has instances, volumes, or buckets remaining. """ + LOG.audit(_("Deleting user: %s"), name, context=context) manager.AuthManager().delete_user(name) - return True def describe_roles(self, context, project_roles=True, **kwargs): @@ -105,15 +110,27 @@ class AdminController(object): operation='add', **kwargs): """Add or remove a role for a user and project.""" if operation == 'add': + if project: + LOG.audit(_("Adding role %s to user %s for project %s"), role, + user, project, context=context) + else: + LOG.audit(_("Adding sitewide role %s to user %s"), role, user, + context=context) manager.AuthManager().add_role(user, role, project) elif operation == 'remove': + if project: + LOG.audit(_("Removing role %s from user %s for project %s"), + role, user, project, context=context) + else: + LOG.audit(_("Removing sitewide role %s from user %s"), role, + user, context=context) manager.AuthManager().remove_role(user, role, project) else: - raise exception.ApiError('operation must be add or remove') + raise exception.ApiError(_('operation must be add or remove')) return True - def generate_x509_for_user(self, _context, name, project=None, **kwargs): + def generate_x509_for_user(self, context, name, project=None, **kwargs): """Generates and returns an x509 certificate for a single user. Is usually called from a client that will wrap this with access and secret key info, and return a zip file. @@ -122,6 +139,8 @@ class AdminController(object): project = name project = manager.AuthManager().get_project(project) user = manager.AuthManager().get_user(name) + LOG.audit(_("Getting x509 for user: %s on project: %s"), name, + project, context=context) return user_dict(user, base64.b64encode(project.get_credentials(user))) def describe_project(self, context, name, **kwargs): @@ -137,6 +156,8 @@ class AdminController(object): def register_project(self, context, name, manager_user, description=None, member_users=None, **kwargs): """Creates a new project""" + LOG.audit(_("Create project %s managed by %s"), name, manager_user, + context=context) return project_dict( manager.AuthManager().create_project( name, @@ -146,6 +167,7 @@ class AdminController(object): def deregister_project(self, context, name): """Permanently deletes a project.""" + LOG.audit(_("Delete project: %s"), name, context=context) manager.AuthManager().delete_project(name) return True @@ -159,11 +181,15 @@ class AdminController(object): **kwargs): """Add or remove a user from a project.""" if operation == 'add': + LOG.audit(_("Adding user %s to project %s"), user, project, + context=context) manager.AuthManager().add_to_project(user, project) elif operation == 'remove': + LOG.audit(_("Removing user %s from project %s"), user, project, + context=context) manager.AuthManager().remove_from_project(user, project) else: - raise exception.ApiError('operation must be add or remove') + raise exception.ApiError(_('operation must be add or remove')) return True # FIXME(vish): these host commands don't work yet, perhaps some of the diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py index a90fbeb0c..d0b417db1 100644 --- a/nova/api/ec2/apirequest.py +++ b/nova/api/ec2/apirequest.py @@ -20,13 +20,13 @@ APIRequest class """ -import logging import re # TODO(termie): replace minidom with etree from xml.dom import minidom -_log = logging.getLogger("api") -_log.setLevel(logging.DEBUG) +from nova import log as logging + +LOG = logging.getLogger("nova.api.request") _c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))') @@ -94,7 +94,7 @@ class APIRequest(object): except AttributeError: _error = _('Unsupported API request: controller = %s,' 'action = %s') % (self.controller, self.action) - _log.warning(_error) + LOG.exception(_error) # TODO: Raise custom exception, trap in apiserver, # and reraise as 400 error. raise Exception(_error) @@ -142,7 +142,7 @@ class APIRequest(object): response = xml.toxml() xml.unlink() - _log.debug(response) + LOG.debug(response) return response def _render_dict(self, xml, el, data): @@ -151,7 +151,7 @@ class APIRequest(object): val = data[key] el.appendChild(self._render_data(xml, key, val)) except: - _log.debug(data) + LOG.debug(data) raise def _render_data(self, xml, el_name, data): diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9fb6307a8..d0db08db7 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -24,17 +24,16 @@ datastore. import base64 import datetime -import logging +import IPy import re import os from nova import context -import IPy - from nova import crypto 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 rpc from nova import utils @@ -45,6 +44,8 @@ from nova.compute import instance_types FLAGS = flags.FLAGS flags.DECLARE('storage_availability_zone', 'nova.volume.manager') +LOG = logging.getLogger("nova.api.cloud") + InvalidInputException = exception.InvalidInputException @@ -280,6 +281,7 @@ class CloudController(object): return {'keypairsSet': result} def create_key_pair(self, context, key_name, **kwargs): + LOG.audit(_("Create key pair %s"), key_name, context=context) data = _gen_key(context, context.user.id, key_name) return {'keyName': key_name, 'keyFingerprint': data['fingerprint'], @@ -287,6 +289,7 @@ class CloudController(object): # TODO(vish): when context is no longer an object, pass it here def delete_key_pair(self, context, key_name, **kwargs): + LOG.audit(_("Delete key pair %s"), key_name, context=context) try: db.key_pair_destroy(context, context.user.id, key_name) except exception.NotFound: @@ -393,6 +396,8 @@ class CloudController(object): return False def revoke_security_group_ingress(self, context, group_name, **kwargs): + LOG.audit(_("Revoke security group ingress %s"), group_name, + context=context) self.compute_api.ensure_default_security_group(context) security_group = db.security_group_get_by_name(context, context.project_id, @@ -419,6 +424,8 @@ class CloudController(object): # for these operations, so support for newer API versions # is sketchy. def authorize_security_group_ingress(self, context, group_name, **kwargs): + LOG.audit(_("Authorize security group ingress %s"), group_name, + context=context) self.compute_api.ensure_default_security_group(context) security_group = db.security_group_get_by_name(context, context.project_id, @@ -455,6 +462,7 @@ class CloudController(object): return source_project_id def create_security_group(self, context, group_name, group_description): + LOG.audit(_("Create Security Group %s"), group_name, context=context) self.compute_api.ensure_default_security_group(context) if db.security_group_exists(context, context.project_id, group_name): raise exception.ApiError(_('group %s already exists') % group_name) @@ -469,6 +477,7 @@ class CloudController(object): group_ref)]} def delete_security_group(self, context, group_name, **kwargs): + LOG.audit(_("Delete security group %s"), group_name, context=context) security_group = db.security_group_get_by_name(context, context.project_id, group_name) @@ -476,6 +485,8 @@ class CloudController(object): return True def get_console_output(self, context, instance_id, **kwargs): + LOG.audit(_("Get console output for instance %s"), instance_id, + context=context) # instance_id is passed in as a list of instances ec2_id = instance_id[0] internal_id = ec2_id_to_internal_id(ec2_id) @@ -539,10 +550,12 @@ class CloudController(object): return v def create_volume(self, context, size, **kwargs): + LOG.audit(_("Create volume of %s GB"), size, context=context) # check quota if quota.allowed_volumes(context, 1, size) < 1: - logging.warn("Quota exceeeded for %s, tried to create %sG volume", - context.project_id, size) + LOG.warn(_("Quota exceeeded for project %s, tried to create " + "%sG volume"), context.project_id, size, + context=context) raise quota.QuotaError("Volume quota exceeded. You cannot " "create a volume of size %s" % size) vol = {} @@ -568,6 +581,8 @@ class CloudController(object): return {'volumeSet': [self._format_volume(context, dict(volume_ref))]} def attach_volume(self, context, volume_id, instance_id, device, **kwargs): + LOG.audit(_("Attach volume %s to instacne %s at %s"), volume_id, + instance_id, device, context=context) volume_ref = db.volume_get_by_ec2_id(context, volume_id) if not re.match("^/dev/[a-z]d[a-z]+$", device): raise exception.ApiError(_("Invalid device specified: %s. " @@ -594,6 +609,7 @@ class CloudController(object): 'volumeId': volume_ref['id']} def detach_volume(self, context, volume_id, **kwargs): + LOG.audit("Detach volume %s", volume_id, context=context) volume_ref = db.volume_get_by_ec2_id(context, volume_id) instance_ref = db.volume_get_instance(context.elevated(), volume_ref['id']) @@ -728,11 +744,11 @@ class CloudController(object): return {'addressesSet': addresses} def allocate_address(self, context, **kwargs): + LOG.audit(_("Allocate address"), context=context) # check quota if quota.allowed_floating_ips(context, 1) < 1: - logging.warn(_("Quota exceeeded for %s, tried to allocate " - "address"), - context.project_id) + LOG.warn(_("Quota exceeeded for %s, tried to allocate address"), + context.project_id, context=context) raise quota.QuotaError(_("Address quota exceeded. You cannot " "allocate any more addresses")) # NOTE(vish): We don't know which network host should get the ip @@ -746,6 +762,7 @@ class CloudController(object): return {'addressSet': [{'publicIp': public_ip}]} def release_address(self, context, public_ip, **kwargs): + LOG.audit(_("Release address %s"), public_ip, context=context) floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) # NOTE(vish): We don't know which network host should get the ip # when we deallocate, so just send it to any one. This @@ -758,6 +775,8 @@ class CloudController(object): return {'releaseResponse': ["Address released."]} def associate_address(self, context, instance_id, public_ip, **kwargs): + LOG.audit(_("Associate address %s to instance %s"), public_ip, + instance_id, context=context) internal_id = ec2_id_to_internal_id(instance_id) instance_ref = self.compute_api.get_instance(context, internal_id) fixed_address = db.instance_get_fixed_address(context, @@ -775,6 +794,7 @@ class CloudController(object): return {'associateResponse': ["Address associated."]} def disassociate_address(self, context, public_ip, **kwargs): + LOG.audit(_("Disassociate address %s"), public_ip, context=context) floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) # NOTE(vish): Get the topic from the host name of the network of # the associated fixed ip. @@ -811,7 +831,7 @@ class CloudController(object): def terminate_instances(self, context, instance_id, **kwargs): """Terminate each instance in instance_id, which is a list of ec2 ids. instance_id is a kwarg so its name cannot be modified.""" - logging.debug("Going to start terminating instances") + LOG.debug(_("Going to start terminating instances")) for ec2_id in instance_id: internal_id = ec2_id_to_internal_id(ec2_id) self.compute_api.delete_instance(context, internal_id) @@ -819,6 +839,7 @@ class CloudController(object): def reboot_instances(self, context, instance_id, **kwargs): """instance_id is a list of instance ids""" + LOG.audit(_("Reboot instance %r"), instance_id, context=context) for ec2_id in instance_id: internal_id = ec2_id_to_internal_id(ec2_id) self.compute_api.reboot(context, internal_id) @@ -850,6 +871,7 @@ class CloudController(object): def delete_volume(self, context, volume_id, **kwargs): # TODO: return error if not authorized + LOG.audit(_("Deleting volume %s"), volume_id, context=context) volume_ref = db.volume_get_by_ec2_id(context, volume_id) if volume_ref['status'] != "available": raise exception.ApiError(_("Volume status must be available")) @@ -871,6 +893,7 @@ class CloudController(object): return {'imagesSet': images} def deregister_image(self, context, image_id, **kwargs): + LOG.audit("De-registering image %s", image_id, context=context) self.image_service.deregister(context, image_id) return {'imageId': image_id} @@ -878,7 +901,8 @@ class CloudController(object): if image_location is None and 'name' in kwargs: image_location = kwargs['name'] image_id = self.image_service.register(context, image_location) - logging.debug("Registered %s as %s" % (image_location, image_id)) + LOG.audit(_("Registered image %s with id %s"), image_location, + image_id, context=context) return {'imageId': image_id} def describe_image_attribute(self, context, image_id, attribute, **kwargs): @@ -906,6 +930,7 @@ class CloudController(object): raise exception.ApiError(_('only group "all" is supported')) if not operation_type in ['add', 'remove']: raise exception.ApiError(_('operation_type must be add or remove')) + LOG.audit(_("Updating image %s publicity"), image_id, context=context) return self.image_service.modify(context, image_id, operation_type) def update_image(self, context, image_id, **kwargs): diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index a57a6698a..848f0b034 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -18,15 +18,15 @@ """Metadata request handler.""" -import logging - import webob.dec import webob.exc +from nova import log as logging from nova import flags from nova.api.ec2 import cloud +LOG = logging.getLogger('nova.api.ec2.metadata') FLAGS = flags.FLAGS @@ -72,8 +72,7 @@ class MetadataRequestHandler(object): remote_address = req.headers.get('X-Forwarded-For', remote_address) meta_data = cc.get_metadata(remote_address) if meta_data is None: - logging.error(_('Failed to get metadata for ip: %s') % - remote_address) + LOG.error(_('Failed to get metadata for ip: %s'), remote_address) raise webob.exc.HTTPNotFound() data = self.lookup(req.path_info, meta_data) if data is None: diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index a1430caed..7e1c03d9f 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -20,28 +20,24 @@ WSGI middleware for OpenStack API controllers. """ -import time - -import logging import routes -import traceback import webob.dec import webob.exc import webob -from nova import context from nova import flags +from nova import log as logging from nova import utils from nova import wsgi from nova.api.openstack import faults from nova.api.openstack import backup_schedules from nova.api.openstack import flavors from nova.api.openstack import images -from nova.api.openstack import ratelimiting from nova.api.openstack import servers from nova.api.openstack import sharedipgroups +LOG = logging.getLogger('nova.api.openstack') FLAGS = flags.FLAGS flags.DEFINE_string('os_api_auth', 'nova.api.openstack.auth.AuthMiddleware', @@ -71,8 +67,7 @@ class API(wsgi.Middleware): try: return req.get_response(self.application) except Exception as ex: - logging.warn(_("Caught error: %s") % str(ex)) - logging.error(traceback.format_exc()) + LOG.exception(_("Caught error: %s"), str(ex)) exc = webob.exc.HTTPInternalServerError(explanation=str(ex)) return faults.Fault(exc) @@ -88,7 +83,7 @@ class APIRouter(wsgi.Router): server_members = {'action': 'POST'} if FLAGS.allow_admin_api: - logging.debug("Including admin operations in API.") + LOG.debug("Including admin operations in API.") server_members['pause'] = 'POST' server_members['unpause'] = 'POST' server_members["diagnostics"] = "GET" diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 7616ff112..3e0837ba8 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -24,11 +24,11 @@ other backends by creating another class that exposes the same public methods. """ -import logging import sys from nova import exception from nova import flags +from nova import log as logging FLAGS = flags.FLAGS @@ -66,6 +66,9 @@ flags.DEFINE_string('ldap_developer', 'cn=developers,ou=Groups,dc=example,dc=com', 'cn for Developers') +LOG = logging.getLogger("nova.ldapdriver") + + # TODO(vish): make an abstract base class with the same public methods # to define a set interface for AuthDrivers. I'm delaying # creating this now because I'm expecting an auth refactor @@ -502,7 +505,7 @@ class LdapDriver(object): try: self.conn.modify_s(group_dn, attr) except self.ldap.OBJECT_CLASS_VIOLATION: - logging.debug(_("Attempted to remove the last member of a group. " + LOG.debug(_("Attempted to remove the last member of a group. " "Deleting the group at %s instead."), group_dn) self.__delete_group(group_dn) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index d3e266952..bfac7fc2a 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -20,7 +20,6 @@ Nova authentication management """ -import logging import os import shutil import string # pylint: disable-msg=W0402 @@ -33,6 +32,7 @@ from nova import crypto from nova import db from nova import exception from nova import flags +from nova import log as logging from nova import utils from nova.auth import signer @@ -71,6 +71,9 @@ flags.DEFINE_string('auth_driver', 'nova.auth.dbdriver.DbDriver', 'Driver that auth manager uses') +LOG = logging.getLogger('nova.authmanager') + + class AuthBase(object): """Base class for objects relating to auth @@ -254,43 +257,51 @@ class AuthManager(object): # TODO(vish): check for valid timestamp (access_key, _sep, project_id) = access.partition(':') - logging.info(_('Looking up user: %r'), access_key) + LOG.debug(_('Looking up user: %r'), access_key) user = self.get_user_from_access_key(access_key) - logging.info('user: %r', user) + LOG.debug('user: %r', user) if user == None: + LOG.audit(_("Failed authorization for access key %s"), access_key) raise exception.NotFound(_('No user found for access key %s') % access_key) # NOTE(vish): if we stop using project name as id we need better # logic to find a default project for user if project_id == '': + LOG.debug(_("Using project name = user name (%s)"), user.name) project_id = user.name project = self.get_project(project_id) if project == None: + LOG.audit(_("failed authorization: no project named %s (user=%s)"), + project_id, user.name) raise exception.NotFound(_('No project called %s could be found') % project_id) if not self.is_admin(user) and not self.is_project_member(user, project): + LOG.audit(_("Failed authorization: user %s not admin and not " + "member of project %s"), user.name, project.name) raise exception.NotFound(_('User %s is not a member of project %s') % (user.id, project.id)) if check_type == 's3': sign = signer.Signer(user.secret.encode()) expected_signature = sign.s3_authorization(headers, verb, path) - logging.debug('user.secret: %s', user.secret) - logging.debug('expected_signature: %s', expected_signature) - logging.debug('signature: %s', signature) + LOG.debug('user.secret: %s', user.secret) + LOG.debug('expected_signature: %s', expected_signature) + LOG.debug('signature: %s', signature) if signature != expected_signature: + LOG.audit(_("Invalid signature for user %s"), user.name) raise exception.NotAuthorized(_('Signature does not match')) elif check_type == 'ec2': # NOTE(vish): hmac can't handle unicode, so encode ensures that # secret isn't unicode expected_signature = signer.Signer(user.secret.encode()).generate( params, verb, server_string, path) - logging.debug('user.secret: %s', user.secret) - logging.debug('expected_signature: %s', expected_signature) - logging.debug('signature: %s', signature) + LOG.debug('user.secret: %s', user.secret) + LOG.debug('expected_signature: %s', expected_signature) + LOG.debug('signature: %s', signature) if signature != expected_signature: + LOG.audit(_("Invalid signature for user %s"), user.name) raise exception.NotAuthorized(_('Signature does not match')) return (user, project) @@ -398,6 +409,12 @@ class AuthManager(object): raise exception.NotFound(_("The %s role can not be found") % role) if project is not None and role in FLAGS.global_roles: raise exception.NotFound(_("The %s role is global only") % role) + if project: + LOG.audit(_("Adding role %s to user %s in project %s"), role, + User.safe_id(user), Project.safe_id(project)) + else: + LOG.audit(_("Adding sitewide role %s to user %s"), role, + User.safe_id(user)) with self.driver() as drv: drv.add_role(User.safe_id(user), role, Project.safe_id(project)) @@ -418,6 +435,12 @@ class AuthManager(object): @type project: Project or project_id @param project: Project in which to remove local role. """ + if project: + LOG.audit(_("Removing role %s from user %s on project %s"), + role, User.safe_id(user), Project.safe_id(project)) + else: + LOG.audit(_("Removing sitewide role %s from user %s"), role, + User.safe_id(user)) with self.driver() as drv: drv.remove_role(User.safe_id(user), role, Project.safe_id(project)) @@ -480,6 +503,8 @@ class AuthManager(object): description, member_users) if project_dict: + LOG.audit(_("Created project %s with manager %s"), name, + manager_user) project = Project(**project_dict) return project @@ -496,6 +521,7 @@ class AuthManager(object): @param project: This will be the new description of the project. """ + LOG.audit(_("modifying project %s"), Project.safe_id(project)) if manager_user: manager_user = User.safe_id(manager_user) with self.driver() as drv: @@ -505,6 +531,8 @@ class AuthManager(object): def add_to_project(self, user, project): """Add user to project""" + LOG.audit(_("Adding user %s to project %s"), User.safe_id(user), + Project.safe_id(project)) with self.driver() as drv: return drv.add_to_project(User.safe_id(user), Project.safe_id(project)) @@ -523,6 +551,8 @@ class AuthManager(object): def remove_from_project(self, user, project): """Removes a user from a project""" + LOG.audit(_("Remove user %s from project %s"), User.safe_id(user), + Project.safe_id(project)) with self.driver() as drv: return drv.remove_from_project(User.safe_id(user), Project.safe_id(project)) @@ -549,6 +579,7 @@ class AuthManager(object): def delete_project(self, project): """Deletes a project""" + LOG.audit(_("Deleting project %s"), Project.safe_id(project)) with self.driver() as drv: drv.delete_project(Project.safe_id(project)) @@ -603,13 +634,16 @@ class AuthManager(object): with self.driver() as drv: user_dict = drv.create_user(name, access, secret, admin) if user_dict: - return User(**user_dict) + rv = User(**user_dict) + LOG.audit(_("Created user %s (admin: %r)"), rv.name, rv.admin) + return rv def delete_user(self, user): """Deletes a user Additionally deletes all users key_pairs""" uid = User.safe_id(user) + LOG.audit(_("Deleting user %s"), uid) db.key_pair_destroy_all_by_user(context.get_admin_context(), uid) with self.driver() as drv: @@ -618,6 +652,12 @@ class AuthManager(object): def modify_user(self, user, access_key=None, secret_key=None, admin=None): """Modify credentials for a user""" uid = User.safe_id(user) + if access_key: + LOG.audit(_("Access Key change for user %s"), uid) + if secret_key: + LOG.audit(_("Secret Key change for user %s"), uid) + if admin is not None: + LOG.audit(_("Admin status set to %r for user %s"), admin, uid) with self.driver() as drv: drv.modify_user(uid, access_key, secret_key, admin) @@ -666,7 +706,7 @@ class AuthManager(object): port=vpn_port) zippy.writestr(FLAGS.credential_vpn_file, config) else: - logging.warn(_("No vpn data for project %s"), pid) + LOG.warn(_("No vpn data for project %s"), pid) zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(pid)) zippy.close() diff --git a/nova/auth/signer.py b/nova/auth/signer.py index f7d29f534..744e315d4 100644 --- a/nova/auth/signer.py +++ b/nova/auth/signer.py @@ -46,7 +46,6 @@ Utility class for parsing signed AMI manifests. import base64 import hashlib import hmac -import logging import urllib # NOTE(vish): for new boto @@ -54,9 +53,13 @@ import boto # NOTE(vish): for old boto import boto.utils +from nova import log as logging from nova.exception import Error +LOG = logging.getLogger('nova.signer') + + class Signer(object): """Hacked up code from boto/connection.py""" @@ -120,7 +123,7 @@ class Signer(object): def _calc_signature_2(self, params, verb, server_string, path): """Generate AWS signature version 2 string.""" - logging.debug('using _calc_signature_2') + LOG.debug('using _calc_signature_2') string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path) if self.hmac_256: current_hmac = self.hmac_256 @@ -136,13 +139,13 @@ class Signer(object): val = urllib.quote(val, safe='-_~') pairs.append(urllib.quote(key, safe='') + '=' + val) qs = '&'.join(pairs) - logging.debug('query string: %s', qs) + LOG.debug('query string: %s', qs) string_to_sign += qs - logging.debug('string_to_sign: %s', string_to_sign) + LOG.debug('string_to_sign: %s', string_to_sign) current_hmac.update(string_to_sign) b64 = base64.b64encode(current_hmac.digest()) - logging.debug('len(b64)=%d', len(b64)) - logging.debug('base64 encoded digest: %s', b64) + LOG.debug('len(b64)=%d', len(b64)) + LOG.debug('base64 encoded digest: %s', b64) return b64 diff --git a/nova/compute/api.py b/nova/compute/api.py index 07c69bd31..c0141e569 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -21,12 +21,12 @@ Handles all API requests relating to instances (guest vms). """ import datetime -import logging import time 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 rpc from nova import utils @@ -34,6 +34,7 @@ from nova.compute import instance_types from nova.db import base FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.compute.api') def generate_default_hostname(internal_id): @@ -58,13 +59,13 @@ class ComputeAPI(base.Base): instance = self.db.instance_get_by_internal_id(context, instance_id) except exception.NotFound as e: - logging.warning("Instance %d was not found in get_network_topic", - instance_id) + LOG.warning(_("Instance %d was not found in get_network_topic"), + instance_id) raise e host = instance['host'] if not host: - raise exception.Error("Instance %d has no host" % instance_id) + raise exception.Error(_("Instance %d has no host") % instance_id) topic = self.db.queue_get_for(context, FLAGS.compute_topic, host) return rpc.call(context, topic, @@ -83,10 +84,10 @@ class ComputeAPI(base.Base): num_instances = quota.allowed_instances(context, max_count, instance_type) if num_instances < min_count: - logging.warn("Quota exceeeded for %s, tried to run %s instances", - context.project_id, min_count) - raise quota.QuotaError("Instance quota exceeded. You can only " - "run %s more instances of this type." % + LOG.warn(_("Quota exceeeded for %s, tried to run %s instances"), + context.project_id, min_count) + raise quota.QuotaError(_("Instance quota exceeded. You can only " + "run %s more instances of this type.") % num_instances, "InstanceLimitExceeded") is_vpn = image_id == FLAGS.vpn_image_id @@ -100,7 +101,7 @@ class ComputeAPI(base.Base): if kernel_id == str(FLAGS.null_kernel): kernel_id = None ramdisk_id = None - logging.debug("Creating a raw instance") + LOG.debug(_("Creating a raw instance")) # Make sure we have access to kernel and ramdisk (if not raw) if kernel_id: self.image_service.show(context, kernel_id) @@ -147,7 +148,7 @@ class ComputeAPI(base.Base): elevated = context.elevated() instances = [] - logging.debug(_("Going to run %s instances..."), num_instances) + LOG.debug(_("Going to run %s instances..."), num_instances) for num in range(num_instances): instance = dict(mac_address=utils.generate_mac(), launch_index=num, @@ -172,7 +173,7 @@ class ComputeAPI(base.Base): instance = self.update_instance(context, instance_id, **updates) instances.append(instance) - logging.debug(_("Casting to scheduler for %s/%s's instance %s"), + LOG.debug(_("Casting to scheduler for %s/%s's instance %s"), context.project_id, context.user_id, instance_id) rpc.cast(context, FLAGS.scheduler_topic, @@ -214,18 +215,18 @@ class ComputeAPI(base.Base): return self.db.instance_update(context, instance_id, kwargs) def delete_instance(self, context, instance_id): - logging.debug("Going to try and terminate %d" % instance_id) + LOG.debug(_("Going to try and terminate %d"), instance_id) try: instance = self.db.instance_get_by_internal_id(context, instance_id) except exception.NotFound as e: - logging.warning(_("Instance %d was not found during terminate"), - instance_id) + LOG.warning(_("Instance %d was not found during terminate"), + instance_id) raise e if (instance['state_description'] == 'terminating'): - logging.warning(_("Instance %d is already being terminated"), - instance_id) + LOG.warning(_("Instance %d is already being terminated"), + instance_id) return self.update_instance(context, diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 814a258cd..741499294 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -22,14 +22,15 @@ Includes injection of SSH PGP keys into authorized_keys file. """ -import logging import os import tempfile from nova import exception from nova import flags +from nova import log as logging +LOG = logging.getLogger('nova.compute.disk') FLAGS = flags.FLAGS flags.DEFINE_integer('minimum_root_size', 1024 * 1024 * 1024 * 10, 'minimum size in bytes of root partition') @@ -67,12 +68,12 @@ def partition(infile, outfile, local_bytes=0, resize=True, execute('resize2fs %s' % infile) file_size = FLAGS.minimum_root_size elif file_size % sector_size != 0: - logging.warn(_("Input partition size not evenly divisible by" - " sector size: %d / %d"), file_size, sector_size) + LOG.warn(_("Input partition size not evenly divisible by" + " sector size: %d / %d"), file_size, sector_size) primary_sectors = file_size / sector_size if local_bytes % sector_size != 0: - logging.warn(_("Bytes for local storage not evenly divisible" - " by sector size: %d / %d"), local_bytes, sector_size) + LOG.warn(_("Bytes for local storage not evenly divisible" + " by sector size: %d / %d"), local_bytes, sector_size) local_sectors = local_bytes / sector_size mbr_last = 62 # a diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 6e8f34347..cc5724346 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -35,10 +35,10 @@ terminating it. """ import datetime -import logging from nova import exception from nova import flags +from nova import log as logging from nova import manager from nova import rpc from nova import utils @@ -53,6 +53,9 @@ flags.DEFINE_string('stub_network', False, 'Stub network related code') +LOG = logging.getLogger('nova.computemanager') + + class ComputeManager(manager.Manager): """Manages the running instances from creation to destruction.""" @@ -111,7 +114,7 @@ class ComputeManager(manager.Manager): instance_ref = self.db.instance_get(context, instance_id) if instance_ref['name'] in self.driver.list_instances(): raise exception.Error(_("Instance has already been created")) - logging.debug(_("instance %s: starting..."), instance_id) + LOG.debug(_("instance %s: starting..."), instance_id) self.db.instance_update(context, instance_id, {'host': self.host}) @@ -149,8 +152,8 @@ class ComputeManager(manager.Manager): instance_id, {'launched_at': now}) except Exception: # pylint: disable-msg=W0702 - logging.exception(_("instance %s: Failed to spawn"), - instance_ref['name']) + LOG.exception(_("instance %s: Failed to spawn"), + instance_ref['name']) self.db.instance_set_state(context, instance_id, power_state.SHUTDOWN) @@ -161,14 +164,16 @@ class ComputeManager(manager.Manager): def terminate_instance(self, context, instance_id): """Terminate an instance on this machine.""" context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) + LOG.audit(_("Terminating instance %s//%s"), + instance_ref['internal_id'], instance_id, context=context) if not FLAGS.stub_network: address = self.db.instance_get_floating_address(context, instance_ref['id']) if address: - logging.debug(_("Disassociating address %s") % address) + LOG.debug(_("Disassociating address %s"), address, + context=context) # NOTE(vish): Right now we don't really care if the ip is # disassociated. We may need to worry about # checking this later. @@ -180,14 +185,15 @@ class ComputeManager(manager.Manager): address = self.db.instance_get_fixed_address(context, instance_ref['id']) if address: - logging.debug(_("Deallocating address %s") % address) + LOG.debug(_("Deallocating address %s"), address, + context=context) # NOTE(vish): Currently, nothing needs to be done on the # network node until release. If this changes, # we will need to cast here. self.network_manager.deallocate_fixed_ip(context.elevated(), address) - logging.debug(_("instance %s: terminating"), instance_id) + LOG.debug(_("instance %s: terminating"), instance_id, context=context) volumes = instance_ref.get('volumes', []) or [] for volume in volumes: @@ -207,15 +213,18 @@ class ComputeManager(manager.Manager): context = context.elevated() self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) + LOG.audit(_("Rebooting instance %s//%s"), instance_ref['internal_id'], + instance_id, context=context) if instance_ref['state'] != power_state.RUNNING: - logging.warn(_('trying to reboot a non-running ' - 'instance: %s (state: %s excepted: %s)'), - instance_ref['internal_id'], - instance_ref['state'], - power_state.RUNNING) - - logging.debug(_('instance %s: rebooting'), instance_ref['name']) + LOG.warn(_('trying to reboot a non-running ' + 'instance: %s (state: %s excepted: %s)'), + instance_ref['internal_id'], + instance_ref['state'], + power_state.RUNNING, + context=context) + + LOG.debug(_('instance %s: rebooting'), instance_ref['name']) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -251,8 +260,8 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug(_('instance %s: rescuing'), - instance_ref['internal_id']) + LOG.audit(_('instance %s: rescuing'), instance_ref['internal_id'], + context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -267,8 +276,8 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug(_('instance %s: unrescuing'), - instance_ref['internal_id']) + LOG.audit(_('instance %s: unrescuing'), instance_ref['internal_id'], + context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -287,8 +296,8 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug('instance %s: pausing', - instance_ref['internal_id']) + LOG.debug(_('instance %s: pausing'), instance_ref['internal_id'], + context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -305,8 +314,8 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug('instance %s: unpausing', - instance_ref['internal_id']) + LOG.debug(_('instance %s: unpausing'), instance_ref['internal_id'], + context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -364,8 +373,9 @@ class ComputeManager(manager.Manager): def get_console_output(self, context, instance_id): """Send the console output for an instance.""" context = context.elevated() - logging.debug(_("instance %s: getting console output"), instance_id) instance_ref = self.db.instance_get(context, instance_id) + LOG.audit(_("Get console output instance %s//%s"), + instance_ref['internal_id'], instance_id, context=context) return self.driver.get_console_output(instance_ref) @@ -373,8 +383,8 @@ class ComputeManager(manager.Manager): def attach_volume(self, context, instance_id, volume_id, mountpoint): """Attach a volume to an instance.""" context = context.elevated() - logging.debug(_("instance %s: attaching volume %s to %s"), instance_id, - volume_id, mountpoint) + LOG.audit(_("instance %s: attaching volume %s to %s"), instance_id, + volume_id, mountpoint, context=context) instance_ref = self.db.instance_get(context, instance_id) dev_path = self.volume_manager.setup_compute_volume(context, volume_id) @@ -390,8 +400,8 @@ class ComputeManager(manager.Manager): # NOTE(vish): The inline callback eats the exception info so we # log the traceback here and reraise the same # ecxception below. - logging.exception(_("instance %s: attach failed %s, removing"), - instance_id, mountpoint) + LOG.exception(_("instance %s: attach failed %s, removing"), + instance_id, mountpoint, context=context) self.volume_manager.remove_compute_volume(context, volume_id) raise exc @@ -402,14 +412,14 @@ class ComputeManager(manager.Manager): def detach_volume(self, context, instance_id, volume_id): """Detach a volume from an instance.""" context = context.elevated() - logging.debug(_("instance %s: detaching volume %s"), - instance_id, - volume_id) instance_ref = self.db.instance_get(context, instance_id) volume_ref = self.db.volume_get(context, volume_id) + LOG.audit(_("Detach volume %s from mountpoint %s on instance %s//%s"), + volume_id, volume_ref['mountpoint'], + instance_ref['internal_id'], instance_id, context=context) if instance_ref['name'] not in self.driver.list_instances(): - logging.warn(_("Detaching volume from unknown instance %s"), - instance_ref['name']) + LOG.warn(_("Detaching volume from unknown instance %s"), + instance_ref['name'], context=context) else: self.driver.detach_volume(instance_ref['name'], volume_ref['mountpoint']) diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py index 60c347a5e..cc94e44f4 100644 --- a/nova/compute/monitor.py +++ b/nova/compute/monitor.py @@ -25,19 +25,17 @@ Instance Monitoring: """ import datetime -import logging import os -import sys import time import boto import boto.s3 import rrdtool -from twisted.internet import defer from twisted.internet import task from twisted.application import service from nova import flags +from nova import log as logging from nova.virt import connection as virt_connection @@ -91,6 +89,9 @@ RRD_VALUES = { utcnow = datetime.datetime.utcnow +LOG = logging.getLogger('nova.instancemonitor') + + def update_rrd(instance, name, data): """ Updates the specified RRD file. @@ -255,20 +256,20 @@ class Instance(object): Updates the instances statistics and stores the resulting graphs in the internal object store on the cloud controller. """ - logging.debug(_('updating %s...'), self.instance_id) + LOG.debug(_('updating %s...'), self.instance_id) try: data = self.fetch_cpu_stats() if data != None: - logging.debug('CPU: %s', data) + LOG.debug('CPU: %s', data) update_rrd(self, 'cpu', data) data = self.fetch_net_stats() - logging.debug('NET: %s', data) + LOG.debug('NET: %s', data) update_rrd(self, 'net', data) data = self.fetch_disk_stats() - logging.debug('DISK: %s', data) + LOG.debug('DISK: %s', data) update_rrd(self, 'disk', data) # TODO(devcamcar): Turn these into pool.ProcessPool.execute() calls @@ -285,7 +286,7 @@ class Instance(object): graph_disk(self, '1w') graph_disk(self, '1m') except Exception: - logging.exception(_('unexpected error during update')) + LOG.exception(_('unexpected error during update')) self.last_updated = utcnow() @@ -309,7 +310,7 @@ class Instance(object): self.cputime = float(info['cpu_time']) self.cputime_last_updated = utcnow() - logging.debug('CPU: %d', self.cputime) + LOG.debug('CPU: %d', self.cputime) # Skip calculation on first pass. Need delta to get a meaningful value. if cputime_last_updated == None: @@ -319,17 +320,17 @@ class Instance(object): d = self.cputime_last_updated - cputime_last_updated t = d.days * 86400 + d.seconds - logging.debug('t = %d', t) + LOG.debug('t = %d', t) # Calculate change over time in number of nanoseconds of CPU time used. cputime_delta = self.cputime - cputime_last - logging.debug('cputime_delta = %s', cputime_delta) + LOG.debug('cputime_delta = %s', cputime_delta) # Get the number of virtual cpus in this domain. vcpus = int(info['num_cpu']) - logging.debug('vcpus = %d', vcpus) + LOG.debug('vcpus = %d', vcpus) # Calculate CPU % used and cap at 100. return min(cputime_delta / (t * vcpus * 1.0e9) * 100, 100) @@ -351,8 +352,8 @@ class Instance(object): rd += rd_bytes wr += wr_bytes except TypeError: - logging.error(_('Cannot get blockstats for "%s" on "%s"'), - disk, self.instance_id) + LOG.error(_('Cannot get blockstats for "%s" on "%s"'), + disk, self.instance_id) raise return '%d:%d' % (rd, wr) @@ -373,8 +374,8 @@ class Instance(object): rx += stats[0] tx += stats[4] except TypeError: - logging.error(_('Cannot get ifstats for "%s" on "%s"'), - interface, self.instance_id) + LOG.error(_('Cannot get ifstats for "%s" on "%s"'), + interface, self.instance_id) raise return '%d:%d' % (rx, tx) @@ -408,7 +409,7 @@ class InstanceMonitor(object, service.Service): try: conn = virt_connection.get_connection(read_only=True) except Exception, exn: - logging.exception(_('unexpected exception getting connection')) + LOG.exception(_('unexpected exception getting connection')) time.sleep(FLAGS.monitoring_instances_delay) return @@ -416,14 +417,14 @@ class InstanceMonitor(object, service.Service): try: self.updateInstances_(conn, domain_ids) except Exception, exn: - logging.exception('updateInstances_') + LOG.exception('updateInstances_') def updateInstances_(self, conn, domain_ids): for domain_id in domain_ids: if not domain_id in self._instances: instance = Instance(conn, domain_id) self._instances[domain_id] = instance - logging.debug(_('Found instance: %s'), domain_id) + LOG.debug(_('Found instance: %s'), domain_id) for key in self._instances.keys(): instance = self._instances[key] diff --git a/nova/crypto.py b/nova/crypto.py index b8405552d..a34b940f5 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -24,7 +24,6 @@ Includes root and intermediate CAs, SSH key_pairs and x509 certificates. import base64 import gettext import hashlib -import logging import os import shutil import struct @@ -39,8 +38,10 @@ gettext.install('nova', unicode=1) from nova import context from nova import db from nova import flags +from nova import log as logging +LOG = logging.getLogger("nova.crypto") FLAGS = flags.FLAGS flags.DEFINE_string('ca_file', 'cacert.pem', _('Filename of root CA')) flags.DEFINE_string('key_file', @@ -254,7 +255,7 @@ def _sign_csr(csr_text, ca_folder): csrfile = open(inbound, "w") csrfile.write(csr_text) csrfile.close() - logging.debug(_("Flags path: %s") % ca_folder) + LOG.debug(_("Flags path: %s"), ca_folder) start = os.getcwd() # Change working dir to CA os.chdir(ca_folder) diff --git a/nova/exception.py b/nova/exception.py index 277033e0f..7c6675e62 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -21,10 +21,8 @@ Nova base exception handling, including decorator for re-raising Nova-type exceptions. SHOULD include dedicated exception logging. """ -import logging -import sys -import traceback - +from nova import log as logging +LOG = logging.getLogger('nova.exception') class ProcessExecutionError(IOError): @@ -84,7 +82,7 @@ def wrap_exception(f): except Exception, e: if not isinstance(e, Error): #exc_type, exc_value, exc_traceback = sys.exc_info() - logging.exception(_('Uncaught exception')) + LOG.exception(_('Uncaught exception')) #logging.error(traceback.extract_stack(exc_traceback)) raise Error(str(e)) raise diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py index 79d8b894d..7c2d7177b 100644 --- a/nova/fakerabbit.py +++ b/nova/fakerabbit.py @@ -18,12 +18,16 @@ """Based a bit on the carrot.backeds.queue backend... but a lot better.""" -import logging import Queue as queue from carrot.backends import base from eventlet import greenthread +from nova import log as logging + + +LOG = logging.getLogger("nova.fakerabbit") + EXCHANGES = {} QUEUES = {} @@ -41,12 +45,12 @@ class Exchange(object): self._routes = {} def publish(self, message, routing_key=None): - logging.debug(_('(%s) publish (key: %s) %s'), - self.name, routing_key, message) + LOG.debug(_('(%s) publish (key: %s) %s'), + self.name, routing_key, message) routing_key = routing_key.split('.')[0] if routing_key in self._routes: for f in self._routes[routing_key]: - logging.debug(_('Publishing to route %s'), f) + LOG.debug(_('Publishing to route %s'), f) f(message, routing_key=routing_key) def bind(self, callback, routing_key): @@ -76,19 +80,19 @@ class Backend(base.BaseBackend): def queue_declare(self, queue, **kwargs): global QUEUES if queue not in QUEUES: - logging.debug(_('Declaring queue %s'), queue) + LOG.debug(_('Declaring queue %s'), queue) QUEUES[queue] = Queue(queue) def exchange_declare(self, exchange, type, *args, **kwargs): global EXCHANGES if exchange not in EXCHANGES: - logging.debug(_('Declaring exchange %s'), exchange) + LOG.debug(_('Declaring exchange %s'), exchange) EXCHANGES[exchange] = Exchange(exchange, type) def queue_bind(self, queue, exchange, routing_key, **kwargs): global EXCHANGES global QUEUES - logging.debug(_('Binding %s to %s with key %s'), + LOG.debug(_('Binding %s to %s with key %s'), queue, exchange, routing_key) EXCHANGES[exchange].bind(QUEUES[queue].push, routing_key) @@ -113,7 +117,7 @@ class Backend(base.BaseBackend): content_type=content_type, content_encoding=content_encoding) message.result = True - logging.debug(_('Getting from %s: %s'), queue, message) + LOG.debug(_('Getting from %s: %s'), queue, message) return message def prepare_message(self, message_data, delivery_mode, diff --git a/nova/flags.py b/nova/flags.py index 4b7334927..f5c2d4233 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -29,8 +29,6 @@ import sys import gflags -from nova import utils - class FlagValues(gflags.FlagValues): """Extension of gflags.FlagValues that allows undefined and runtime flags. @@ -213,10 +211,10 @@ DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('glance_port', 9292, 'glance port') -DEFINE_string('glance_host', utils.get_my_ip(), 'glance host') +DEFINE_string('glance_host', '127.0.0.1', 'glance host') DEFINE_integer('s3_port', 3333, 's3 port') -DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)') -DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)') +DEFINE_string('s3_host', '127.0.0.1', 's3 host (for infrastructure)') +DEFINE_string('s3_dmz', '127.0.0.1', 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') @@ -236,8 +234,8 @@ DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('ec2_prefix', 'http', 'prefix for ec2') -DEFINE_string('cc_host', utils.get_my_ip(), 'ip of api server') -DEFINE_string('cc_dmz', utils.get_my_ip(), 'internal ip of api server') +DEFINE_string('cc_host', '127.0.0.1', 'ip of api server') +DEFINE_string('cc_dmz', '127.0.0.1', 'internal ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2') diff --git a/nova/image/glance.py b/nova/image/glance.py index cc3192e7c..a3a2f4308 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -19,20 +19,17 @@ import httplib import json -import logging import urlparse -import webob.exc - -from nova.compute import api as compute_api -from nova import utils -from nova import flags from nova import exception -import nova.image.service +from nova import flags +from nova import log as logging +from nova.image import service -FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.image.glance') +FLAGS = flags.FLAGS flags.DEFINE_string('glance_teller_address', 'http://127.0.0.1', 'IP address or URL where Glance\'s Teller service resides') flags.DEFINE_string('glance_teller_port', '9191', @@ -78,8 +75,8 @@ class ParallaxClient(object): data = json.loads(res.read())['images'] return data else: - logging.warn(_("Parallax returned HTTP error %d from " - "request for /images"), res.status_int) + LOG.warn(_("Parallax returned HTTP error %d from " + "request for /images"), res.status_int) return [] finally: c.close() @@ -97,8 +94,8 @@ class ParallaxClient(object): data = json.loads(res.read())['images'] return data else: - logging.warn(_("Parallax returned HTTP error %d from " - "request for /images/detail"), res.status_int) + LOG.warn(_("Parallax returned HTTP error %d from " + "request for /images/detail"), res.status_int) return [] finally: c.close() @@ -166,7 +163,7 @@ class ParallaxClient(object): c.close() -class GlanceImageService(nova.image.service.BaseImageService): +class GlanceImageService(service.BaseImageService): """Provides storage and retrieval of disk image objects within Glance.""" def __init__(self): diff --git a/nova/log.py b/nova/log.py new file mode 100644 index 000000000..88a961e13 --- /dev/null +++ b/nova/log.py @@ -0,0 +1,261 @@ +# 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 logging handler. + +This module adds to logging functionality by adding the option to specify +a context object when calling the various log methods. If the context object +is not specified, default formatting is used. + +It also allows setting of formatting information through flags. +""" + + +import cStringIO +import json +import logging +import logging.handlers +import traceback + +from nova import flags +# TODO(todd): fix after version.py merge +# 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 ' + '[%(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] ' + '%(message)s', + 'format string to use for log messages') + +flags.DEFINE_string('logging_debug_format_suffix', + 'from %(processName)s (pid=%(process)d) %(funcName)s' + ' %(pathname)s:%(lineno)d', + 'data to append to log format when level is DEBUG') + +flags.DEFINE_string('logging_exception_prefix', + '(%(name)s): TRACE: ', + 'prefix each line of exception output with this format') + +flags.DEFINE_list('default_log_levels', + ['amqplib=WARN', + 'sqlalchemy=WARN', + 'audit=INFO'], + 'list of logger=LEVEL pairs') + +flags.DEFINE_bool('use_syslog', False, 'output to syslog') +flags.DEFINE_string('logfile', None, 'output to named file') + + + +# A list of things we want to replicate from logging. +# levels +CRITICAL = logging.CRITICAL +FATAL = logging.FATAL +ERROR = logging.ERROR +WARNING = logging.WARNING +WARN = logging.WARN +INFO = logging.INFO +DEBUG = logging.DEBUG +NOTSET = logging.NOTSET +# methods +getLogger = logging.getLogger +debug = logging.debug +info = logging.info +warning = logging.warning +warn = logging.warn +error = logging.error +exception = logging.exception +critical = logging.critical +log = logging.log +# handlers +StreamHandler = logging.StreamHandler +FileHandler = logging.FileHandler +# logging.SysLogHandler is nicer than logging.logging.handler.SysLogHandler. +SysLogHandler = logging.handlers.SysLogHandler + + +# our new audit level +AUDIT = logging.INFO + 1 +logging.addLevelName(AUDIT, 'AUDIT') + + +def _dictify_context(context): + if context == None: + return None + if not isinstance(context, dict) \ + and getattr(context, 'to_dict', None): + context = context.to_dict() + return context + + +def basicConfig(): + logging.basicConfig() + for handler in logging.root.handlers: + handler.setFormatter(_formatter) + if FLAGS.verbose: + logging.root.setLevel(logging.DEBUG) + if FLAGS.use_syslog: + syslog = SysLogHandler(address='/dev/log') + syslog.setFormatter(_formatter) + logging.root.addHandler(syslog) + if FLAGS.logfile: + logfile = FileHandler(FLAGS.logfile) + logfile.setFormatter(_formatter) + logging.root.addHandler(logfile) + + +class NovaLogger(logging.Logger): + """ + NovaLogger manages request context and formatting. + + This becomes the class that is instanciated by logging.getLogger. + """ + def __init__(self, name, level=NOTSET): + level_name = self._get_level_from_flags(name, FLAGS) + level = globals()[level_name] + logging.Logger.__init__(self, name, level) + + def _get_level_from_flags(self, name, FLAGS): + # if exactly "nova", or a child logger, honor the verbose flag + if (name == "nova" or name.startswith("nova.")) and FLAGS.verbose: + return 'DEBUG' + for pair in FLAGS.default_log_levels: + logger, _sep, level = pair.partition('=') + # NOTE(todd): if we set a.b, we want a.b.c to have the same level + # (but not a.bc, so we check the dot) + if name == logger: + return level + if name.startswith(logger) and name[len(logger)] == '.': + return level + return 'INFO' + + def _log(self, level, msg, args, exc_info=None, extra=None, context=None): + """Extract context from any log call""" + if not extra: + extra = {} + if context: + extra.update(_dictify_context(context)) + # TODO(todd): fix after version.py merge + #extra.update({"nova_version": version.string_with_vcs()}) + logging.Logger._log(self, level, msg, args, exc_info, extra) + + def addHandler(self, handler): + """Each handler gets our custom formatter""" + handler.setFormatter(_formatter) + logging.Logger.addHandler(self, handler) + + def audit(self, msg, *args, **kwargs): + """Shortcut for our AUDIT level""" + if self.isEnabledFor(AUDIT): + self._log(AUDIT, msg, args, **kwargs) + + def exception(self, msg, *args, **kwargs): + """Logging.exception doesn't handle kwargs, so breaks context""" + if not kwargs.get('exc_info'): + kwargs['exc_info'] = 1 + self.error(msg, *args, **kwargs) + # NOTE(todd): does this really go here, or in _log ? + extra = kwargs.get('extra') + if not extra: + return + env = extra.get('environment') + if env: + env = env.copy() + for k in env.keys(): + if not isinstance(env[k], str): + env.pop(k) + message = "Environment: %s" % json.dumps(env) + kwargs.pop('exc_info') + self.error(message, **kwargs) + +logging.setLoggerClass(NovaLogger) + + +class NovaRootLogger(NovaLogger): + pass + +if not isinstance(logging.root, NovaRootLogger): + logging.root = NovaRootLogger("nova.root", WARNING) + NovaLogger.root = logging.root + NovaLogger.manager.root = logging.root + + +class NovaFormatter(logging.Formatter): + """ + A nova.context.RequestContext aware formatter configured through flags. + + The flags used to set format strings are: logging_context_foramt_string + and logging_default_format_string. You can also specify + logging_debug_format_suffix to append extra formatting if the log level is + debug. + + For information about what variables are available for the formatter see: + http://docs.python.org/library/logging.html#formatter + """ + + def format(self, record): + """Uses contextstring if request_id is set, otherwise default""" + if record.__dict__.get('request_id', None): + self._fmt = FLAGS.logging_context_format_string + else: + self._fmt = FLAGS.logging_default_format_string + if record.levelno == logging.DEBUG \ + and FLAGS.logging_debug_format_suffix: + self._fmt += " " + FLAGS.logging_debug_format_suffix + # Cache this on the record, Logger will respect our formated copy + if record.exc_info: + record.exc_text = self.formatException(record.exc_info, record) + return logging.Formatter.format(self, record) + + def formatException(self, exc_info, record=None): + """Format exception output with FLAGS.logging_exception_prefix""" + if not record: + return logging.Formatter.formatException(self, exc_info) + stringbuffer = cStringIO.StringIO() + traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], + None, stringbuffer) + lines = stringbuffer.getvalue().split("\n") + stringbuffer.close() + formatted_lines = [] + for line in lines: + pl = FLAGS.logging_exception_prefix % record.__dict__ + fl = "%s%s" % (pl, line) + formatted_lines.append(fl) + return "\n".join(formatted_lines) + +_formatter = NovaFormatter() + + +def audit(msg, *args, **kwargs): + """Shortcut for logging to root log with sevrity 'AUDIT'.""" + if len(logging.root.handlers) == 0: + basicConfig() + logging.root.log(AUDIT, msg, *args, **kwargs) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 931a89554..c525d5dc8 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -17,16 +17,17 @@ Implements vlans, bridges, and iptables rules using linux utilities. """ -import logging import os -# TODO(ja): does the definition of network_path belong here? - from nova import db from nova import flags +from nova import log as logging from nova import utils +LOG = logging.getLogger("nova.linux_net") + + def _bin_file(script): """Return the absolute path to scipt in the bin directory""" return os.path.abspath(os.path.join(__file__, "../../../bin", script)) @@ -172,7 +173,7 @@ def ensure_vlan(vlan_num): """Create a vlan unless it already exists""" interface = "vlan%s" % vlan_num if not _device_exists(interface): - logging.debug(_("Starting VLAN inteface %s"), interface) + LOG.debug(_("Starting VLAN inteface %s"), interface) _execute("sudo vconfig set_name_type VLAN_PLUS_VID_NO_PAD") _execute("sudo vconfig add %s %s" % (FLAGS.vlan_interface, vlan_num)) _execute("sudo ifconfig %s up" % interface) @@ -182,7 +183,7 @@ def ensure_vlan(vlan_num): def ensure_bridge(bridge, interface, net_attrs=None): """Create a bridge unless it already exists""" if not _device_exists(bridge): - logging.debug(_("Starting Bridge interface for %s"), interface) + LOG.debug(_("Starting Bridge interface for %s"), interface) _execute("sudo brctl addbr %s" % bridge) _execute("sudo brctl setfd %s 0" % bridge) # _execute("sudo brctl setageing %s 10" % bridge) @@ -248,9 +249,9 @@ def update_dhcp(context, network_id): _execute('sudo kill -HUP %d' % pid) return except Exception as exc: # pylint: disable-msg=W0703 - logging.debug(_("Hupping dnsmasq threw %s"), exc) + LOG.debug(_("Hupping dnsmasq threw %s"), exc) else: - logging.debug(_("Pid %d is stale, relaunching dnsmasq"), pid) + LOG.debug(_("Pid %d is stale, relaunching dnsmasq"), pid) # FLAGFILE and DNSMASQ_INTERFACE in env env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile, @@ -270,7 +271,7 @@ def _host_dhcp(fixed_ip_ref): def _execute(cmd, *args, **kwargs): """Wrapper around utils._execute for fake_network""" if FLAGS.fake_network: - logging.debug("FAKE NET: %s", cmd) + LOG.debug("FAKE NET: %s", cmd) return "fake", 0 else: return utils.execute(cmd, *args, **kwargs) @@ -328,7 +329,7 @@ def _stop_dnsmasq(network): try: _execute('sudo kill -TERM %d' % pid) except Exception as exc: # pylint: disable-msg=W0703 - logging.debug(_("Killing dnsmasq threw %s"), exc) + LOG.debug(_("Killing dnsmasq threw %s"), exc) def _dhcp_file(bridge, kind): diff --git a/nova/network/manager.py b/nova/network/manager.py index 16aa8f895..2b7325fd0 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -45,7 +45,6 @@ topologies. All of the network commands are issued to a subclass of """ import datetime -import logging import math import socket @@ -55,11 +54,13 @@ 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 manager from nova import utils from nova import rpc +LOG = logging.getLogger("nova.networkmanager") FLAGS = flags.FLAGS flags.DEFINE_string('flat_network_bridge', 'br100', 'Bridge for simple network instances') @@ -131,7 +132,7 @@ class NetworkManager(manager.Manager): def set_network_host(self, context, network_id): """Safely sets the host of the network.""" - logging.debug(_("setting network host")) + LOG.debug(_("setting network host")) host = self.db.network_set_host(context, network_id, self.host) @@ -186,7 +187,7 @@ class NetworkManager(manager.Manager): def lease_fixed_ip(self, context, mac, address): """Called by dhcp-bridge when ip is leased.""" - logging.debug("Leasing IP %s", address) + LOG.debug(_("Leasing IP %s"), address) fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] if not instance_ref: @@ -201,12 +202,11 @@ class NetworkManager(manager.Manager): {'leased': True, 'updated_at': now}) if not fixed_ip_ref['allocated']: - logging.warn(_("IP %s leased that was already deallocated"), - address) + LOG.warn(_("IP %s leased that was already deallocated"), address) def release_fixed_ip(self, context, mac, address): """Called by dhcp-bridge when ip is released.""" - logging.debug("Releasing IP %s", address) + LOG.debug("Releasing IP %s", address) fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] if not instance_ref: @@ -216,7 +216,7 @@ class NetworkManager(manager.Manager): raise exception.Error(_("IP %s released from bad mac %s vs %s") % (address, instance_ref['mac_address'], mac)) if not fixed_ip_ref['leased']: - logging.warn(_("IP %s released that was not leased"), address) + LOG.warn(_("IP %s released that was not leased"), address) self.db.fixed_ip_update(context, fixed_ip_ref['address'], {'leased': False}) @@ -437,7 +437,7 @@ class VlanManager(NetworkManager): self.host, time) if num: - logging.debug(_("Dissassociated %s stale fixed ip(s)"), num) + LOG.debug(_("Dissassociated %s stale fixed ip(s)"), num) def init_host(self): """Do any initialization that needs to be run if this is a diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py index 52257f69f..8e9235da4 100644 --- a/nova/objectstore/handler.py +++ b/nova/objectstore/handler.py @@ -39,7 +39,6 @@ S3 client with this module:: import datetime import json -import logging import multiprocessing import os import urllib @@ -54,12 +53,14 @@ from twisted.web import static from nova import context from nova import exception from nova import flags +from nova import log as logging from nova import utils from nova.auth import manager from nova.objectstore import bucket from nova.objectstore import image +LOG = logging.getLogger('nova.objectstore.handler') FLAGS = flags.FLAGS flags.DEFINE_string('s3_listen_host', '', 'Host to listen on.') @@ -132,9 +133,11 @@ def get_context(request): request.uri, headers=request.getAllHeaders(), check_type='s3') - return context.RequestContext(user, project) + rv = context.RequestContext(user, project) + LOG.audit("Authenticated request", context=rv) + return rv except exception.Error as ex: - logging.debug(_("Authentication Failure: %s"), ex) + LOG.debug(_("Authentication Failure: %s"), ex) raise exception.NotAuthorized() @@ -176,7 +179,7 @@ class S3(ErrorHandlingResource): def render_GET(self, request): # pylint: disable-msg=R0201 """Renders the GET request for a list of buckets as XML""" - logging.debug('List of buckets requested') + LOG.debug(_('List of buckets requested'), context=request.context) buckets = [b for b in bucket.Bucket.all() \ if b.is_authorized(request.context)] @@ -203,7 +206,7 @@ class BucketResource(ErrorHandlingResource): def render_GET(self, request): "Returns the keys for the bucket resource""" - logging.debug("List keys for bucket %s", self.name) + LOG.debug(_("List keys for bucket %s"), self.name) try: bucket_object = bucket.Bucket(self.name) @@ -211,6 +214,8 @@ class BucketResource(ErrorHandlingResource): return error.NoResource(message="No such bucket").render(request) if not bucket_object.is_authorized(request.context): + LOG.audit(_("Unauthorized attempt to access bucket %s"), + self.name, context=request.context) raise exception.NotAuthorized() prefix = get_argument(request, "prefix", u"") @@ -227,8 +232,8 @@ class BucketResource(ErrorHandlingResource): def render_PUT(self, request): "Creates the bucket resource""" - logging.debug(_("Creating bucket %s"), self.name) - logging.debug("calling bucket.Bucket.create(%r, %r)", + LOG.debug(_("Creating bucket %s"), self.name) + LOG.debug("calling bucket.Bucket.create(%r, %r)", self.name, request.context) bucket.Bucket.create(self.name, request.context) @@ -237,10 +242,12 @@ class BucketResource(ErrorHandlingResource): def render_DELETE(self, request): """Deletes the bucket resource""" - logging.debug(_("Deleting bucket %s"), self.name) + LOG.debug(_("Deleting bucket %s"), self.name) bucket_object = bucket.Bucket(self.name) if not bucket_object.is_authorized(request.context): + LOG.audit(_("Unauthorized attempt to delete bucket %s"), + self.name, context=request.context) raise exception.NotAuthorized() bucket_object.delete() @@ -261,11 +268,12 @@ class ObjectResource(ErrorHandlingResource): Raises NotAuthorized if user in request context is not authorized to delete the object. """ - logging.debug(_("Getting object: %s / %s"), - self.bucket.name, - self.name) + LOG.debug(_("Getting object: %s / %s"), self.bucket.name, self.name) if not self.bucket.is_authorized(request.context): + LOG.audit(_("Unauthorized attempt to get object %s from bucket " + "%s"), self.name, self.bucket.name, + context=request.context) raise exception.NotAuthorized() obj = self.bucket[urllib.unquote(self.name)] @@ -281,11 +289,12 @@ class ObjectResource(ErrorHandlingResource): Raises NotAuthorized if user in request context is not authorized to delete the object. """ - logging.debug(_("Putting object: %s / %s"), - self.bucket.name, - self.name) + LOG.debug(_("Putting object: %s / %s"), self.bucket.name, self.name) if not self.bucket.is_authorized(request.context): + LOG.audit(_("Unauthorized attempt to upload object %s to bucket " + "%s"), + self.name, self.bucket.name, context=request.context) raise exception.NotAuthorized() key = urllib.unquote(self.name) @@ -302,11 +311,13 @@ class ObjectResource(ErrorHandlingResource): authorized to delete the object. """ - logging.debug(_("Deleting object: %s / %s"), - self.bucket.name, - self.name) + LOG.debug(_("Deleting object: %s / %s"), self.bucket.name, self.name, + context=request.context) if not self.bucket.is_authorized(request.context): + LOG.audit("Unauthorized attempt to delete object %s from " + "bucket %s", self.name, self.bucket.name, + context=request.context) raise exception.NotAuthorized() del self.bucket[urllib.unquote(self.name)] @@ -379,13 +390,21 @@ class ImagesResource(resource.Resource): image_path = os.path.join(FLAGS.images_path, image_id) if not image_path.startswith(FLAGS.images_path) or \ os.path.exists(image_path): + LOG.audit(_("Not authorized to upload image: invalid directory " + "%s"), + image_path, context=request.context) raise exception.NotAuthorized() bucket_object = bucket.Bucket(image_location.split("/")[0]) if not bucket_object.is_authorized(request.context): + LOG.audit(_("Not authorized to upload image: unauthorized " + "bucket %s"), bucket_object.name, + context=request.context) raise exception.NotAuthorized() + LOG.audit(_("Starting image upload: %s"), image_id, + context=request.context) p = multiprocessing.Process(target=image.Image.register_aws_image, args=(image_id, image_location, request.context)) p.start() @@ -398,17 +417,21 @@ class ImagesResource(resource.Resource): image_id = get_argument(request, 'image_id', u'') image_object = image.Image(image_id) if not image_object.is_authorized(request.context): - logging.debug(_("not authorized for render_POST in images")) + LOG.audit(_("Not authorized to update attributes of image %s"), + image_id, context=request.context) raise exception.NotAuthorized() operation = get_argument(request, 'operation', u'') if operation: # operation implies publicity toggle - logging.debug(_("handling publicity toggle")) - image_object.set_public(operation == 'add') + newstatus = (operation == 'add') + LOG.audit(_("Toggling publicity flag of image %s %r"), image_id, + newstatus, context=request.context) + image_object.set_public(newstatus) else: # other attributes imply update - logging.debug(_("update user fields")) + LOG.audit(_("Updating user fields on image %s"), image_id, + context=request.context) clean_args = {} for arg in request.args.keys(): clean_args[arg] = request.args[arg][0] @@ -421,9 +444,12 @@ class ImagesResource(resource.Resource): image_object = image.Image(image_id) if not image_object.is_authorized(request.context): + LOG.audit(_("Unauthorized attempt to delete image %s"), + image_id, context=request.context) raise exception.NotAuthorized() image_object.delete() + LOG.audit(_("Deleted image: %s"), image_id, context=request.context) request.setResponseCode(204) return '' diff --git a/nova/rpc.py b/nova/rpc.py index 844088348..02052485d 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -22,7 +22,6 @@ No fan-out support yet. """ import json -import logging import sys import time import traceback @@ -36,13 +35,12 @@ from nova import context from nova import exception from nova import fakerabbit from nova import flags +from nova import log as logging from nova import utils FLAGS = flags.FLAGS - -LOG = logging.getLogger('amqplib') -LOG.setLevel(logging.DEBUG) +LOG = logging.getLogger('nova.rpc') class Connection(carrot_connection.BrokerConnection): @@ -91,14 +89,14 @@ class Consumer(messaging.Consumer): self.failed_connection = False break except: # Catching all because carrot sucks - logging.exception(_("AMQP server on %s:%d is unreachable." - " Trying again in %d seconds.") % ( - FLAGS.rabbit_host, - FLAGS.rabbit_port, - FLAGS.rabbit_retry_interval)) + LOG.exception(_("AMQP server on %s:%d is unreachable." + " Trying again in %d seconds.") % ( + FLAGS.rabbit_host, + FLAGS.rabbit_port, + FLAGS.rabbit_retry_interval)) self.failed_connection = True if self.failed_connection: - logging.exception(_("Unable to connect to AMQP server" + LOG.exception(_("Unable to connect to AMQP server" " after %d tries. Shutting down.") % FLAGS.rabbit_max_retries) sys.exit(1) @@ -116,14 +114,14 @@ class Consumer(messaging.Consumer): self.declare() super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) if self.failed_connection: - logging.error(_("Reconnected to queue")) + LOG.error(_("Reconnected to queue")) self.failed_connection = False # NOTE(vish): This is catching all errors because we really don't # exceptions to be logged 10 times a second if some # persistent failure occurs. except Exception: # pylint: disable-msg=W0703 if not self.failed_connection: - logging.exception(_("Failed to fetch message from queue")) + LOG.exception(_("Failed to fetch message from queue")) self.failed_connection = True def attach_to_eventlet(self): @@ -242,8 +240,8 @@ def msg_reply(msg_id, reply=None, failure=None): if failure: message = str(failure[1]) tb = traceback.format_exception(*failure) - logging.error(_("Returning exception %s to caller"), message) - logging.error(tb) + LOG.error(_("Returning exception %s to caller"), message) + LOG.error(tb) failure = (failure[0].__name__, str(failure[1]), tb) conn = Connection.instance(True) publisher = DirectPublisher(connection=conn, msg_id=msg_id) diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 44e21f2fd..a4d6dd574 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -21,15 +21,16 @@ Scheduler Service """ -import logging import functools from nova import db from nova import flags +from nova import log as logging from nova import manager from nova import rpc from nova import utils +LOG = logging.getLogger('nova.scheduler.manager') FLAGS = flags.FLAGS flags.DEFINE_string('scheduler_driver', 'nova.scheduler.chance.ChanceScheduler', @@ -65,4 +66,4 @@ class SchedulerManager(manager.Manager): db.queue_get_for(context, topic, host), {"method": method, "args": kwargs}) - logging.debug(_("Casting to %s %s for %s"), topic, host, method) + LOG.debug(_("Casting to %s %s for %s"), topic, host, method) diff --git a/nova/service.py b/nova/service.py index f1f90742f..fef7f0593 100644 --- a/nova/service.py +++ b/nova/service.py @@ -21,7 +21,6 @@ Generic Node baseclass for all workers that run on hosts """ import inspect -import logging import os import sys @@ -32,6 +31,7 @@ from eventlet import greenpool from nova import context from nova import db from nova import exception +from nova import log as logging from nova import flags from nova import rpc from nova import utils @@ -151,7 +151,7 @@ class Service(object): report_interval = FLAGS.report_interval if not periodic_interval: periodic_interval = FLAGS.periodic_interval - logging.warn(_("Starting %s node"), topic) + logging.audit(_("Starting %s node"), topic) service_obj = cls(host, binary, topic, manager, report_interval, periodic_interval) @@ -206,20 +206,17 @@ class Service(object): def serve(*services): - argv = FLAGS(sys.argv) + FLAGS(sys.argv) + logging.basicConfig() + + # TODO(todd): make this pigggyback the flag-based level override method + logging.getLogger('amqplib').setLevel(logging.WARN) if not services: services = [Service.create()] name = '_'.join(x.binary for x in services) - logging.debug("Serving %s" % name) - - logging.getLogger('amqplib').setLevel(logging.WARN) - - if FLAGS.verbose: - logging.getLogger().setLevel(logging.DEBUG) - else: - logging.getLogger().setLevel(logging.WARNING) + logging.debug(_("Serving %s"), name) logging.debug(_("Full set of FLAGS:")) for flag in FLAGS: diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 0f274bd15..f5be9c94f 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -22,7 +22,6 @@ and as a WSGI layer import json import datetime -import logging import unittest import stubout diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py index ceac17adb..da86e6e11 100644 --- a/nova/tests/objectstore_unittest.py +++ b/nova/tests/objectstore_unittest.py @@ -23,7 +23,6 @@ Unittets for S3 objectstore clone. import boto import glob import hashlib -import logging import os import shutil import tempfile @@ -63,7 +62,6 @@ class ObjectStoreTestCase(test.TestCase): self.flags(buckets_path=os.path.join(OSS_TEMPDIR, 'buckets'), images_path=os.path.join(OSS_TEMPDIR, 'images'), ca_path=os.path.join(os.path.dirname(__file__), 'CA')) - logging.getLogger().setLevel(logging.DEBUG) self.auth_manager = manager.AuthManager() self.auth_manager.create_user('user1') diff --git a/nova/tests/test_access.py b/nova/tests/test_access.py index 58fdea3b5..0929903cf 100644 --- a/nova/tests/test_access.py +++ b/nova/tests/test_access.py @@ -17,7 +17,6 @@ # under the License. import unittest -import logging import webob from nova import context diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index 15d40bc53..35ffffb67 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -16,17 +16,18 @@ # License for the specific language governing permissions and limitations # under the License. -import logging from M2Crypto import X509 import unittest from nova import crypto from nova import flags +from nova import log as logging from nova import test from nova.auth import manager from nova.api.ec2 import cloud FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.tests.auth_unittest') class user_generator(object): @@ -211,12 +212,12 @@ class AuthManagerTestCase(object): # NOTE(vish): Setup runs genroot.sh if it hasn't been run cloud.CloudController().setup() _key, cert_str = crypto.generate_x509_cert(user.id, project.id) - logging.debug(cert_str) + LOG.debug(cert_str) full_chain = crypto.fetch_ca(project_id=project.id, chain=True) int_cert = crypto.fetch_ca(project_id=project.id, chain=False) cloud_cert = crypto.fetch_ca() - logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain) + LOG.debug("CA chain:\n\n =====\n%s\n\n=====", full_chain) signed_cert = X509.load_cert_string(cert_str) chain_cert = X509.load_cert_string(full_chain) int_cert = X509.load_cert_string(int_cert) @@ -331,7 +332,7 @@ class AuthManagerLdapTestCase(AuthManagerTestCase, test.TestCase): test.TestCase.__init__(self, *args, **kwargs) import nova.auth.fakeldap as fakeldap if FLAGS.flush_db: - logging.info("Flushing datastore") + LOG.info("Flushing datastore") r = fakeldap.Store.instance() r.flushdb() diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 70d2c44da..e6ad2432b 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -18,7 +18,6 @@ from base64 import b64decode import json -import logging from M2Crypto import BIO from M2Crypto import RSA import os @@ -31,6 +30,7 @@ from nova import context from nova import crypto from nova import db from nova import flags +from nova import log as logging from nova import rpc from nova import service from nova import test @@ -41,6 +41,7 @@ from nova.objectstore import image FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.tests.cloud') # Temp dirs for working with image attributes through the cloud controller # (stole this from objectstore_unittest.py) @@ -56,7 +57,6 @@ class CloudTestCase(test.TestCase): images_path=IMAGES_PATH) self.conn = rpc.Connection.instance() - logging.getLogger().setLevel(logging.DEBUG) # set up our cloud self.cloud = cloud.CloudController() @@ -178,7 +178,7 @@ class CloudTestCase(test.TestCase): def test_run_instances(self): if FLAGS.connection_type == 'fake': - logging.debug("Can't test instances without a real virtual env.") + LOG.debug(_("Can't test instances without a real virtual env.")) return image_id = FLAGS.default_image instance_type = FLAGS.default_instance_type @@ -190,25 +190,25 @@ class CloudTestCase(test.TestCase): # TODO: check for proper response instance_id = rv['reservationSet'][0].keys()[0] instance = rv['reservationSet'][0][instance_id][0] - logging.debug("Need to watch instance %s until it's running..." % - instance['instance_id']) + LOG.debug(_("Need to watch instance %s until it's running..."), + instance['instance_id']) while True: greenthread.sleep(1) info = self.cloud._get_instance(instance['instance_id']) - logging.debug(info['state']) + LOG.debug(info['state']) if info['state'] == power_state.RUNNING: break self.assert_(rv) - if connection_type != 'fake': + if FLAGS.connection_type != 'fake': time.sleep(45) # Should use boto for polling here for reservations in rv['reservationSet']: # for res_id in reservations.keys(): - # logging.debug(reservations[res_id]) + # LOG.debug(reservations[res_id]) # for instance in reservations[res_id]: for instance in reservations[reservations.keys()[0]]: instance_id = instance['instance_id'] - logging.debug("Terminating instance %s" % instance_id) + LOG.debug(_("Terminating instance %s"), instance_id) rv = yield self.compute.terminate_instance(instance_id) def test_instance_update_state(self): diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 1fb9143f1..889ffe99f 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -20,12 +20,12 @@ Tests For Compute """ import datetime -import logging 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 test from nova import utils from nova.auth import manager @@ -33,12 +33,12 @@ from nova.compute import api as compute_api FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.tests.compute') class ComputeTestCase(test.TestCase): """Test case for compute""" def setUp(self): - logging.getLogger().setLevel(logging.DEBUG) super(ComputeTestCase, self).setUp() self.flags(connection_type='fake', stub_network=True, @@ -101,13 +101,13 @@ class ComputeTestCase(test.TestCase): self.compute.run_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) - logging.info(_("Running instances: %s"), instances) + LOG.info(_("Running instances: %s"), instances) self.assertEqual(len(instances), 1) self.compute.terminate_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) - logging.info(_("After terminating instances: %s"), instances) + LOG.info(_("After terminating instances: %s"), instances) self.assertEqual(len(instances), 0) def test_run_terminate_timestamps(self): diff --git a/nova/tests/test_log.py b/nova/tests/test_log.py new file mode 100644 index 000000000..d8dd38708 --- /dev/null +++ b/nova/tests/test_log.py @@ -0,0 +1,107 @@ +import cStringIO + +from nova import context +from nova import log +from nova import test + + +def _fake_context(): + return context.RequestContext(1, 1) + + +class RootLoggerTestCase(test.TrialTestCase): + def setUp(self): + super(RootLoggerTestCase, self).setUp() + self.log = log.logging.root + + def tearDown(self): + super(RootLoggerTestCase, self).tearDown() + log.NovaLogger.manager.loggerDict = {} + + def test_is_nova_instance(self): + self.assert_(isinstance(self.log, log.NovaLogger)) + + def test_name_is_nova_root(self): + self.assertEqual("nova.root", self.log.name) + + def test_handlers_have_nova_formatter(self): + formatters = [] + for h in self.log.handlers: + f = h.formatter + if isinstance(f, log.NovaFormatter): + formatters.append(f) + self.assert_(formatters) + self.assertEqual(len(formatters), len(self.log.handlers)) + + def test_handles_context_kwarg(self): + self.log.info("foo", context=_fake_context()) + self.assert_(True) # didn't raise exception + + def test_module_level_methods_handle_context_arg(self): + log.info("foo", context=_fake_context()) + self.assert_(True) # didn't raise exception + + def test_module_level_audit_handles_context_arg(self): + log.audit("foo", context=_fake_context()) + self.assert_(True) # didn't raise exception + +class NovaFormatterTestCase(test.TrialTestCase): + def setUp(self): + super(NovaFormatterTestCase, self).setUp() + self.flags(logging_context_format_string="HAS CONTEXT "\ + "[%(request_id)s]: %(message)s", + logging_default_format_string="NOCTXT: %(message)s", + logging_debug_format_suffix="--DBG") + self.log = log.logging.root + self.stream = cStringIO.StringIO() + handler = log.StreamHandler(self.stream) + self.log.addHandler(handler) + self.log.setLevel(log.DEBUG) + + def tearDown(self): + super(NovaFormatterTestCase, self).tearDown() + log.NovaLogger.manager.loggerDict = {} + + def test_uncontextualized_log(self): + self.log.info("foo") + self.assertEqual("NOCTXT: foo\n", self.stream.getvalue()) + + def test_contextualized_log(self): + ctxt = _fake_context() + self.log.info("bar", context=ctxt) + expected = "HAS CONTEXT [%s]: bar\n" % ctxt.request_id + self.assertEqual(expected, self.stream.getvalue()) + + def test_debugging_log(self): + self.log.debug("baz") + self.assertEqual("NOCTXT: baz --DBG\n", self.stream.getvalue()) + +class NovaLoggerTestCase(test.TrialTestCase): + def setUp(self): + super(NovaLoggerTestCase, self).setUp() + self.flags(default_log_levels=["nova-test=AUDIT"], verbose=False) + self.log = log.getLogger('nova-test') + + def tearDown(self): + super(NovaLoggerTestCase, self).tearDown() + log.NovaLogger.manager.loggerDict = {} + + def test_has_level_from_flags(self): + self.assertEqual(log.AUDIT, self.log.level) + + def test_child_log_has_level_of_parent_flag(self): + l = log.getLogger('nova-test.foo') + self.assertEqual(log.AUDIT, l.level) + +class VerboseLoggerTestCase(test.TrialTestCase): + def setUp(self): + super(VerboseLoggerTestCase, self).setUp() + self.flags(default_log_levels=["nova.test=AUDIT"], verbose=True) + self.log = log.getLogger('nova.test') + + def tearDown(self): + super(VerboseLoggerTestCase, self).tearDown() + log.NovaLogger.manager.loggerDict = {} + + def test_will_be_verbose_if_named_nova_and_verbose_flag_set(self): + self.assertEqual(log.DEBUG, self.log.level) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 96473ac7c..349e20f84 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -20,18 +20,18 @@ Unit Tests for network code """ import IPy import os -import logging from nova import context from nova import db from nova import exception from nova import flags -from nova import service +from nova import log as logging from nova import test from nova import utils from nova.auth import manager FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.tests.network') class NetworkTestCase(test.TestCase): @@ -45,7 +45,6 @@ class NetworkTestCase(test.TestCase): fake_network=True, network_size=16, num_networks=5) - logging.getLogger().setLevel(logging.DEBUG) self.manager = manager.AuthManager() self.user = self.manager.create_user('netuser', 'netuser', 'netuser') self.projects = [] @@ -328,7 +327,7 @@ def lease_ip(private_ip): 'TESTING': '1', 'FLAGFILE': FLAGS.dhcpbridge_flagfile} (out, err) = utils.execute(cmd, addl_env=env) - logging.debug("ISSUE_IP: %s, %s ", out, err) + LOG.debug("ISSUE_IP: %s, %s ", out, err) def release_ip(private_ip): @@ -344,4 +343,4 @@ def release_ip(private_ip): 'TESTING': '1', 'FLAGFILE': FLAGS.dhcpbridge_flagfile} (out, err) = utils.execute(cmd, addl_env=env) - logging.debug("RELEASE_IP: %s, %s ", out, err) + LOG.debug("RELEASE_IP: %s, %s ", out, err) diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 8cf2a5e54..ea7a006e2 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -16,11 +16,8 @@ # License for the specific language governing permissions and limitations # under the License. -import logging - from nova import context from nova import db -from nova import exception from nova import flags from nova import quota from nova import test @@ -34,7 +31,6 @@ FLAGS = flags.FLAGS class QuotaTestCase(test.TestCase): def setUp(self): - logging.getLogger().setLevel(logging.DEBUG) super(QuotaTestCase, self).setUp() self.flags(connection_type='fake', quota_instances=2, diff --git a/nova/tests/test_rpc.py b/nova/tests/test_rpc.py index 6ea2edcab..0e72b3eba 100644 --- a/nova/tests/test_rpc.py +++ b/nova/tests/test_rpc.py @@ -18,15 +18,16 @@ """ Unit Tests for remote procedure calls using queue """ -import logging from nova import context from nova import flags +from nova import logging from nova import rpc from nova import test FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.tests.rpc') class RpcTestCase(test.TestCase): @@ -115,13 +116,13 @@ class TestReceiver(object): @staticmethod def echo(context, value): """Simply returns whatever value is sent in""" - logging.debug("Received %s", value) + LOG.debug(_("Received %s"), value) return value @staticmethod def context(context, value): """Returns dictionary version of context""" - logging.debug("Received %s", context) + LOG.debug(_("Received %s"), context) return context.to_dict() @staticmethod diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py index b13455fb0..b40ca004b 100644 --- a/nova/tests/test_volume.py +++ b/nova/tests/test_volume.py @@ -19,23 +19,23 @@ Tests for Volume Code. """ -import logging from nova import context from nova import exception from nova import db from nova import flags +from nova import log as logging from nova import test from nova import utils FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.tests.volume') class VolumeTestCase(test.TestCase): """Test Case for volumes.""" def setUp(self): - logging.getLogger().setLevel(logging.DEBUG) super(VolumeTestCase, self).setUp() self.compute = utils.import_object(FLAGS.compute_manager) self.flags(connection_type='fake') @@ -159,7 +159,7 @@ class VolumeTestCase(test.TestCase): volume_id) self.assert_(iscsi_target not in targets) targets.append(iscsi_target) - logging.debug("Target %s allocated", iscsi_target) + LOG.debug(_("Target %s allocated"), iscsi_target) total_slots = FLAGS.iscsi_num_targets for _index in xrange(total_slots): volume_id = self._create_volume() diff --git a/nova/twistd.py b/nova/twistd.py index 29be9c4e1..556271999 100644 --- a/nova/twistd.py +++ b/nova/twistd.py @@ -22,7 +22,6 @@ manage pid files and support syslogging. """ import gflags -import logging import os import signal import sys @@ -34,6 +33,7 @@ from twisted.python import runtime from twisted.python import usage from nova import flags +from nova import log as logging if runtime.platformType == "win32": @@ -234,22 +234,12 @@ def serve(filename): OptionsClass = WrapTwistedOptions(TwistdServerOptions) options = OptionsClass() argv = options.parseOptions() - logging.getLogger('amqplib').setLevel(logging.WARN) FLAGS.python = filename FLAGS.no_save = True if not FLAGS.pidfile: FLAGS.pidfile = '%s.pid' % name elif FLAGS.pidfile.endswith('twistd.pid'): FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid', '%s.pid' % name) - # NOTE(vish): if we're running nodaemon, redirect the log to stdout - if FLAGS.nodaemon and not FLAGS.logfile: - FLAGS.logfile = "-" - if not FLAGS.logfile: - FLAGS.logfile = '%s.log' % name - elif FLAGS.logfile.endswith('twistd.log'): - FLAGS.logfile = FLAGS.logfile.replace('twistd.log', '%s.log' % name) - if FLAGS.logdir: - FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile) if not FLAGS.prefix: FLAGS.prefix = name elif FLAGS.prefix.endswith('twisted'): @@ -270,19 +260,10 @@ def serve(filename): print 'usage: %s [options] [start|stop|restart]' % argv[0] sys.exit(1) - formatter = logging.Formatter( - '(%(name)s): %(levelname)s %(message)s') - handler = logging.StreamHandler(log.StdioOnnaStick()) - handler.setFormatter(formatter) - logging.getLogger().addHandler(handler) - - if FLAGS.verbose: - logging.getLogger().setLevel(logging.DEBUG) - else: - logging.getLogger().setLevel(logging.WARNING) - + logging.basicConfig() logging.debug(_("Full set of FLAGS:")) for flag in FLAGS: logging.debug("%s : %s" % (flag, FLAGS.get(flag, None))) + logging.audit(_("Starting %s"), name) twistd.runApp(options) diff --git a/nova/utils.py b/nova/utils.py index 15112faa2..cc632b835 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -22,7 +22,6 @@ System-level utilities and helper functions. import datetime import inspect -import logging import os import random import subprocess @@ -37,8 +36,10 @@ from eventlet import greenthread from nova import exception from nova.exception import ProcessExecutionError +from nova import log as logging +LOG = logging.getLogger("nova.utils") TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" @@ -109,7 +110,7 @@ def vpn_ping(address, port, timeout=0.05, session_id=None): def fetchfile(url, target): - logging.debug(_("Fetching %s") % url) + LOG.debug(_("Fetching %s") % url) # c = pycurl.Curl() # fp = open(target, "wb") # c.setopt(c.URL, url) @@ -121,7 +122,7 @@ def fetchfile(url, target): def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): - logging.debug(_("Running cmd (subprocess): %s"), cmd) + LOG.debug(_("Running cmd (subprocess): %s"), cmd) env = os.environ.copy() if addl_env: env.update(addl_env) @@ -134,7 +135,7 @@ def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): result = obj.communicate() obj.stdin.close() if obj.returncode: - logging.debug(_("Result was %s") % (obj.returncode)) + LOG.debug(_("Result was %s") % (obj.returncode)) if check_exit_code and obj.returncode != 0: (stdout, stderr) = result raise ProcessExecutionError(exit_code=obj.returncode, @@ -167,12 +168,12 @@ def default_flagfile(filename='nova.conf'): def debug(arg): - logging.debug('debug in callback: %s', arg) + LOG.debug(_('debug in callback: %s'), arg) return arg def runthis(prompt, cmd, check_exit_code=True): - logging.debug(_("Running %s") % (cmd)) + LOG.debug(_("Running %s"), (cmd)) rv, err = execute(cmd, check_exit_code=check_exit_code) @@ -203,7 +204,7 @@ def get_my_ip(): csock.close() return addr except socket.gaierror as ex: - logging.warn(_("Couldn't get IP, using 127.0.0.1 %s"), ex) + LOG.warn(_("Couldn't get IP, using 127.0.0.1 %s"), ex) return "127.0.0.1" @@ -296,7 +297,7 @@ class LazyPluggable(object): fromlist = backend self.__backend = __import__(name, None, None, fromlist) - logging.info('backend %s', self.__backend) + LOG.debug(_('backend %s'), self.__backend) return self.__backend def __getattr__(self, key): diff --git a/nova/virt/connection.py b/nova/virt/connection.py index 61e99944e..7602cbe50 100644 --- a/nova/virt/connection.py +++ b/nova/virt/connection.py @@ -19,15 +19,16 @@ """Abstraction of the underlying virtualization API.""" -import logging import sys from nova import flags +from nova import log as logging from nova.virt import fake from nova.virt import libvirt_conn from nova.virt import xenapi_conn +LOG = logging.getLogger("nova.virt.connection") FLAGS = flags.FLAGS @@ -66,6 +67,6 @@ def get_connection(read_only=False): raise Exception('Unknown connection type "%s"' % t) if conn is None: - logging.error(_('Failed to open connection to the hypervisor')) + LOG.error(_('Failed to open connection to the hypervisor')) sys.exit(1) return conn diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 00edfbdc8..ac82fdadb 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -36,7 +36,6 @@ Supports KVM, QEMU, UML, and XEN. """ -import logging import os import shutil @@ -50,6 +49,7 @@ 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.api import context from nova.auth import manager @@ -63,6 +63,9 @@ libxml2 = None Template = None +LOG = logging.getLogger('nova.virt.libvirt_conn') + + FLAGS = flags.FLAGS # TODO(vish): These flags should probably go into a shared location flags.DEFINE_string('rescue_image_id', 'ami-rescue', 'Rescue ami image') @@ -130,7 +133,7 @@ class LibvirtConnection(object): @property def _conn(self): if not self._wrapped_conn or not self._test_connection(): - logging.debug(_('Connecting to libvirt: %s') % self.libvirt_uri) + LOG.debug(_('Connecting to libvirt: %s'), self.libvirt_uri) self._wrapped_conn = self._connect(self.libvirt_uri, self.read_only) return self._wrapped_conn @@ -142,7 +145,7 @@ class LibvirtConnection(object): except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_SYSTEM_ERROR and \ e.get_error_domain() == libvirt.VIR_FROM_REMOTE: - logging.debug(_('Connection to libvirt broke')) + LOG.debug(_('Connection to libvirt broke')) return False raise @@ -214,8 +217,8 @@ class LibvirtConnection(object): def _cleanup(self, instance): target = os.path.join(FLAGS.instances_path, instance['name']) - logging.info(_('instance %s: deleting instance files %s'), - instance['name'], target) + LOG.info(_('instance %s: deleting instance files %s'), + instance['name'], target) if os.path.exists(target): shutil.rmtree(target) @@ -279,10 +282,10 @@ class LibvirtConnection(object): db.instance_set_state(context.get_admin_context(), instance['id'], state) if state == power_state.RUNNING: - logging.debug(_('instance %s: rebooted'), instance['name']) + LOG.debug(_('instance %s: rebooted'), instance['name']) timer.stop() except Exception, exn: - logging.error(_('_wait_for_reboot failed: %s'), exn) + LOG.exception(_('_wait_for_reboot failed: %s'), exn) db.instance_set_state(context.get_admin_context(), instance['id'], power_state.SHUTDOWN) @@ -325,10 +328,10 @@ class LibvirtConnection(object): state = self.get_info(instance['name'])['state'] db.instance_set_state(None, instance['id'], state) if state == power_state.RUNNING: - logging.debug(_('instance %s: rescued'), instance['name']) + LOG.debug(_('instance %s: rescued'), instance['name']) timer.stop() except Exception, exn: - logging.error(_('_wait_for_rescue failed: %s'), exn) + LOG.exception(_('_wait_for_rescue failed: %s'), exn) db.instance_set_state(None, instance['id'], power_state.SHUTDOWN) @@ -353,7 +356,7 @@ class LibvirtConnection(object): NWFilterFirewall(self._conn).setup_nwfilters_for_instance(instance) self._create_image(instance, xml) self._conn.createXML(xml, 0) - logging.debug(_("instance %s: is running"), instance['name']) + LOG.debug(_("instance %s: is running"), instance['name']) timer = utils.LoopingCall(f=None) @@ -363,11 +366,11 @@ class LibvirtConnection(object): db.instance_set_state(context.get_admin_context(), instance['id'], state) if state == power_state.RUNNING: - logging.debug(_('instance %s: booted'), instance['name']) + LOG.debug(_('instance %s: booted'), instance['name']) timer.stop() except: - logging.exception(_('instance %s: failed to boot'), - instance['name']) + LOG.exception(_('instance %s: failed to boot'), + instance['name']) db.instance_set_state(context.get_admin_context(), instance['id'], power_state.SHUTDOWN) @@ -377,11 +380,11 @@ class LibvirtConnection(object): return timer.start(interval=0.5, now=True) def _flush_xen_console(self, virsh_output): - logging.info('virsh said: %r' % (virsh_output,)) + LOG.info('virsh said: %r', virsh_output) virsh_output = virsh_output[0].strip() if virsh_output.startswith('/dev/'): - logging.info(_('cool, it\'s a device')) + LOG.info(_('cool, it\'s a device')) out, err = utils.execute("sudo dd if=%s iflag=nonblock" % virsh_output, check_exit_code=False) return out @@ -389,7 +392,7 @@ class LibvirtConnection(object): return '' def _append_to_file(self, data, fpath): - logging.info(_('data: %r, fpath: %r') % (data, fpath)) + LOG.info(_('data: %r, fpath: %r'), data, fpath) fp = open(fpath, 'a+') fp.write(data) return fpath @@ -397,7 +400,7 @@ class LibvirtConnection(object): def _dump_file(self, fpath): fp = open(fpath, 'r+') contents = fp.read() - logging.info('Contents: %r' % (contents,)) + LOG.info('Contents: %r', contents) return contents @exception.wrap_exception @@ -431,7 +434,7 @@ class LibvirtConnection(object): # TODO(termie): these are blocking calls, it would be great # if they weren't. - logging.info(_('instance %s: Creating image'), inst['name']) + LOG.info(_('instance %s: Creating image'), inst['name']) f = open(basepath('libvirt.xml'), 'w') f.write(libvirt_xml) f.close() @@ -487,10 +490,10 @@ class LibvirtConnection(object): 'dns': network_ref['dns']} if key or net: if key: - logging.info(_('instance %s: injecting key into image %s'), + LOG.info(_('instance %s: injecting key into image %s'), inst['name'], inst.image_id) if net: - logging.info(_('instance %s: injecting net into image %s'), + LOG.info(_('instance %s: injecting net into image %s'), inst['name'], inst.image_id) try: disk.inject_data(basepath('disk-raw'), key, net, @@ -498,9 +501,9 @@ class LibvirtConnection(object): execute=execute) except Exception as e: # This could be a windows image, or a vmdk format disk - logging.warn(_('instance %s: ignoring error injecting data' - ' into image %s (%s)'), - inst['name'], inst.image_id, e) + LOG.warn(_('instance %s: ignoring error injecting data' + ' into image %s (%s)'), + inst['name'], inst.image_id, e) if inst['kernel_id']: if os.path.exists(basepath('disk')): @@ -526,8 +529,10 @@ class LibvirtConnection(object): def to_xml(self, instance, rescue=False): # TODO(termie): cache? - logging.debug(_('instance %s: starting toXML method'), - instance['name']) + LOG.debug('instance %s: starting toXML method', instance['name']) + network = db.project_get_network(context.get_admin_context(), + instance['project_id']) + LOG.debug(_('instance %s: starting toXML method'), instance['name']) network = db.network_get_by_instance(context.get_admin_context(), instance['id']) # FIXME(vish): stick this in db @@ -569,7 +574,7 @@ class LibvirtConnection(object): xml_info['disk'] = xml_info['basepath'] + "/disk" xml = str(Template(self.libvirt_xml, searchList=[xml_info])) - logging.debug(_('instance %s: finished toXML method'), + LOG.debug(_('instance %s: finished toXML method'), instance['name']) return xml @@ -870,9 +875,9 @@ class NWFilterFirewall(object): rule_xml += "dstportstart='%s' dstportend='%s' " % \ (rule.from_port, rule.to_port) elif rule.protocol == 'icmp': - logging.info('rule.protocol: %r, rule.from_port: %r, ' - 'rule.to_port: %r' % - (rule.protocol, rule.from_port, rule.to_port)) + 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: diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index aa4026f97..f2c3a34f6 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -52,12 +52,12 @@ A fake XenAPI SDK. import datetime -import logging import uuid from pprint import pformat from nova import exception +from nova import log as logging _CLASSES = ['host', 'network', 'session', 'SR', 'VBD',\ @@ -65,9 +65,11 @@ _CLASSES = ['host', 'network', 'session', 'SR', 'VBD',\ _db_content = {} +LOG = logging.getLogger("nova.virt.xenapi.fake") + def log_db_contents(msg=None): - logging.debug(_("%s: _db_content => %s"), msg or "", pformat(_db_content)) + LOG.debug(_("%s: _db_content => %s"), msg or "", pformat(_db_content)) def reset(): @@ -242,9 +244,9 @@ class SessionBase(object): full_params = (self._session,) + params meth = getattr(self, methodname, None) if meth is None: - logging.warn('Raising NotImplemented') + LOG.debug('Raising NotImplemented') raise NotImplementedError( - 'xenapi.fake does not have an implementation for %s' % + _('xenapi.fake does not have an implementation for %s') % methodname) return meth(*full_params) @@ -278,12 +280,12 @@ class SessionBase(object): if impl is not None: def callit(*params): - logging.warn('Calling %s %s', name, impl) + LOG.debug(_('Calling %s %s'), name, impl) self._check_session(params) return impl(*params) return callit if self._is_gettersetter(name, True): - logging.warn('Calling getter %s', name) + LOG.debug(_('Calling getter %s'), name) return lambda *params: self._getter(name, params) elif self._is_create(name): return lambda *params: self._create(name, params) @@ -333,10 +335,10 @@ class SessionBase(object): field in _db_content[cls][ref]): return _db_content[cls][ref][field] - logging.error('Raising NotImplemented') + LOG.debuug(_('Raising NotImplemented')) raise NotImplementedError( - 'xenapi.fake does not have an implementation for %s or it has ' - 'been called with the wrong number of arguments' % name) + _('xenapi.fake does not have an implementation for %s or it has ' + 'been called with the wrong number of arguments') % name) def _setter(self, name, params): self._check_session(params) @@ -351,7 +353,7 @@ class SessionBase(object): field in _db_content[cls][ref]): _db_content[cls][ref][field] = val - logging.warn('Raising NotImplemented') + LOG.debug(_('Raising NotImplemented')) raise NotImplementedError( 'xenapi.fake does not have an implementation for %s or it has ' 'been called with the wrong number of arguments or the database ' @@ -399,7 +401,7 @@ class SessionBase(object): self._session not in _db_content['session']): raise Failure(['HANDLE_INVALID', 'session', self._session]) if len(params) == 0 or params[0] != self._session: - logging.warn('Raising NotImplemented') + LOG.debug(_('Raising NotImplemented')) raise NotImplementedError('Call to XenAPI without using .xenapi') def _check_arg_count(self, params, expected): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 9d1b51848..1e9448a26 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -19,7 +19,6 @@ Helper methods for operations related to the management of VM records and their attributes like VDIs, VIFs, as well as their lookup functions. """ -import logging import pickle import urllib from xml.dom import minidom @@ -27,6 +26,7 @@ from xml.dom import minidom from eventlet import event from nova import exception from nova import flags +from nova import log as logging from nova import utils from nova.auth.manager import AuthManager from nova.compute import instance_types @@ -37,6 +37,7 @@ from nova.virt.xenapi.volume_utils import StorageError FLAGS = flags.FLAGS +LOG = logging.getLogger("nova.virt.xenapi.vm_utils") XENAPI_POWER_STATE = { 'Halted': power_state.SHUTDOWN, @@ -121,9 +122,9 @@ class VMHelper(HelperBase): rec['HVM_boot_params'] = {'order': 'dc'} rec['platform'] = {'acpi': 'true', 'apic': 'true', 'pae': 'true', 'viridian': 'true'} - logging.debug('Created VM %s...', instance.name) + LOG.debug(_('Created VM %s...'), instance.name) vm_ref = session.call_xenapi('VM.create', rec) - logging.debug(_('Created VM %s as %s.'), instance.name, vm_ref) + LOG.debug(_('Created VM %s as %s.'), instance.name, vm_ref) return vm_ref @classmethod @@ -143,10 +144,9 @@ class VMHelper(HelperBase): vbd_rec['qos_algorithm_type'] = '' vbd_rec['qos_algorithm_params'] = {} vbd_rec['qos_supported_algorithms'] = [] - logging.debug(_('Creating VBD for VM %s, VDI %s ... '), - vm_ref, vdi_ref) + LOG.debug(_('Creating VBD for VM %s, VDI %s ... '), vm_ref, vdi_ref) vbd_ref = session.call_xenapi('VBD.create', vbd_rec) - logging.debug(_('Created VBD %s for VM %s, VDI %s.'), vbd_ref, vm_ref, + LOG.debug(_('Created VBD %s for VM %s, VDI %s.'), vbd_ref, vm_ref, vdi_ref) return vbd_ref @@ -161,7 +161,7 @@ class VMHelper(HelperBase): if vbd_rec['userdevice'] == str(number): return vbd except cls.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) raise StorageError(_('VBD not found in instance %s') % vm_ref) @classmethod @@ -170,7 +170,7 @@ class VMHelper(HelperBase): try: vbd_ref = session.call_xenapi('VBD.unplug', vbd_ref) except cls.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) if exc.details[0] != 'DEVICE_ALREADY_DETACHED': raise StorageError(_('Unable to unplug VBD %s') % vbd_ref) @@ -183,7 +183,7 @@ class VMHelper(HelperBase): #with Josh Kearney session.wait_for_task(0, task) except cls.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) raise StorageError(_('Unable to destroy VBD %s') % vbd_ref) @classmethod @@ -199,11 +199,11 @@ class VMHelper(HelperBase): vif_rec['other_config'] = {} vif_rec['qos_algorithm_type'] = '' vif_rec['qos_algorithm_params'] = {} - logging.debug(_('Creating VIF for VM %s, network %s.'), vm_ref, - network_ref) + LOG.debug(_('Creating VIF for VM %s, network %s.'), vm_ref, + network_ref) vif_ref = session.call_xenapi('VIF.create', vif_rec) - logging.debug(_('Created VIF %s for VM %s, network %s.'), vif_ref, - vm_ref, network_ref) + LOG.debug(_('Created VIF %s for VM %s, network %s.'), vif_ref, + vm_ref, network_ref) return vif_ref @classmethod @@ -213,8 +213,7 @@ class VMHelper(HelperBase): """ #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added - logging.debug(_("Snapshotting VM %s with label '%s'..."), - vm_ref, label) + LOG.debug(_("Snapshotting VM %s with label '%s'..."), vm_ref, label) vm_vdi_ref, vm_vdi_rec = get_vdi_for_vm_safely(session, vm_ref) vm_vdi_uuid = vm_vdi_rec["uuid"] @@ -227,8 +226,8 @@ class VMHelper(HelperBase): template_vdi_rec = get_vdi_for_vm_safely(session, template_vm_ref)[1] template_vdi_uuid = template_vdi_rec["uuid"] - logging.debug(_('Created snapshot %s from VM %s.'), template_vm_ref, - vm_ref) + LOG.debug(_('Created snapshot %s from VM %s.'), template_vm_ref, + vm_ref) parent_uuid = wait_for_vhd_coalesce( session, instance_id, sr_ref, vm_vdi_ref, original_parent_uuid) @@ -241,8 +240,7 @@ class VMHelper(HelperBase): """ Requests that the Glance plugin bundle the specified VDIs and push them into Glance using the specified human-friendly name. """ - logging.debug(_("Asking xapi to upload %s as '%s'"), - vdi_uuids, image_name) + LOG.debug(_("Asking xapi to upload %s as '%s'"), vdi_uuids, image_name) params = {'vdi_uuids': vdi_uuids, 'image_name': image_name, @@ -260,7 +258,7 @@ class VMHelper(HelperBase): """ url = images.image_url(image) access = AuthManager().get_access_key(user, project) - logging.debug("Asking xapi to fetch %s as %s", url, access) + LOG.debug(_("Asking xapi to fetch %s as %s"), url, access) fn = (type != ImageType.KERNEL_RAMDISK) and 'get_vdi' or 'get_kernel' args = {} args['src_url'] = url @@ -278,7 +276,7 @@ class VMHelper(HelperBase): @classmethod def lookup_image(cls, session, vdi_ref): - logging.debug("Looking up vdi %s for PV kernel", vdi_ref) + LOG.debug(_("Looking up vdi %s for PV kernel"), vdi_ref) fn = "is_vdi_pv" args = {} args['vdi-ref'] = vdi_ref @@ -289,7 +287,7 @@ class VMHelper(HelperBase): pv = True elif pv_str.lower() == 'false': pv = False - logging.debug("PV Kernel in VDI:%d", pv) + LOG.debug(_("PV Kernel in VDI:%d"), pv) return pv @classmethod @@ -317,10 +315,9 @@ class VMHelper(HelperBase): vdi = session.get_xenapi().VBD.get_VDI(vbd) # Test valid VDI record = session.get_xenapi().VDI.get_record(vdi) - logging.debug(_('VDI %s is still available'), - record['uuid']) + LOG.debug(_('VDI %s is still available'), record['uuid']) except cls.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) else: vdis.append(vdi) if len(vdis) > 0: @@ -331,10 +328,10 @@ class VMHelper(HelperBase): @classmethod def compile_info(cls, record): """Fill record with VM status information""" - logging.info(_("(VM_UTILS) xenserver vm state -> |%s|"), - record['power_state']) - logging.info(_("(VM_UTILS) xenapi power_state -> |%s|"), - XENAPI_POWER_STATE[record['power_state']]) + LOG.info(_("(VM_UTILS) xenserver vm state -> |%s|"), + record['power_state']) + LOG.info(_("(VM_UTILS) xenapi power_state -> |%s|"), + XENAPI_POWER_STATE[record['power_state']]) return {'state': XENAPI_POWER_STATE[record['power_state']], 'max_mem': long(record['memory_static_max']) >> 10, 'mem': long(record['memory_dynamic_max']) >> 10, @@ -388,11 +385,9 @@ def get_vhd_parent(session, vdi_rec): """ if 'vhd-parent' in vdi_rec['sm_config']: parent_uuid = vdi_rec['sm_config']['vhd-parent'] - #NOTE(sirp): changed xenapi -> get_xenapi() parent_ref = session.get_xenapi().VDI.get_by_uuid(parent_uuid) parent_rec = session.get_xenapi().VDI.get_record(parent_ref) - #NOTE(sirp): changed log -> logging - logging.debug(_("VHD %s has parent %s"), vdi_rec['uuid'], parent_ref) + LOG.debug(_("VHD %s has parent %s"), vdi_rec['uuid'], parent_ref) return parent_ref, parent_rec else: return None @@ -409,7 +404,7 @@ def get_vhd_parent_uuid(session, vdi_ref): def scan_sr(session, instance_id, sr_ref): - logging.debug(_("Re-scanning SR %s"), sr_ref) + LOG.debug(_("Re-scanning SR %s"), sr_ref) task = session.call_xenapi('Async.SR.scan', sr_ref) session.wait_for_task(instance_id, task) @@ -433,10 +428,9 @@ def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, scan_sr(session, instance_id, sr_ref) parent_uuid = get_vhd_parent_uuid(session, vdi_ref) if original_parent_uuid and (parent_uuid != original_parent_uuid): - logging.debug( - _("Parent %s doesn't match original parent %s, " - "waiting for coalesce..."), - parent_uuid, original_parent_uuid) + LOG.debug(_("Parent %s doesn't match original parent %s, " + "waiting for coalesce..."), parent_uuid, + original_parent_uuid) else: done.send(parent_uuid) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 76f31635a..b35153f90 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -18,10 +18,9 @@ Management class for VM-related functions (spawn, reboot, etc). """ -import logging - from nova import db from nova import context +from nova import log as logging from nova import exception from nova import utils @@ -31,6 +30,8 @@ from nova.virt.xenapi.network_utils import NetworkHelper from nova.virt.xenapi.vm_utils import VMHelper from nova.virt.xenapi.vm_utils import ImageType +XenAPI = None +LOG = logging.getLogger("nova.virt.xenapi.vmops") class VMOps(object): """ @@ -92,10 +93,9 @@ class VMOps(object): if network_ref: VMHelper.create_vif(self._session, vm_ref, network_ref, instance.mac_address) - logging.debug(_('Starting VM %s...'), vm_ref) + LOG.debug(_('Starting VM %s...'), vm_ref) self._session.call_xenapi('VM.start', vm_ref, False, False) - logging.info(_('Spawning VM %s created %s.'), instance.name, - vm_ref) + LOG.info(_('Spawning VM %s created %s.'), instance.name, vm_ref) # NOTE(armando): Do we really need to do this in virt? timer = utils.LoopingCall(f=None) @@ -106,12 +106,12 @@ class VMOps(object): db.instance_set_state(context.get_admin_context(), instance['id'], state) if state == power_state.RUNNING: - logging.debug(_('Instance %s: booted'), instance['name']) + LOG.debug(_('Instance %s: booted'), instance['name']) timer.stop() except Exception, exc: - logging.warn(exc) - logging.exception(_('instance %s: failed to boot'), - instance['name']) + LOG.warn(exc) + LOG.exception(_('instance %s: failed to boot'), + instance['name']) db.instance_set_state(context.get_admin_context(), instance['id'], power_state.SHUTDOWN) @@ -194,7 +194,7 @@ class VMOps(object): task = self._session.call_xenapi('Async.VM.hard_shutdown', vm) self._session.wait_for_task(instance.id, task) except self.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) # Disk clean-up if vdis: @@ -203,20 +203,20 @@ class VMOps(object): task = self._session.call_xenapi('Async.VDI.destroy', vdi) self._session.wait_for_task(instance.id, task) except self.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) # VM Destroy try: task = self._session.call_xenapi('Async.VM.destroy', vm) self._session.wait_for_task(instance.id, task) except self.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) def _wait_with_callback(self, instance_id, task, callback): ret = None try: ret = self._session.wait_for_task(instance_id, task) except XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) callback(ret) def pause(self, instance, callback): diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 1ca813bcf..d95859225 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -21,16 +21,17 @@ and storage repositories import re import string -import logging from nova import db from nova import context from nova import exception from nova import flags +from nova import log as logging from nova import utils from nova.virt.xenapi import HelperBase FLAGS = flags.FLAGS +LOG = logging.getLogger("nova.virt.xenapi.volume_utils") class StorageError(Exception): @@ -53,7 +54,7 @@ class VolumeHelper(HelperBase): """ sr_ref = session.get_xenapi().SR.get_by_name_label(label) if len(sr_ref) == 0: - logging.debug('Introducing %s...', label) + LOG.debug(_('Introducing %s...'), label) record = {} if 'chapuser' in info and 'chappassword' in info: record = {'target': info['targetHost'], @@ -70,10 +71,10 @@ class VolumeHelper(HelperBase): session.get_xenapi_host(), record, '0', label, description, 'iscsi', '', False, {}) - logging.debug('Introduced %s as %s.', label, sr_ref) + LOG.debug(_('Introduced %s as %s.'), label, sr_ref) return sr_ref except cls.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) raise StorageError(_('Unable to create Storage Repository')) else: return sr_ref[0] @@ -85,32 +86,32 @@ class VolumeHelper(HelperBase): vdi_ref = session.get_xenapi().VBD.get_VDI(vbd_ref) sr_ref = session.get_xenapi().VDI.get_SR(vdi_ref) except cls.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) raise StorageError(_('Unable to find SR from VBD %s') % vbd_ref) return sr_ref @classmethod def destroy_iscsi_storage(cls, session, sr_ref): """Forget the SR whilst preserving the state of the disk""" - logging.debug("Forgetting SR %s ... ", sr_ref) + LOG.debug(_("Forgetting SR %s ... "), sr_ref) pbds = [] try: pbds = session.get_xenapi().SR.get_PBDs(sr_ref) except cls.XenAPI.Failure, exc: - logging.warn('Ignoring exception %s when getting PBDs for %s', - exc, sr_ref) + LOG.warn(_('Ignoring exception %s when getting PBDs for %s'), + exc, sr_ref) for pbd in pbds: try: session.get_xenapi().PBD.unplug(pbd) except cls.XenAPI.Failure, exc: - logging.warn('Ignoring exception %s when unplugging PBD %s', - exc, pbd) + LOG.warn(_('Ignoring exception %s when unplugging PBD %s'), + exc, pbd) try: session.get_xenapi().SR.forget(sr_ref) - logging.debug("Forgetting SR %s done.", sr_ref) + LOG.debug(_("Forgetting SR %s done."), sr_ref) except cls.XenAPI.Failure, exc: - logging.warn('Ignoring exception %s when forgetting SR %s', - exc, sr_ref) + LOG.warn(_('Ignoring exception %s when forgetting SR %s'), exc, + sr_ref) @classmethod def introduce_vdi(cls, session, sr_ref): @@ -118,12 +119,12 @@ class VolumeHelper(HelperBase): try: vdis = session.get_xenapi().SR.get_VDIs(sr_ref) except cls.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) raise StorageError(_('Unable to introduce VDI on SR %s') % sr_ref) try: vdi_rec = session.get_xenapi().VDI.get_record(vdis[0]) except cls.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) raise StorageError(_('Unable to get record' ' of VDI %s on') % vdis[0]) else: @@ -141,7 +142,7 @@ class VolumeHelper(HelperBase): vdi_rec['xenstore_data'], vdi_rec['sm_config']) except cls.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) raise StorageError(_('Unable to introduce VDI for SR %s') % sr_ref) @@ -165,11 +166,8 @@ class VolumeHelper(HelperBase): target_host = _get_target_host(iscsi_portal) target_port = _get_target_port(iscsi_portal) target_iqn = _get_iqn(iscsi_name, volume_id) - logging.debug('(vol_id,number,host,port,iqn): (%s,%s,%s,%s)', - volume_id, - target_host, - target_port, - target_iqn) + LOG.debug('(vol_id,number,host,port,iqn): (%s,%s,%s,%s)', + volume_id, target_host, target_port, target_iqn) if (device_number < 0) or \ (volume_id is None) or \ (target_host is None) or \ @@ -196,7 +194,7 @@ class VolumeHelper(HelperBase): elif re.match('^[0-9]+$', mountpoint): return string.atoi(mountpoint, 10) else: - logging.warn('Mountpoint cannot be translated: %s', mountpoint) + LOG.warn(_('Mountpoint cannot be translated: %s'), mountpoint) return -1 @@ -253,7 +251,7 @@ def _get_target(volume_id): "sendtargets -p %s" % volume_ref['host']) except exception.ProcessExecutionError, exc: - logging.warn(exc) + LOG.exception(exc) else: targets = r.splitlines() if len(_e) == 0 and len(targets) == 1: diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index fdeb2506c..189f968c6 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -17,14 +17,17 @@ """ Management class for Storage-related functions (attach, detach, etc). """ -import logging from nova import exception +from nova import log as logging from nova.virt.xenapi.vm_utils import VMHelper from nova.virt.xenapi.volume_utils import VolumeHelper from nova.virt.xenapi.volume_utils import StorageError +LOG = logging.getLogger("nova.virt.xenapi.volumeops") + + class VolumeOps(object): """ Management class for Volume-related tasks @@ -45,8 +48,8 @@ class VolumeOps(object): raise exception.NotFound(_('Instance %s not found') % instance_name) # NOTE: No Resource Pool concept so far - logging.debug(_("Attach_volume: %s, %s, %s"), - instance_name, device_path, mountpoint) + LOG.debug(_("Attach_volume: %s, %s, %s"), + instance_name, device_path, mountpoint) # Create the iSCSI SR, and the PDB through which hosts access SRs. # But first, retrieve target info, like Host, IQN, LUN and SCSIID vol_rec = VolumeHelper.parse_volume_info(device_path, mountpoint) @@ -61,7 +64,7 @@ class VolumeOps(object): try: vdi_ref = VolumeHelper.introduce_vdi(self._session, sr_ref) except StorageError, exc: - logging.warn(exc) + LOG.exception(exc) VolumeHelper.destroy_iscsi_storage(self._session, sr_ref) raise Exception(_('Unable to create VDI on SR %s for instance %s') % (sr_ref, @@ -73,7 +76,7 @@ class VolumeOps(object): vol_rec['deviceNumber'], False) except self.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) VolumeHelper.destroy_iscsi_storage(self._session, sr_ref) raise Exception(_('Unable to use SR %s for instance %s') % (sr_ref, @@ -84,13 +87,13 @@ class VolumeOps(object): vbd_ref) self._session.wait_for_task(vol_rec['deviceNumber'], task) except self.XenAPI.Failure, exc: - logging.warn(exc) + LOG.exception(exc) VolumeHelper.destroy_iscsi_storage(self._session, sr_ref) raise Exception(_('Unable to attach volume to instance %s') % instance_name) - logging.info(_('Mountpoint %s attached to instance %s'), - mountpoint, instance_name) + LOG.info(_('Mountpoint %s attached to instance %s'), + mountpoint, instance_name) def detach_volume(self, instance_name, mountpoint): """Detach volume storage to VM instance""" @@ -100,13 +103,13 @@ class VolumeOps(object): raise exception.NotFound(_('Instance %s not found') % instance_name) # Detach VBD from VM - logging.debug(_("Detach_volume: %s, %s"), instance_name, mountpoint) + LOG.debug(_("Detach_volume: %s, %s"), instance_name, mountpoint) device_number = VolumeHelper.mountpoint_to_number(mountpoint) try: vbd_ref = VMHelper.find_vbd_by_number(self._session, vm_ref, device_number) except StorageError, exc: - logging.warn(exc) + LOG.exception(exc) raise Exception(_('Unable to locate volume %s') % mountpoint) else: try: @@ -114,13 +117,13 @@ class VolumeOps(object): vbd_ref) VMHelper.unplug_vbd(self._session, vbd_ref) except StorageError, exc: - logging.warn(exc) + LOG.exception(exc) raise Exception(_('Unable to detach volume %s') % mountpoint) try: VMHelper.destroy_vbd(self._session, vbd_ref) except StorageError, exc: - logging.warn(exc) + LOG.exception(exc) # Forget SR VolumeHelper.destroy_iscsi_storage(self._session, sr_ref) - logging.info(_('Mountpoint %s detached from instance %s'), - mountpoint, instance_name) + LOG.info(_('Mountpoint %s detached from instance %s'), + mountpoint, instance_name) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index f17c8f39d..a798f9e33 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -50,7 +50,6 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block. :iqn_prefix: IQN Prefix, e.g. 'iqn.2010-10.org.openstack' """ -import logging import sys import xmlrpclib @@ -61,9 +60,14 @@ from nova import context from nova import db from nova import utils from nova import flags +from nova import log as logging from nova.virt.xenapi.vmops import VMOps from nova.virt.xenapi.volumeops import VolumeOps + +LOG = logging.getLogger("nova.virt.xenapi") + + FLAGS = flags.FLAGS flags.DEFINE_string('xenapi_connection_url', @@ -248,7 +252,7 @@ class XenAPISession(object): return elif status == "success": result = self._session.xenapi.task.get_result(task) - logging.info(_("Task [%s] %s status: success %s") % ( + LOG.info(_("Task [%s] %s status: success %s") % ( name, task, result)) @@ -256,7 +260,7 @@ class XenAPISession(object): else: error_info = self._session.xenapi.task.get_error_info(task) action["error"] = str(error_info) - logging.warn(_("Task [%s] %s status: %s %s") % ( + LOG.warn(_("Task [%s] %s status: %s %s") % ( name, task, status, @@ -264,7 +268,7 @@ class XenAPISession(object): done.send_exception(self.XenAPI.Failure(error_info)) db.instance_action_create(context.get_admin_context(), action) except self.XenAPI.Failure, exc: - logging.warn(exc) + LOG.warn(exc) done.send_exception(*sys.exc_info()) def _unwrap_plugin_exceptions(self, func, *args, **kwargs): @@ -272,7 +276,7 @@ class XenAPISession(object): try: return func(*args, **kwargs) except self.XenAPI.Failure, exc: - logging.debug(_("Got exception: %s"), exc) + LOG.debug(_("Got exception: %s"), exc) if (len(exc.details) == 4 and exc.details[0] == 'XENAPI_PLUGIN_EXCEPTION' and exc.details[2] == 'Failure'): @@ -285,7 +289,7 @@ class XenAPISession(object): else: raise except xmlrpclib.ProtocolError, exc: - logging.debug(_("Got exception: %s"), exc) + LOG.debug(_("Got exception: %s"), exc) raise diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 8353b9712..477e0abf4 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -20,15 +20,16 @@ Drivers for volumes. """ -import logging import os import time from nova import exception from nova import flags +from nova import log as logging from nova import utils +LOG = logging.getLogger("nova.volume.driver") FLAGS = flags.FLAGS flags.DEFINE_string('volume_group', 'nova-volumes', 'Name for the VG that will contain exported volumes') @@ -73,8 +74,8 @@ class VolumeDriver(object): tries = tries + 1 if tries >= FLAGS.num_shell_tries: raise - logging.exception(_("Recovering from a failed execute." - "Try number %s"), tries) + LOG.exception(_("Recovering from a failed execute. " + "Try number %s"), tries) time.sleep(tries ** 2) def check_for_setup_error(self): @@ -205,7 +206,7 @@ class FakeAOEDriver(AOEDriver): @staticmethod def fake_execute(cmd, *_args, **_kwargs): """Execute that simply logs the command.""" - logging.debug(_("FAKE AOE: %s"), cmd) + LOG.debug(_("FAKE AOE: %s"), cmd) return (None, None) @@ -310,5 +311,5 @@ class FakeISCSIDriver(ISCSIDriver): @staticmethod def fake_execute(cmd, *_args, **_kwargs): """Execute that simply logs the command.""" - logging.debug(_("FAKE ISCSI: %s"), cmd) + LOG.debug(_("FAKE ISCSI: %s"), cmd) return (None, None) diff --git a/nova/volume/manager.py b/nova/volume/manager.py index 966334c50..6348539c5 100644 --- a/nova/volume/manager.py +++ b/nova/volume/manager.py @@ -42,17 +42,18 @@ intact. """ -import logging import datetime from nova import context from nova import exception from nova import flags +from nova import log as logging from nova import manager from nova import utils +LOG = logging.getLogger('nova.volume.manager') FLAGS = flags.FLAGS flags.DEFINE_string('storage_availability_zone', 'nova', @@ -81,7 +82,7 @@ class VolumeManager(manager.Manager): self.driver.check_for_setup_error() ctxt = context.get_admin_context() volumes = self.db.volume_get_all_by_host(ctxt, self.host) - logging.debug(_("Re-exporting %s volumes"), len(volumes)) + LOG.debug(_("Re-exporting %s volumes"), len(volumes)) for volume in volumes: self.driver.ensure_export(ctxt, volume) @@ -89,7 +90,7 @@ class VolumeManager(manager.Manager): """Creates and exports the volume.""" context = context.elevated() volume_ref = self.db.volume_get(context, volume_id) - logging.info(_("volume %s: creating"), volume_ref['name']) + LOG.info(_("volume %s: creating"), volume_ref['name']) self.db.volume_update(context, volume_id, @@ -98,18 +99,18 @@ class VolumeManager(manager.Manager): # before passing it to the driver. volume_ref['host'] = self.host - logging.debug(_("volume %s: creating lv of size %sG"), - volume_ref['name'], volume_ref['size']) + LOG.debug(_("volume %s: creating lv of size %sG"), volume_ref['name'], + volume_ref['size']) self.driver.create_volume(volume_ref) - logging.debug(_("volume %s: creating export"), volume_ref['name']) + LOG.debug(_("volume %s: creating export"), volume_ref['name']) self.driver.create_export(context, volume_ref) now = datetime.datetime.utcnow() self.db.volume_update(context, volume_ref['id'], {'status': 'available', 'launched_at': now}) - logging.debug(_("volume %s: created successfully"), volume_ref['name']) + LOG.debug(_("volume %s: created successfully"), volume_ref['name']) return volume_id def delete_volume(self, context, volume_id): @@ -120,12 +121,12 @@ class VolumeManager(manager.Manager): raise exception.Error(_("Volume is still attached")) if volume_ref['host'] != self.host: raise exception.Error(_("Volume is not local to this node")) - logging.debug(_("volume %s: removing export"), volume_ref['name']) + LOG.debug(_("volume %s: removing export"), volume_ref['name']) self.driver.remove_export(context, volume_ref) - logging.debug(_("volume %s: deleting"), volume_ref['name']) + LOG.debug(_("volume %s: deleting"), volume_ref['name']) self.driver.delete_volume(volume_ref) self.db.volume_destroy(context, volume_id) - logging.debug(_("volume %s: deleted successfully"), volume_ref['name']) + LOG.debug(_("volume %s: deleted successfully"), volume_ref['name']) return True def setup_compute_volume(self, context, volume_id): diff --git a/nova/wsgi.py b/nova/wsgi.py index b5d6b96c1..6336912e4 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -22,7 +22,6 @@ Utility methods for working with WSGI servers """ import json -import logging import sys from xml.dom import minidom @@ -35,18 +34,23 @@ import webob import webob.dec import webob.exc +from nova import log as logging -logging.getLogger("routes.middleware").addHandler(logging.StreamHandler()) +# TODO(todd): should this just piggyback the handler for root logger +# since we usually log to syslog, but changes if not daemonzied? +logging.getLogger("routes.middleware").addHandler(logging.StreamHandler()) class Server(object): """Server class to manage multiple WSGI sockets and applications.""" def __init__(self, threads=1000): + logging.basicConfig() self.pool = eventlet.GreenPool(threads) def start(self, application, port, host='0.0.0.0', backlog=128): """Run a WSGI server with the given application.""" + logging.audit("Starting %s on %s:%s", sys.argv[0], host, port) socket = eventlet.listen((host, port), backlog=backlog) self.pool.spawn_n(self._run, application, socket) -- cgit From b9576a9f73195656f4a0a1327cd6bee3c4a6b6c9 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 4 Jan 2011 00:26:41 -0500 Subject: Final few log tweaks, i18n, levels, including contexts, etc. --- nova/api/ec2/__init__.py | 14 ++++---- nova/api/ec2/cloud.py | 4 +-- nova/api/openstack/__init__.py | 2 +- nova/auth/ldapdriver.py | 3 +- nova/auth/manager.py | 3 +- nova/compute/manager.py | 77 +++++++++++++++++++++--------------------- nova/compute/monitor.py | 2 +- nova/exception.py | 1 + nova/network/manager.py | 14 ++++---- nova/objectstore/handler.py | 2 +- nova/rpc.py | 5 +-- nova/service.py | 3 -- nova/virt/libvirt_conn.py | 10 +++--- nova/virt/xenapi/fake.py | 2 +- nova/virt/xenapi/vmops.py | 1 + nova/wsgi.py | 2 +- 16 files changed, 72 insertions(+), 73 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 4dd2e55cd..2fa1f636c 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -130,7 +130,7 @@ class Lockout(wsgi.Middleware): failures_key = "authfailures-%s" % access_key failures = int(self.mc.get(failures_key) or 0) if failures >= FLAGS.lockout_attempts: - detail = "Too many failed authentications." + detail = _("Too many failed authentications.") raise webob.exc.HTTPForbidden(detail=detail) res = req.get_response(self.application) if res.status_int == 403: @@ -139,13 +139,14 @@ class Lockout(wsgi.Middleware): # NOTE(vish): To use incr, failures has to be a string. self.mc.set(failures_key, '1', time=FLAGS.lockout_window * 60) elif failures >= FLAGS.lockout_attempts: - LOG.warn('Access key %s has had %d failed authentications' - ' and will be locked out for %d minutes.', + LOG.warn(_('Access key %s has had %d failed authentications' + ' and will be locked out for %d minutes.'), access_key, failures, FLAGS.lockout_minutes) self.mc.set(failures_key, str(failures), time=FLAGS.lockout_minutes * 60) return res + class Authenticate(wsgi.Middleware): """Authenticate an EC2 request and add 'ec2.context' to WSGI environ.""" @@ -297,8 +298,9 @@ class Authorizer(wsgi.Middleware): if self._matches_any_role(context, allowed_roles): return self.application else: - LOG.audit("Unauthorized request for controller=%s and action=%s", - controller_name, action, context=context) + LOG.audit(_("Unauthorized request for controller=%s " + "and action=%s"), controller_name, action, + context=context) raise webob.exc.HTTPUnauthorized() def _matches_any_role(self, context, roles): @@ -337,7 +339,7 @@ class Executor(wsgi.Application): LOG.info(_('NotFound raised: %s'), str(ex), context=context) return self._error(req, context, type(ex).__name__, str(ex)) except exception.ApiError as ex: - LOG.exception('ApiError raised', context=context) + LOG.exception(_('ApiError raised: %s'), str(ex), context=context) if ex.code: return self._error(req, context, ex.code, str(ex)) else: diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index d0db08db7..2fb4455f2 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -609,7 +609,7 @@ class CloudController(object): 'volumeId': volume_ref['id']} def detach_volume(self, context, volume_id, **kwargs): - LOG.audit("Detach volume %s", volume_id, context=context) + LOG.audit(_("Detach volume %s"), volume_id, context=context) volume_ref = db.volume_get_by_ec2_id(context, volume_id) instance_ref = db.volume_get_instance(context.elevated(), volume_ref['id']) @@ -893,7 +893,7 @@ class CloudController(object): return {'imagesSet': images} def deregister_image(self, context, image_id, **kwargs): - LOG.audit("De-registering image %s", image_id, context=context) + LOG.audit(_("De-registering image %s"), image_id, context=context) self.image_service.deregister(context, image_id) return {'imageId': image_id} diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 7e1c03d9f..ad203c51f 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -83,7 +83,7 @@ class APIRouter(wsgi.Router): server_members = {'action': 'POST'} if FLAGS.allow_admin_api: - LOG.debug("Including admin operations in API.") + LOG.debug(_("Including admin operations in API.")) server_members['pause'] = 'POST' server_members['unpause'] = 'POST' server_members["diagnostics"] = "GET" diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 3e0837ba8..c8de20028 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -65,7 +65,6 @@ flags.DEFINE_string('ldap_netadmin', flags.DEFINE_string('ldap_developer', 'cn=developers,ou=Groups,dc=example,dc=com', 'cn for Developers') - LOG = logging.getLogger("nova.ldapdriver") @@ -506,7 +505,7 @@ class LdapDriver(object): self.conn.modify_s(group_dn, attr) except self.ldap.OBJECT_CLASS_VIOLATION: LOG.debug(_("Attempted to remove the last member of a group. " - "Deleting the group at %s instead."), group_dn) + "Deleting the group at %s instead."), group_dn) self.__delete_group(group_dn) def __remove_from_all(self, uid): diff --git a/nova/auth/manager.py b/nova/auth/manager.py index bfac7fc2a..5685ae5e2 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -70,8 +70,7 @@ flags.DEFINE_string('credential_rc_file', '%src', flags.DEFINE_string('auth_driver', 'nova.auth.dbdriver.DbDriver', 'Driver that auth manager uses') - -LOG = logging.getLogger('nova.authmanager') +LOG = logging.getLogger('nova.auth.manager') class AuthBase(object): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index cc5724346..0098ded74 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -52,8 +52,7 @@ flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection', flags.DEFINE_string('stub_network', False, 'Stub network related code') - -LOG = logging.getLogger('nova.computemanager') +LOG = logging.getLogger('nova.compute.manager') class ComputeManager(manager.Manager): @@ -114,7 +113,8 @@ class ComputeManager(manager.Manager): instance_ref = self.db.instance_get(context, instance_id) if instance_ref['name'] in self.driver.list_instances(): raise exception.Error(_("Instance has already been created")) - LOG.debug(_("instance %s: starting..."), instance_id) + LOG.audit(_("instance %s//%s: starting..."), + instance_ref['internal_id'], instance_id, context=context) self.db.instance_update(context, instance_id, {'host': self.host}) @@ -152,8 +152,9 @@ class ComputeManager(manager.Manager): instance_id, {'launched_at': now}) except Exception: # pylint: disable-msg=W0702 - LOG.exception(_("instance %s: Failed to spawn"), - instance_ref['name']) + LOG.exception(_("instance %s//%s: Failed to spawn"), + instance_ref['internal_id'], instance_id, + context=context) self.db.instance_set_state(context, instance_id, power_state.SHUTDOWN) @@ -193,8 +194,6 @@ class ComputeManager(manager.Manager): self.network_manager.deallocate_fixed_ip(context.elevated(), address) - LOG.debug(_("instance %s: terminating"), instance_id, context=context) - volumes = instance_ref.get('volumes', []) or [] for volume in volumes: self.detach_volume(context, instance_id, volume['id']) @@ -217,14 +216,13 @@ class ComputeManager(manager.Manager): instance_id, context=context) if instance_ref['state'] != power_state.RUNNING: - LOG.warn(_('trying to reboot a non-running ' + .warn(_('trying to reboot a non-running ' 'instance: %s (state: %s excepted: %s)'), instance_ref['internal_id'], instance_ref['state'], power_state.RUNNING, context=context) - LOG.debug(_('instance %s: rebooting'), instance_ref['name']) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -244,13 +242,14 @@ class ComputeManager(manager.Manager): # potentially? self._update_state(context, instance_id) - logging.debug(_('instance %s: snapshotting'), instance_ref['name']) + LOG.audit(_('instance %s//%s: snapshotting'), + instance_ref['internal_id'], instance_id, context=context) if instance_ref['state'] != power_state.RUNNING: - logging.warn(_('trying to snapshot a non-running ' - 'instance: %s (state: %s excepted: %s)'), - instance_ref['internal_id'], - instance_ref['state'], - power_state.RUNNING) + LOG.warn(_('trying to snapshot a non-running ' + 'instance: %s//%s (state: %s excepted: %s)'), + instance_ref['internal_id'], instance_id, + instance_ref['state'], + power_state.RUNNING) self.driver.snapshot(instance_ref, name) @@ -259,9 +258,8 @@ class ComputeManager(manager.Manager): """Rescue an instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - - LOG.audit(_('instance %s: rescuing'), instance_ref['internal_id'], - context=context) + LOG.audit(_('instance %s//%s: rescuing'), instance_ref['internal_id'], + instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -276,8 +274,8 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_('instance %s: unrescuing'), instance_ref['internal_id'], - context=context) + LOG.audit(_('instance %s//%s: unrescuing'), + instance_ref['internal_id'], instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -295,9 +293,8 @@ class ComputeManager(manager.Manager): """Pause an instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - - LOG.debug(_('instance %s: pausing'), instance_ref['internal_id'], - context=context) + LOG.audit(_('instance %s//%s: pausing'), instance_ref['internal_id'], + instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -313,9 +310,8 @@ class ComputeManager(manager.Manager): """Unpause a paused instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - - LOG.debug(_('instance %s: unpausing'), instance_ref['internal_id'], - context=context) + LOG.audit(_('instance %s//%s: unpausing'), + instance_ref['internal_id'], instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -332,8 +328,9 @@ class ComputeManager(manager.Manager): instance_ref = self.db.instance_get(context, instance_id) if instance_ref["state"] == power_state.RUNNING: - logging.debug(_("instance %s: retrieving diagnostics"), - instance_ref["internal_id"]) + LOG.audit(_("instance %s//%s: retrieving diagnostics"), + instance_ref["internal_id"], instance_id, + context=context) return self.driver.get_diagnostics(instance_ref) @exception.wrap_exception @@ -342,8 +339,8 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug(_('instance %s: suspending'), - instance_ref['internal_id']) + LOG.audit(_('instance %s//%s: suspending'), + instance_ref['internal_id'], instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'suspending') @@ -359,7 +356,8 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug(_('instance %s: resuming'), instance_ref['internal_id']) + LOG.audit(_('instance %s//%s: resuming'), instance_ref['internal_id'], + instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'resuming') @@ -374,18 +372,18 @@ class ComputeManager(manager.Manager): """Send the console output for an instance.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_("Get console output instance %s//%s"), + LOG.audit(_("Get console output for instance %s//%s"), instance_ref['internal_id'], instance_id, context=context) - return self.driver.get_console_output(instance_ref) @exception.wrap_exception def attach_volume(self, context, instance_id, volume_id, mountpoint): """Attach a volume to an instance.""" context = context.elevated() - LOG.audit(_("instance %s: attaching volume %s to %s"), instance_id, - volume_id, mountpoint, context=context) instance_ref = self.db.instance_get(context, instance_id) + LOG.audit(_("instance %s//%s: attaching volume %s to %s"), + instance_ref['internal_id'], instance_id, + volume_id, mountpoint, context=context) dev_path = self.volume_manager.setup_compute_volume(context, volume_id) try: @@ -400,8 +398,9 @@ class ComputeManager(manager.Manager): # NOTE(vish): The inline callback eats the exception info so we # log the traceback here and reraise the same # ecxception below. - LOG.exception(_("instance %s: attach failed %s, removing"), - instance_id, mountpoint, context=context) + LOG.exception(_("instance %s//%s: attach failed %s, removing"), + instance_ref['internal_id'], instance_id, + mountpoint, context=context) self.volume_manager.remove_compute_volume(context, volume_id) raise exc @@ -418,8 +417,8 @@ class ComputeManager(manager.Manager): volume_id, volume_ref['mountpoint'], instance_ref['internal_id'], instance_id, context=context) if instance_ref['name'] not in self.driver.list_instances(): - LOG.warn(_("Detaching volume from unknown instance %s"), - instance_ref['name'], context=context) + LOG.warn(_("Detaching volume from unknown instance %s//%s"), + instance_ref['internal_id'], instance_id, context=context) else: self.driver.detach_volume(instance_ref['name'], volume_ref['mountpoint']) diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py index cc94e44f4..14d0e8ca1 100644 --- a/nova/compute/monitor.py +++ b/nova/compute/monitor.py @@ -89,7 +89,7 @@ RRD_VALUES = { utcnow = datetime.datetime.utcnow -LOG = logging.getLogger('nova.instancemonitor') +LOG = logging.getLogger('nova.compute.monitor') def update_rrd(instance, name, data): diff --git a/nova/exception.py b/nova/exception.py index 7c6675e62..7680e534a 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -24,6 +24,7 @@ Nova-type exceptions. SHOULD include dedicated exception logging. from nova import log as logging LOG = logging.getLogger('nova.exception') + class ProcessExecutionError(IOError): def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, diff --git a/nova/network/manager.py b/nova/network/manager.py index 2b7325fd0..fd286f210 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -60,7 +60,7 @@ from nova import utils from nova import rpc -LOG = logging.getLogger("nova.networkmanager") +LOG = logging.getLogger("nova.network.manager") FLAGS = flags.FLAGS flags.DEFINE_string('flat_network_bridge', 'br100', 'Bridge for simple network instances') @@ -132,7 +132,7 @@ class NetworkManager(manager.Manager): def set_network_host(self, context, network_id): """Safely sets the host of the network.""" - LOG.debug(_("setting network host")) + LOG.debug(_("setting network host"), context=context) host = self.db.network_set_host(context, network_id, self.host) @@ -187,7 +187,7 @@ class NetworkManager(manager.Manager): def lease_fixed_ip(self, context, mac, address): """Called by dhcp-bridge when ip is leased.""" - LOG.debug(_("Leasing IP %s"), address) + LOG.debug(_("Leasing IP %s"), address, context=context) fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] if not instance_ref: @@ -202,11 +202,12 @@ class NetworkManager(manager.Manager): {'leased': True, 'updated_at': now}) if not fixed_ip_ref['allocated']: - LOG.warn(_("IP %s leased that was already deallocated"), address) + LOG.warn(_("IP %s leased that was already deallocated"), address, + context=context) def release_fixed_ip(self, context, mac, address): """Called by dhcp-bridge when ip is released.""" - LOG.debug("Releasing IP %s", address) + LOG.debug("Releasing IP %s", address, context=context) fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] if not instance_ref: @@ -216,7 +217,8 @@ class NetworkManager(manager.Manager): raise exception.Error(_("IP %s released from bad mac %s vs %s") % (address, instance_ref['mac_address'], mac)) if not fixed_ip_ref['leased']: - LOG.warn(_("IP %s released that was not leased"), address) + LOG.warn(_("IP %s released that was not leased"), address, + context=context) self.db.fixed_ip_update(context, fixed_ip_ref['address'], {'leased': False}) diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py index 8e9235da4..bc26fd3c5 100644 --- a/nova/objectstore/handler.py +++ b/nova/objectstore/handler.py @@ -134,7 +134,7 @@ def get_context(request): headers=request.getAllHeaders(), check_type='s3') rv = context.RequestContext(user, project) - LOG.audit("Authenticated request", context=rv) + LOG.audit(_("Authenticated request"), context=rv) return rv except exception.Error as ex: LOG.debug(_("Authentication Failure: %s"), ex) diff --git a/nova/rpc.py b/nova/rpc.py index 02052485d..bdf2f74b3 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -96,8 +96,9 @@ class Consumer(messaging.Consumer): FLAGS.rabbit_retry_interval)) self.failed_connection = True if self.failed_connection: - LOG.exception(_("Unable to connect to AMQP server" - " after %d tries. Shutting down.") % FLAGS.rabbit_max_retries) + LOG.exception(_("Unable to connect to AMQP server " + "after %d tries. Shutting down."), + FLAGS.rabbit_max_retries) sys.exit(1) def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): diff --git a/nova/service.py b/nova/service.py index fef7f0593..a459f8df4 100644 --- a/nova/service.py +++ b/nova/service.py @@ -209,9 +209,6 @@ def serve(*services): FLAGS(sys.argv) logging.basicConfig() - # TODO(todd): make this pigggyback the flag-based level override method - logging.getLogger('amqplib').setLevel(logging.WARN) - if not services: services = [Service.create()] diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ac82fdadb..764ef6600 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -62,9 +62,7 @@ libvirt = None libxml2 = None Template = None - -LOG = logging.getLogger('nova.virt.libvirt_conn') - +LOG = logging.getLogger('nova.virt.libvirt_conn') FLAGS = flags.FLAGS # TODO(vish): These flags should probably go into a shared location @@ -380,7 +378,7 @@ class LibvirtConnection(object): return timer.start(interval=0.5, now=True) def _flush_xen_console(self, virsh_output): - LOG.info('virsh said: %r', virsh_output) + LOG.info(_('virsh said: %r'), virsh_output) virsh_output = virsh_output[0].strip() if virsh_output.startswith('/dev/'): @@ -400,7 +398,7 @@ class LibvirtConnection(object): def _dump_file(self, fpath): fp = open(fpath, 'r+') contents = fp.read() - LOG.info('Contents: %r', contents) + LOG.info(_('Contents of file %s: %r'), fpath, contents) return contents @exception.wrap_exception @@ -529,7 +527,7 @@ class LibvirtConnection(object): def to_xml(self, instance, rescue=False): # TODO(termie): cache? - LOG.debug('instance %s: starting toXML method', instance['name']) + LOG.debug(_('instance %s: starting toXML method'), instance['name']) network = db.project_get_network(context.get_admin_context(), instance['project_id']) LOG.debug(_('instance %s: starting toXML method'), instance['name']) diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index f2c3a34f6..96d8f5fc8 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -244,7 +244,7 @@ class SessionBase(object): full_params = (self._session,) + params meth = getattr(self, methodname, None) if meth is None: - LOG.debug('Raising NotImplemented') + LOG.debug(_('Raising NotImplemented')) raise NotImplementedError( _('xenapi.fake does not have an implementation for %s') % methodname) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b35153f90..88350f91a 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -33,6 +33,7 @@ from nova.virt.xenapi.vm_utils import ImageType XenAPI = None LOG = logging.getLogger("nova.virt.xenapi.vmops") + class VMOps(object): """ Management class for VM-related tasks diff --git a/nova/wsgi.py b/nova/wsgi.py index 6336912e4..9c333d3ab 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -36,11 +36,11 @@ import webob.exc from nova import log as logging - # TODO(todd): should this just piggyback the handler for root logger # since we usually log to syslog, but changes if not daemonzied? logging.getLogger("routes.middleware").addHandler(logging.StreamHandler()) + class Server(object): """Server class to manage multiple WSGI sockets and applications.""" -- cgit From 45f2f563d1722d2f4d81d49de9d6a3cfd3d0fe3e Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 4 Jan 2011 00:37:47 -0500 Subject: Don't know where that LOG went... --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0098ded74..d68801146 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -216,7 +216,7 @@ class ComputeManager(manager.Manager): instance_id, context=context) if instance_ref['state'] != power_state.RUNNING: - .warn(_('trying to reboot a non-running ' + LOG.warn(_('trying to reboot a non-running ' 'instance: %s (state: %s excepted: %s)'), instance_ref['internal_id'], instance_ref['state'], -- cgit From aab31f797b41feb5b9b8856dd2df4b46435ccdbc Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 5 Jan 2011 00:59:39 -0500 Subject: Silence eventlet.wsgi.server so it doesn't go to stdout and pollute our logs. --- nova/wsgi.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index 9c333d3ab..f85503149 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -36,9 +36,10 @@ import webob.exc from nova import log as logging -# TODO(todd): should this just piggyback the handler for root logger -# since we usually log to syslog, but changes if not daemonzied? -logging.getLogger("routes.middleware").addHandler(logging.StreamHandler()) + +class NullWsgiLogger(object): + def write(*args): + pass class Server(object): @@ -63,7 +64,8 @@ class Server(object): def _run(self, application, socket): """Start a WSGI server in a new green thread.""" - eventlet.wsgi.server(socket, application, custom_pool=self.pool) + eventlet.wsgi.server(socket, application, custom_pool=self.pool, + log=NullWsgiLogger()) class Application(object): -- cgit From ada65e007e4511f63efcbfa94e51d2d41af1d315 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 5 Jan 2011 01:54:31 -0500 Subject: Better method for eventlet.wsgi.server logging. --- nova/log.py | 2 +- nova/wsgi.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/log.py b/nova/log.py index 88a961e13..97472b20b 100644 --- a/nova/log.py +++ b/nova/log.py @@ -67,7 +67,7 @@ flags.DEFINE_string('logging_exception_prefix', flags.DEFINE_list('default_log_levels', ['amqplib=WARN', 'sqlalchemy=WARN', - 'audit=INFO'], + 'eventlet.wsgi.server=WARN'], 'list of logger=LEVEL pairs') flags.DEFINE_bool('use_syslog', False, 'output to syslog') diff --git a/nova/wsgi.py b/nova/wsgi.py index f85503149..b48747be4 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -37,9 +37,15 @@ import webob.exc from nova import log as logging -class NullWsgiLogger(object): - def write(*args): - pass +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): @@ -64,8 +70,9 @@ class Server(object): 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=NullWsgiLogger()) + log=WritableLogger(logger)) class Application(object): -- cgit From b437a98738c7a564205d1b27e36b844cd54445d1 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 14:16:14 -0600 Subject: add in xs-console worker and tests. --- nova/compute/manager.py | 5 ++ nova/console/__init__.py | 11 +++ nova/console/driver.py | 59 +++++++++++++ nova/console/fake.py | 59 +++++++++++++ nova/console/manager.py | 130 +++++++++++++++++++++++++++ nova/console/xvp.conf.template | 16 ++++ nova/console/xvp.py | 193 +++++++++++++++++++++++++++++++++++++++++ nova/db/api.py | 41 +++++++++ nova/db/sqlalchemy/api.py | 81 +++++++++++++++++ nova/db/sqlalchemy/models.py | 23 ++++- nova/flags.py | 3 + nova/tests/test_console.py | 134 ++++++++++++++++++++++++++++ nova/virt/fake.py | 5 ++ nova/virt/libvirt_conn.py | 8 ++ nova/virt/xenapi_conn.py | 7 ++ 15 files changed, 774 insertions(+), 1 deletion(-) create mode 100644 nova/console/__init__.py create mode 100644 nova/console/driver.py create mode 100644 nova/console/fake.py create mode 100644 nova/console/manager.py create mode 100644 nova/console/xvp.conf.template create mode 100644 nova/console/xvp.py create mode 100644 nova/tests/test_console.py (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 70b175e7c..295e75eca 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -99,6 +99,11 @@ class ComputeManager(manager.Manager): FLAGS.network_topic, host) + + def get_console_pool_info(self, context, console_type): + return self.driver.get_console_pool_info(console_type) + + @exception.wrap_exception def refresh_security_group(self, context, security_group_id, **_kwargs): """This call passes stright through to the virtualization driver.""" diff --git a/nova/console/__init__.py b/nova/console/__init__.py new file mode 100644 index 000000000..adce8621e --- /dev/null +++ b/nova/console/__init__.py @@ -0,0 +1,11 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +""" +:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp) +===================================================== + +.. automodule:: nova.console + :platform: Unix + :synopsis: Wrapper around console proxies such as xvp to set up multitenant VM console access +.. moduleauthor:: Monsyne Dragon +""" diff --git a/nova/console/driver.py b/nova/console/driver.py new file mode 100644 index 000000000..b92765b34 --- /dev/null +++ b/nova/console/driver.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 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. + +""" +ConsoleProxy base class that all ConsoleProxies should inherit from +""" + +from nova import exception + + +class ConsoleProxy(object): + """The base class for all ConsoleProxy driver classes.""" + + @property + def console_type(self): + raise NotImplementedError("Must specify type in subclass") + + def setup_console(self, context, console): + """Sets up actual proxies""" + raise NotImplementedError("Must implement setup in subclass") + + def teardown_console(self, context, console): + """Tears down actual proxies""" + raise NotImplementedError("Must implement teardown in subclass") + + def init_host(self): + """Start up any config'ed consoles on start""" + pass + + def generate_password(self, length=8): + """Returns random console password""" + return os.urandom(length*2).encode('base64')[:length] + + def get_port(self, context): + """get available port for consoles that need one""" + return None + + def fix_pool_password(self, password): + """Trim password to length, and any other massaging""" + return password + + def fix_console_password(self, password): + """Trim password to length, and any other massaging""" + return password + diff --git a/nova/console/fake.py b/nova/console/fake.py new file mode 100644 index 000000000..4a9f1244c --- /dev/null +++ b/nova/console/fake.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 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. + +""" +Fake ConsoleProxy driver for tests. +""" + +from nova import exception +from nova.console import driver + +class FakeConsoleProxy(driver.ConsoleProxy): + """Fake ConsoleProxy driver.""" + + @property + def console_type(self): + return "fake" + + def setup_console(self, context, console): + """Sets up actual proxies""" + pass + + def teardown_console(self, context, console): + """Tears down actual proxies""" + pass + + def init_host(self): + """Start up any config'ed consoles on start""" + pass + + def generate_password(self, length=8): + """Returns random console password""" + return "fakepass" + + def get_port(self, context): + """get available port for consoles that need one""" + return 5999 + + def fix_pool_password(self, password): + """Trim password to length, and any other massaging""" + return password + + def fix_console_password(self, password): + """Trim password to length, and any other massaging""" + return password + diff --git a/nova/console/manager.py b/nova/console/manager.py new file mode 100644 index 000000000..93c6fabce --- /dev/null +++ b/nova/console/manager.py @@ -0,0 +1,130 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 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. + +""" +Console Proxy Service +""" + +import logging +import functools + +from nova import exception +from nova import flags +from nova import manager +from nova import rpc +from nova import utils + +FLAGS = flags.FLAGS +flags.DEFINE_string('console_driver', + 'nova.console.xvp.XVPConsoleProxy', + 'Driver to use for the console proxy') +flags.DEFINE_boolean('stub_compute', False, + 'Stub calls to compute worker for tests') + +class ConsoleProxyManager(manager.Manager): + + """ Sets up and tears down any proxy connections needed for accessing + instance consoles securely""" + + def __init__(self, console_driver=None, *args, **kwargs): + if not console_driver: + console_driver = FLAGS.console_driver + self.driver = utils.import_object(console_driver) + super(ConsoleProxyManager, self).__init__(*args, **kwargs) + self.driver.host = self.host + + def init_host(self): + self.driver.init_host() + + @exception.wrap_exception + def add_console(self, context, instance_id, password = None, + port = None, **kwargs): + instance = self.db.instance_get(context, instance_id) + host = instance['host'] + name = instance['name'] + pool = self.get_pool_for_instance_host(context, host) + try: + console = self.db.console_get_by_pool_instance(context, + pool['id'], + instance_id) + except exception.NotFound: + logging.debug("Adding console") + if not password: + password = self.driver.generate_password() + if not port: + port = self.driver.get_port(context) + console_data = {'instance_name' : name, + 'instance_id' : instance_id, + 'password' : password, + 'pool_id' : pool['id']} + if port: + console_data['port'] = port + console = self.db.console_create(context, console_data) + self.driver.setup_console(context, console) + return console['id'] + + @exception.wrap_exception + def remove_console(self, context, instance_id, **_kwargs): + instance = self.db.instance_get(context, instance_id) + host = instance['host'] + pool = self.get_pool_for_instance_host(context, host) + try: + console = self.db.console_get_by_pool_instance(context, + pool['id'], + instance_id) + except exception.NotFound: + logging.debug(_('Tried to remove non-existant console in pool ' + '%(pool_id)s for instance %(instance_id)s.' % + {'instance_id' : instance_id, + 'pool_id' : pool['id']})) + return + self.db.console_delete(context, console['id']) + self.driver.teardown_console(context, console) + + + def get_pool_for_instance_host(self, context, instance_host): + context = context.elevated() + console_type = self.driver.console_type + try: + pool = self.db.console_pool_get_by_host_type(context, + instance_host, + self.host, + console_type) + except exception.NotFound: + #NOTE(mdragon): Right now, the only place this info exists is the + # compute worker's flagfile, at least for + # xenserver. Thus we ned to ask. + if FLAGS.stub_compute: + pool_info = {'address' : '127.0.0.1', + 'username' : 'test', + 'password' : '1234pass'} + else: + pool_info = rpc.call(context, + self.db.queue_get_for(context, + FLAGS.compute_topic, + instance_host), + {"method": "get_console_pool_info", + "args": {"console_type": console_type}}) + pool_info['password'] = self.driver.fix_pool_password( + pool_info['password']) + pool_info['host'] = self.host + pool_info['console_type'] = self.driver.console_type + pool_info['compute_host'] = instance_host + pool = self.db.console_pool_create(context, pool_info) + return pool + + diff --git a/nova/console/xvp.conf.template b/nova/console/xvp.conf.template new file mode 100644 index 000000000..695ddbe96 --- /dev/null +++ b/nova/console/xvp.conf.template @@ -0,0 +1,16 @@ +# One time password use with time window +OTP ALLOW IPCHECK HTTP 60 +#if $multiplex_port +MULTIPLEX $multiplex_port +#end if + +#for $pool in $pools +POOL $pool.address + DOMAIN $pool.address + MANAGER root $pool.password + HOST $pool.address + VM - dummy 0123456789ABCDEF + #for $console in $pool.consoles + VM #if $multiplex_port then '-' else $console.port # $console.instance_name $pass_encode($console.password) + #end for +#end for diff --git a/nova/console/xvp.py b/nova/console/xvp.py new file mode 100644 index 000000000..62ad3b2bb --- /dev/null +++ b/nova/console/xvp.py @@ -0,0 +1,193 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 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. + +""" +XVP (Xenserver VNC Proxy) driver. +""" + +import fcntl +import logging +import os +import signal +import subprocess + +from Cheetah.Template import Template + +from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import utils +from nova.console import driver + +flags.DEFINE_string('console_xvp_conf_template', + utils.abspath('console/xvp.conf.template'), + 'XVP conf template') +flags.DEFINE_string('console_xvp_conf', + '/etc/xvp.conf', + 'generated XVP conf file') +flags.DEFINE_string('console_xvp_pid', + '/var/run/xvp.pid', + 'XVP master process pid file') +flags.DEFINE_string('console_xvp_log', + '/var/log/xvp.log', + 'XVP log file') +flags.DEFINE_integer('console_xvp_multiplex_port', + 5900, + "port for XVP to multiplex VNC connections on") +FLAGS = flags.FLAGS + +class XVPConsoleProxy(driver.ConsoleProxy): + """Sets up XVP config, and manages xvp daemon""" + + def __init__(self): + self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read() + self.host = FLAGS.host #default, set by manager. + super(XVPConsoleProxy, self).__init__() + + @property + def console_type(self): + return "vnc+xvp" + + def get_port(self, context): + """get available port for consoles that need one""" + #TODO(mdragon): implement port selection for non multiplex ports, + # we are not using that, but someone else may want + # it. + return FLAGS.console_xvp_multiplex_port + + def setup_console(self, context, console): + """Sets up actual proxies""" + self._rebuild_xvp_conf(context.elevated()) + + def teardown_console(self, context, console): + """Tears down actual proxies""" + self._rebuild_xvp_conf(context.elevated()) + + def init_host(self): + """Start up any config'ed consoles on start""" + ctxt = context.get_admin_context() + self._rebuild_xvp_conf(ctxt) + + def fix_pool_password(self, password): + """Trim password to length, and encode""" + return self._xvp_encrypt(password, is_pool_password=True) + + def fix_console_password(self, password): + """Trim password to length, and encode""" + return self._xvp_encrypt(password) + + def _rebuild_xvp_conf(self, context): + logging.debug("Rebuilding xvp conf") + pools = [ pool for pool in + db.console_pool_get_all_by_host_type(context, self.host, + self.console_type) + if pool['consoles']] + if not pools: + logging.debug("No console pools!") + self._xvp_stop() + return + conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port, + 'pools': pools, + 'pass_encode' : self.fix_console_password } + config = str(Template(self.xvpconf_template, searchList=[conf_data])) + self._write_conf(config) + self._xvp_restart() + + def _write_conf(self, config): + logging.debug('Re-wrote %s' % FLAGS.console_xvp_conf) + with open(FLAGS.console_xvp_conf, 'w') as cfile: + cfile.write(config) + + def _xvp_stop(self): + logging.debug("Stopping xvp") + pid = self._xvp_pid() + if not pid: + return + try: + os.kill(pid,signal.SIGTERM) + except OSError: + #if it's already not running, no problem. + pass + + def _xvp_start(self): + if self._xvp_check_running(): + return + logging.debug("Starting xvp") + try: + utils.execute('xvp -p %s -c %s -l %s' % + (FLAGS.console_xvp_pid, + FLAGS.console_xvp_conf, + FLAGS.console_xvp_log)) + except exception.ProcessExecutionError, err: + logging.error("Error starting xvp: %s" % err) + + def _xvp_restart(self): + logging.debug("Restarting xvp") + if not self._xvp_check_running(): + logging.debug("xvp not running...") + self._xvp_start() + else: + pid = self._xvp_pid() + os.kill(pid, signal.SIGUSR1) + + def _xvp_pid(self): + try: + with open(FLAGS.console_xvp_pid, 'r') as pidfile: + pid = int(pidfile.read()) + except IOError: + return None + except ValueError: + return None + return pid + + def _xvp_check_running(self): + pid = self._xvp_pid() + if not pid: + return False + try: + os.kill(pid,0) + except OSError: + return False + return True + + def _xvp_encrypt(self, password, is_pool_password=False): + """Call xvp to obfuscate passwords for config file. + + Args: + - password: the password to encode, max 8 char for vm passwords, + and 16 chars for pool passwords. passwords will + be trimmed to max len before encoding. + - is_pool_password: True if this this is the XenServer api password + False if it's a VM console password + (xvp uses different keys and max lengths for pool passwords) + + Note that xvp's obfuscation should not be considered 'real' encryption. + It simply DES encrypts the passwords with static keys plainly viewable + in the xvp source code.""" + maxlen = 8 + flag = '-e' + if is_pool_password: + maxlen = 16 + flag = '-x' + #xvp will blow up on passwords that are too long (mdragon) + password = password[:maxlen] + out, err = utils.execute('xvp %s' % flag, process_input=password) + #p = subprocess.Popen(['xvp', flag], stdin=subprocess.PIPE, stdout=subprocess.PIPE) + #out,err = p.communicate(password) + return out.strip() + diff --git a/nova/db/api.py b/nova/db/api.py index fde3f0852..af9856cb6 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -884,3 +884,44 @@ def host_get_networks(context, host): """ return IMPL.host_get_networks(context, host) + +################## + +def console_pool_create(context, values): + """Create console pool.""" + return IMPL.console_pool_create(context, values) + +def console_pool_get(context, pool_id): + """Get a console pool.""" + return IMPL.console_pool_get(context, pool_id) + + +def console_pool_get_by_host_type(context, compute_host, proxy_host, + console_type): + """Fetch a console pool for a given proxy host, compute host, and type.""" + return IMPL.console_pool_get_by_host_type(context, + compute_host, + proxy_host, + console_type) + + +def console_pool_get_all_by_host_type(context, host, console_type): + """Fetch all pools for given proxy host and type.""" + return IMPL.console_pool_get_all_by_host_type(context, + host, + console_type) + + +def console_create(context,values): + """Create a console.""" + return IMPL.console_create(context, values) + +def console_delete(context, console_id): + """Delete a console.""" + return IMPL.console_delete(context, console_id) + +def console_get_by_pool_instance(context, pool_id, instance_id): + """Get console entry for a given instance and pool.""" + return IMPL.console_get_by_pool_instance(context, pool_id, instance_id) + + diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7e945e4cb..25a3922c7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1863,3 +1863,84 @@ def host_get_networks(context, host): filter_by(deleted=False).\ filter_by(host=host).\ all() + + +################## + + +def console_pool_create(context, values): + pool = models.ConsolePool() + pool.update(values) + pool.save() + return pool + + +def console_pool_get(context, pool_id): + session = get_session() + result = session.query(models.ConsolePool).\ + filter_by(deleted=False).\ + filter_by(id=pool_id).\ + first() + if not result: + raise exception.NotFound(_("No console pool with id %(pool_id)s") % {'pool_id': pool_id}) + + return result + +def console_pool_get_by_host_type(context, compute_host, host, + console_type): + session = get_session() + result = session.query(models.ConsolePool).\ + filter_by(host=host).\ + filter_by(console_type=console_type).\ + filter_by(compute_host=compute_host).\ + filter_by(deleted=False).\ + options(joinedload('consoles')).\ + first() + if not result: + raise exception.NotFound(_('No console pool of type %(type)s ' + 'for compute host %(compute_host)s ' + 'on proxy host %(host)s') % + {'type' : console_type, + 'compute_host' : compute_host, + 'host' : host}) + return result + + +def console_pool_get_all_by_host_type(context, host, console_type): + session = get_session() + return session.query(models.ConsolePool).\ + filter_by(host=host).\ + filter_by(console_type=console_type).\ + filter_by(deleted=False).\ + options(joinedload('consoles')).\ + all() + + +def console_create(context, values): + console = models.Console() + console.update(values) + console.save() + return console + +def console_delete(context, console_id): + session = get_session() + with session.begin(): + # consoles are meant to be transient. (mdragon) + session.execute('delete from consoles ' + 'where id=:id', {'id': console_id}) + +def console_get_by_pool_instance(context, pool_id, instance_id): + session = get_session() + result = session.query(models.Console).\ + filter_by(pool_id=pool_id).\ + filter_by(instance_id=instance_id).\ + options(joinedload('pool')).\ + first() + if not result: + raise exception.NotFound(_('No console for instance %(instance_id)s ' + 'in pool %(pool_id)s') % + {'instance_id': instance_id, + 'pool_id': pool_id}) + return result + + diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 693db8d23..e7f2d427e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -553,6 +553,27 @@ class FloatingIp(BASE, NovaBase): project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) +class ConsolePool(BASE, NovaBase): + """Represents pool of consoles on the same physical node.""" + __tablename__ = 'console_pools' + id = Column(Integer, primary_key=True) + address = Column(String(255)) + username = Column(String(255)) + password = Column(String(255)) + console_type = Column(String(255)) + host = Column(String(255)) + compute_host = Column(String(255)) + +class Console(BASE, NovaBase): + """Represents a console session for an instance.""" + __tablename__ = 'consoles' + id = Column(Integer, primary_key=True) + instance_name = Column(String(255)) + instance_id = Column(Integer) + password = Column(String(255)) + port = Column(Integer,nullable=True) + pool_id = Column(Integer, ForeignKey('console_pools.id')) + pool = relationship(ConsolePool, backref=backref('consoles')) def register_models(): """Register Models and create metadata. @@ -565,7 +586,7 @@ def register_models(): Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp, Network, SecurityGroup, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, - Project, Certificate) # , Image, Host + Project, Certificate, ConsolePool, Console) # , Image, Host engine = create_engine(FLAGS.sql_connection, echo=False) for model in models: model.metadata.create_all(engine) diff --git a/nova/flags.py b/nova/flags.py index 76a98d35a..447cc6c6c 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -216,6 +216,7 @@ DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)') DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') +DEFINE_string('console_topic', 'console', 'the topic console proxy nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') @@ -263,6 +264,8 @@ DEFINE_string('sql_connection', DEFINE_string('compute_manager', 'nova.compute.manager.ComputeManager', 'Manager for compute') +DEFINE_string('console_manager', 'nova.console.manager.ConsoleProxyManager', + 'Manager for console proxy') DEFINE_string('network_manager', 'nova.network.manager.VlanManager', 'Manager for network') DEFINE_string('volume_manager', 'nova.volume.manager.VolumeManager', diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py new file mode 100644 index 000000000..9f06a1771 --- /dev/null +++ b/nova/tests/test_console.py @@ -0,0 +1,134 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# 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. + +""" +Tests For Console proxy. +""" + +import datetime +import logging + +from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import test +from nova import utils +from nova.auth import manager +from nova.console import manager as console_manager + +FLAGS = flags.FLAGS + + +class ConsoleTestCase(test.TestCase): + """Test case for console proxy""" + def setUp(self): + logging.getLogger().setLevel(logging.DEBUG) + super(ConsoleTestCase, self).setUp() + self.flags(console_driver='nova.console.fake.FakeConsoleProxy', + stub_compute=True) + self.console = utils.import_object(FLAGS.console_manager) + self.manager = manager.AuthManager() + self.user = self.manager.create_user('fake', 'fake', 'fake') + self.project = self.manager.create_project('fake', 'fake', 'fake') + self.context = context.get_admin_context() + self.host = 'test_compute_host' + + def tearDown(self): + self.manager.delete_user(self.user) + self.manager.delete_project(self.project) + super(ConsoleTestCase, self).tearDown() + + def _create_instance(self): + """Create a test instance""" + inst = {} + #inst['host'] = self.host + #inst['name'] = 'instance-1234' + inst['image_id'] = 'ami-test' + inst['reservation_id'] = 'r-fakeres' + inst['launch_time'] = '10' + inst['user_id'] = self.user.id + inst['project_id'] = self.project.id + inst['instance_type'] = 'm1.tiny' + inst['mac_address'] = utils.generate_mac() + inst['ami_launch_index'] = 0 + return db.instance_create(self.context, inst)['id'] + + def test_get_pool_for_instance_host(self): + pool = self.console.get_pool_for_instance_host(self.context, self.host) + self.assertEqual(pool['compute_host'], self.host) + + def test_get_pool_creates_new_pool_if_needed(self): + self.assertRaises(exception.NotFound, + db.console_pool_get_by_host_type, + self.context, + self.host, + self.console.host, + self.console.driver.console_type) + pool = self.console.get_pool_for_instance_host(self.context, + self.host) + pool2 = db.console_pool_get_by_host_type(self.context, + self.host, + self.console.host, + self.console.driver.console_type) + self.assertEqual(pool['id'], pool2['id']) + + def test_get_pool_does_not_create_new_pool_if_exists(self): + pool_info = {'address' : '127.0.0.1', + 'username' : 'test', + 'password' : '1234pass', + 'host' : self.console.host, + 'console_type' : self.console.driver.console_type, + 'compute_host' : 'sometesthostname' } + new_pool = db.console_pool_create(self.context, pool_info) + pool = self.console.get_pool_for_instance_host(self.context, + 'sometesthostname') + self.assertEqual(pool['id'], new_pool['id']) + + def test_add_console(self): + instance_id = self._create_instance() + self.console.add_console(self.context, instance_id) + instance = db.instance_get(self.context, instance_id) + pool = db.console_pool_get_by_host_type(self.context, + instance['host'], + self.console.host, + self.console.driver.console_type) + + console_instances = [con['instance_id'] for con in pool.consoles] + self.assert_(instance_id in console_instances) + + def test_add_console_does_not_duplicate(self): + instance_id = self._create_instance() + cons1 = self.console.add_console(self.context, instance_id) + cons2 = self.console.add_console(self.context, instance_id) + self.assertEqual(cons1,cons2) + + def test_remove_console(self): + instance_id = self._create_instance() + self.console.add_console(self.context, instance_id) + self.console.remove_console(self.context, instance_id) + + instance = db.instance_get(self.context, instance_id) + pool = db.console_pool_get_by_host_type(self.context, + instance['host'], + self.console.host, + self.console.driver.console_type) + + console_instances = [con['instance_id'] for con in pool.consoles] + self.assert_(instance_id not in console_instances) + diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 706888b0d..acabb8034 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -272,6 +272,11 @@ class FakeConnection(object): def get_console_output(self, instance): return 'FAKE CONSOLE OUTPUT' + def get_console_pool_info(self, console_type): + return {'address' : '127.0.0.1', + 'username' : 'fakeuser', + 'password' : 'fakepassword'} + class FakeInstance(object): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 65cf65098..51353147f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -671,6 +671,14 @@ class LibvirtConnection(object): fw = NWFilterFirewall(self._conn) fw.ensure_security_group_filter(security_group_id) + def get_console_pool_info(self, console_type): + #TODO(mdragon): console proxy should be implemented for libvirt, + # in case someone wants to use it with kvm or + # such. For now return fake data. + return {'address' : '127.0.0.1', + 'username' : 'fakeuser', + 'password' : 'fakepassword'} + class NWFilterFirewall(object): """ diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 7f03d6c2b..abad0a08a 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -52,6 +52,7 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block. import logging import sys +import urlparse import xmlrpclib from eventlet import event @@ -177,6 +178,12 @@ class XenAPIConnection(object): """Detach volume storage to VM instance""" return self._volumeops.detach_volume(instance_name, mountpoint) + def get_console_pool_info(self, console_type): + xs_url = urlparse.urlparse(FLAGS.xenapi_connection_url) + return {'address' : xs_url.netloc, + 'username' : FLAGS.xenapi_connection_username, + 'password' : FLAGS.xenapi_connection_password} + class XenAPISession(object): """The session to invoke XenAPI SDK calls""" -- cgit From f21f078113fc81c1dcee4f3a077bd555c0cf85f6 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 19:45:46 -0600 Subject: Fix a bunch of pep8 stuff --- nova/api/openstack/consoles.py | 5 ----- nova/compute/manager.py | 2 -- nova/console/__init__.py | 7 ++++--- nova/console/api.py | 2 +- nova/console/driver.py | 3 +-- nova/console/fake.py | 2 +- nova/console/manager.py | 26 ++++++++++++-------------- nova/console/xvp.py | 18 ++++++++---------- nova/db/api.py | 11 ++++++++--- nova/db/sqlalchemy/api.py | 22 +++++++++++++--------- nova/db/sqlalchemy/models.py | 5 ++++- nova/flags.py | 3 ++- nova/tests/test_console.py | 19 +++++++++---------- nova/virt/fake.py | 6 +++--- nova/virt/libvirt_conn.py | 8 ++++---- nova/virt/xenapi_conn.py | 6 +++--- 16 files changed, 73 insertions(+), 72 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index bf3403655..49eefe09d 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -85,8 +85,3 @@ class Controller(wsgi.Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() - -# def detail(self, req, id): -# """ Returns a complete list of consoles for this instance""" -# return _translate_detail_keys({}) - diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 3e73c351c..403b46b2a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -112,11 +112,9 @@ class ComputeManager(manager.Manager): FLAGS.network_topic, host) - def get_console_pool_info(self, context, console_type): return self.driver.get_console_pool_info(console_type) - @exception.wrap_exception def refresh_security_group(self, context, security_group_id, **_kwargs): """This call passes stright through to the virtualization driver.""" diff --git a/nova/console/__init__.py b/nova/console/__init__.py index adce8621e..491df075b 100644 --- a/nova/console/__init__.py +++ b/nova/console/__init__.py @@ -1,11 +1,12 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 """ -:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp) +:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp) ===================================================== .. automodule:: nova.console :platform: Unix - :synopsis: Wrapper around console proxies such as xvp to set up multitenant VM console access -.. moduleauthor:: Monsyne Dragon + :synopsis: Wrapper around console proxies such as xvp to set up + multitenant VM console access +.. moduleauthor:: Monsyne Dragon """ diff --git a/nova/console/api.py b/nova/console/api.py index 78bfe636b..54317e3b3 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -29,6 +29,7 @@ from nova import rpc FLAGS = flags.FLAGS + class ConsoleAPI(base.Base): """API for spining up or down console proxy connections""" @@ -70,7 +71,6 @@ class ConsoleAPI(base.Base): {"method": "add_console", "args": {"instance_id": instance['id']}}) - def _get_console_topic(self, context, instance_host): topic = self.db.queue_get_for(context, FLAGS.compute_topic, diff --git a/nova/console/driver.py b/nova/console/driver.py index b92765b34..d2dafb1f3 100644 --- a/nova/console/driver.py +++ b/nova/console/driver.py @@ -43,7 +43,7 @@ class ConsoleProxy(object): def generate_password(self, length=8): """Returns random console password""" - return os.urandom(length*2).encode('base64')[:length] + return os.urandom(length * 2).encode('base64')[:length] def get_port(self, context): """get available port for consoles that need one""" @@ -56,4 +56,3 @@ class ConsoleProxy(object): def fix_console_password(self, password): """Trim password to length, and any other massaging""" return password - diff --git a/nova/console/fake.py b/nova/console/fake.py index 4a9f1244c..46782bdb3 100644 --- a/nova/console/fake.py +++ b/nova/console/fake.py @@ -22,6 +22,7 @@ Fake ConsoleProxy driver for tests. from nova import exception from nova.console import driver + class FakeConsoleProxy(driver.ConsoleProxy): """Fake ConsoleProxy driver.""" @@ -56,4 +57,3 @@ class FakeConsoleProxy(driver.ConsoleProxy): def fix_console_password(self, password): """Trim password to length, and any other massaging""" return password - diff --git a/nova/console/manager.py b/nova/console/manager.py index e3cbdae0e..b743e55b5 100644 --- a/nova/console/manager.py +++ b/nova/console/manager.py @@ -35,9 +35,10 @@ flags.DEFINE_string('console_driver', flags.DEFINE_boolean('stub_compute', False, 'Stub calls to compute worker for tests') + class ConsoleProxyManager(manager.Manager): - """ Sets up and tears down any proxy connections needed for accessing + """ Sets up and tears down any proxy connections needed for accessing instance consoles securely""" def __init__(self, console_driver=None, *args, **kwargs): @@ -51,8 +52,8 @@ class ConsoleProxyManager(manager.Manager): self.driver.init_host() @exception.wrap_exception - def add_console(self, context, instance_id, password = None, - port = None, **kwargs): + def add_console(self, context, instance_id, password=None, + port=None, **kwargs): instance = self.db.instance_get(context, instance_id) host = instance['host'] name = instance['name'] @@ -67,10 +68,10 @@ class ConsoleProxyManager(manager.Manager): password = self.driver.generate_password() if not port: port = self.driver.get_port(context) - console_data = {'instance_name' : name, - 'instance_id' : instance_id, - 'password' : password, - 'pool_id' : pool['id']} + console_data = {'instance_name': name, + 'instance_id': instance_id, + 'password': password, + 'pool_id': pool['id']} if port: console_data['port'] = port console = self.db.console_create(context, console_data) @@ -84,12 +85,11 @@ class ConsoleProxyManager(manager.Manager): except exception.NotFound: logging.debug(_('Tried to remove non-existant console ' '%(console_id)s.') % - {'console_id' : console_id}) + {'console_id': console_id}) return self.db.console_delete(context, console_id) self.driver.teardown_console(context, console) - def get_pool_for_instance_host(self, context, instance_host): context = context.elevated() console_type = self.driver.console_type @@ -103,9 +103,9 @@ class ConsoleProxyManager(manager.Manager): # compute worker's flagfile, at least for # xenserver. Thus we ned to ask. if FLAGS.stub_compute: - pool_info = {'address' : '127.0.0.1', - 'username' : 'test', - 'password' : '1234pass'} + pool_info = {'address': '127.0.0.1', + 'username': 'test', + 'password': '1234pass'} else: pool_info = rpc.call(context, self.db.queue_get_for(context, @@ -120,5 +120,3 @@ class ConsoleProxyManager(manager.Manager): pool_info['compute_host'] = instance_host pool = self.db.console_pool_create(context, pool_info) return pool - - diff --git a/nova/console/xvp.py b/nova/console/xvp.py index 62ad3b2bb..161b5ce20 100644 --- a/nova/console/xvp.py +++ b/nova/console/xvp.py @@ -51,12 +51,13 @@ flags.DEFINE_integer('console_xvp_multiplex_port', "port for XVP to multiplex VNC connections on") FLAGS = flags.FLAGS + class XVPConsoleProxy(driver.ConsoleProxy): """Sets up XVP config, and manages xvp daemon""" def __init__(self): self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read() - self.host = FLAGS.host #default, set by manager. + self.host = FLAGS.host # default, set by manager. super(XVPConsoleProxy, self).__init__() @property @@ -93,8 +94,8 @@ class XVPConsoleProxy(driver.ConsoleProxy): def _rebuild_xvp_conf(self, context): logging.debug("Rebuilding xvp conf") - pools = [ pool for pool in - db.console_pool_get_all_by_host_type(context, self.host, + pools = [pool for pool in + db.console_pool_get_all_by_host_type(context, self.host, self.console_type) if pool['consoles']] if not pools: @@ -103,7 +104,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): return conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port, 'pools': pools, - 'pass_encode' : self.fix_console_password } + 'pass_encode': self.fix_console_password} config = str(Template(self.xvpconf_template, searchList=[conf_data])) self._write_conf(config) self._xvp_restart() @@ -119,7 +120,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): if not pid: return try: - os.kill(pid,signal.SIGTERM) + os.kill(pid, signal.SIGTERM) except OSError: #if it's already not running, no problem. pass @@ -129,7 +130,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): return logging.debug("Starting xvp") try: - utils.execute('xvp -p %s -c %s -l %s' % + utils.execute('xvp -p %s -c %s -l %s' % (FLAGS.console_xvp_pid, FLAGS.console_xvp_conf, FLAGS.console_xvp_log)) @@ -160,7 +161,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): if not pid: return False try: - os.kill(pid,0) + os.kill(pid, 0) except OSError: return False return True @@ -187,7 +188,4 @@ class XVPConsoleProxy(driver.ConsoleProxy): #xvp will blow up on passwords that are too long (mdragon) password = password[:maxlen] out, err = utils.execute('xvp %s' % flag, process_input=password) - #p = subprocess.Popen(['xvp', flag], stdin=subprocess.PIPE, stdout=subprocess.PIPE) - #out,err = p.communicate(password) return out.strip() - diff --git a/nova/db/api.py b/nova/db/api.py index 15b87520b..2c53fbeef 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -895,12 +895,15 @@ def host_get_networks(context, host): """ return IMPL.host_get_networks(context, host) + ################## + def console_pool_create(context, values): """Create console pool.""" return IMPL.console_pool_create(context, values) + def console_pool_get(context, pool_id): """Get a console pool.""" return IMPL.console_pool_get(context, pool_id) @@ -922,24 +925,26 @@ def console_pool_get_all_by_host_type(context, host, console_type): console_type) -def console_create(context,values): +def console_create(context, values): """Create a console.""" return IMPL.console_create(context, values) + def console_delete(context, console_id): """Delete a console.""" return IMPL.console_delete(context, console_id) + def console_get_by_pool_instance(context, pool_id, instance_id): """Get console entry for a given instance and pool.""" return IMPL.console_get_by_pool_instance(context, pool_id, instance_id) + def console_get_all_by_instance(context, instance_id): """Get consoles for a given instance.""" return IMPL.console_get_all_by_instance(context, instance_id) + def console_get(context, console_id, instance_id=None): """Get a specific console (possibly on a given instance).""" return IMPL.console_get(context, console_id, instance_id) - - diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 77dfe4a28..7b70566eb 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1835,10 +1835,12 @@ def console_pool_get(context, pool_id): filter_by(id=pool_id).\ first() if not result: - raise exception.NotFound(_("No console pool with id %(pool_id)s") % {'pool_id': pool_id}) + raise exception.NotFound(_("No console pool with id %(pool_id)s") % + {'pool_id': pool_id}) return result + def console_pool_get_by_host_type(context, compute_host, host, console_type): session = get_session() @@ -1853,9 +1855,9 @@ def console_pool_get_by_host_type(context, compute_host, host, raise exception.NotFound(_('No console pool of type %(type)s ' 'for compute host %(compute_host)s ' 'on proxy host %(host)s') % - {'type' : console_type, - 'compute_host' : compute_host, - 'host' : host}) + {'type': console_type, + 'compute_host': compute_host, + 'host': host}) return result @@ -1875,13 +1877,15 @@ def console_create(context, values): console.save() return console + def console_delete(context, console_id): session = get_session() with session.begin(): - # consoles are meant to be transient. (mdragon) + # consoles are meant to be transient. (mdragon) session.execute('delete from consoles ' 'where id=:id', {'id': console_id}) + def console_get_by_pool_instance(context, pool_id, instance_id): session = get_session() result = session.query(models.Console).\ @@ -1891,11 +1895,12 @@ def console_get_by_pool_instance(context, pool_id, instance_id): first() if not result: raise exception.NotFound(_('No console for instance %(instance_id)s ' - 'in pool %(pool_id)s') % + 'in pool %(pool_id)s') % {'instance_id': instance_id, 'pool_id': pool_id}) return result + def console_get_all_by_instance(context, instance_id): session = get_session() results = session.query(models.Console).\ @@ -1904,6 +1909,7 @@ def console_get_all_by_instance(context, instance_id): all() return results + def console_get(context, console_id, instance_id=None): session = get_session() query = session.query(models.Console).\ @@ -1914,7 +1920,5 @@ def console_get(context, console_id, instance_id=None): if not result: idesc = _(" on instance %(instance_id)s") if instance_id else "" raise exception.NotFound(_("No console with id %(instance)s") % - {'instance' : idesc}) + {'instance': idesc}) return result - - diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 58cf21b0d..dd75927d0 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -537,6 +537,7 @@ class FloatingIp(BASE, NovaBase): project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) + class ConsolePool(BASE, NovaBase): """Represents pool of consoles on the same physical node.""" __tablename__ = 'console_pools' @@ -548,6 +549,7 @@ class ConsolePool(BASE, NovaBase): host = Column(String(255)) compute_host = Column(String(255)) + class Console(BASE, NovaBase): """Represents a console session for an instance.""" __tablename__ = 'consoles' @@ -555,10 +557,11 @@ class Console(BASE, NovaBase): instance_name = Column(String(255)) instance_id = Column(Integer) password = Column(String(255)) - port = Column(Integer,nullable=True) + port = Column(Integer, nullable=True) pool_id = Column(Integer, ForeignKey('console_pools.id')) pool = relationship(ConsolePool, backref=backref('consoles')) + def register_models(): """Register Models and create metadata. diff --git a/nova/flags.py b/nova/flags.py index 58ba4d16d..f3a19c0c8 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -218,7 +218,8 @@ DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)') DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') -DEFINE_string('console_topic', 'console', 'the topic console proxy nodes listen on') +DEFINE_string('console_topic', 'console', + 'the topic console proxy nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py index b23b1499b..31b5ca79c 100644 --- a/nova/tests/test_console.py +++ b/nova/tests/test_console.py @@ -89,12 +89,12 @@ class ConsoleTestCase(test.TestCase): self.assertEqual(pool['id'], pool2['id']) def test_get_pool_does_not_create_new_pool_if_exists(self): - pool_info = {'address' : '127.0.0.1', - 'username' : 'test', - 'password' : '1234pass', - 'host' : self.console.host, - 'console_type' : self.console.driver.console_type, - 'compute_host' : 'sometesthostname' } + pool_info = {'address': '127.0.0.1', + 'username': 'test', + 'password': '1234pass', + 'host': self.console.host, + 'console_type': self.console.driver.console_type, + 'compute_host': 'sometesthostname'} new_pool = db.console_pool_create(self.context, pool_info) pool = self.console.get_pool_for_instance_host(self.context, 'sometesthostname') @@ -107,16 +107,16 @@ class ConsoleTestCase(test.TestCase): pool = db.console_pool_get_by_host_type(self.context, instance['host'], self.console.host, - self.console.driver.console_type) + self.console.driver.console_type) - console_instances = [con['instance_id'] for con in pool.consoles] + console_instances = [con['instance_id'] for con in pool.consoles] self.assert_(instance_id in console_instances) def test_add_console_does_not_duplicate(self): instance_id = self._create_instance() cons1 = self.console.add_console(self.context, instance_id) cons2 = self.console.add_console(self.context, instance_id) - self.assertEqual(cons1,cons2) + self.assertEqual(cons1, cons2) def test_remove_console(self): instance_id = self._create_instance() @@ -127,4 +127,3 @@ class ConsoleTestCase(test.TestCase): db.console_get, self.context, console_id) - diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 13490b12e..849261f07 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -290,9 +290,9 @@ class FakeConnection(object): return 'FAKE CONSOLE OUTPUT' def get_console_pool_info(self, console_type): - return {'address' : '127.0.0.1', - 'username' : 'fakeuser', - 'password' : 'fakepassword'} + return {'address': '127.0.0.1', + 'username': 'fakeuser', + 'password': 'fakepassword'} class FakeInstance(object): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ded1004cd..0e3b6dff6 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -697,10 +697,10 @@ class LibvirtConnection(object): def get_console_pool_info(self, console_type): #TODO(mdragon): console proxy should be implemented for libvirt, # in case someone wants to use it with kvm or - # such. For now return fake data. - return {'address' : '127.0.0.1', - 'username' : 'fakeuser', - 'password' : 'fakepassword'} + # such. For now return fake data. + return {'address': '127.0.0.1', + 'username': 'fakeuser', + 'password': 'fakepassword'} class NWFilterFirewall(object): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index c702059f7..86efb6b07 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -189,9 +189,9 @@ class XenAPIConnection(object): def get_console_pool_info(self, console_type): xs_url = urlparse.urlparse(FLAGS.xenapi_connection_url) - return {'address' : xs_url.netloc, - 'username' : FLAGS.xenapi_connection_username, - 'password' : FLAGS.xenapi_connection_password} + return {'address': xs_url.netloc, + 'username': FLAGS.xenapi_connection_username, + 'password': FLAGS.xenapi_connection_password} class XenAPISession(object): -- cgit From f9fa25f9a873c1e4831c342689f7b5adc8f41013 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 20:14:36 -0600 Subject: add in separate public hostname for console hosts. flesh out console api data. --- nova/api/openstack/consoles.py | 17 +++++++++++++---- nova/console/manager.py | 7 ++++++- nova/db/sqlalchemy/models.py | 1 + 3 files changed, 20 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 49eefe09d..e108bab86 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -23,15 +23,24 @@ from nova.console import api as console_api from nova.api.openstack import faults -def _translate_keys(inst): +def _translate_keys(cons): """Coerces a console instance into proper dictionary format """ - return dict(console=inst) + pool = cons['pool'] + info = {'id': cons['id'], + 'console_type': pool['console_type']} + return dict(console=info) -def _translate_detail_keys(inst): +def _translate_detail_keys(cons): """Coerces a console instance into proper dictionary format with correctly mapped attributes """ - return dict(console=inst) + pool = cons['pool'] + info = {'id': cons['id'], + 'console_type': pool['console_type'], + 'password': cons['password'], + 'port': cons['port'], + 'host': pool['public_hostname']} + return dict(console=info) class Controller(wsgi.Controller): diff --git a/nova/console/manager.py b/nova/console/manager.py index b743e55b5..c55ca8e8f 100644 --- a/nova/console/manager.py +++ b/nova/console/manager.py @@ -19,8 +19,9 @@ Console Proxy Service """ -import logging import functools +import logging +import socket from nova import exception from nova import flags @@ -34,6 +35,9 @@ flags.DEFINE_string('console_driver', 'Driver to use for the console proxy') flags.DEFINE_boolean('stub_compute', False, 'Stub calls to compute worker for tests') +flags.DEFINE_string('console_public_hostname', + socket.gethostname(), + 'Publicly visable name for this console host') class ConsoleProxyManager(manager.Manager): @@ -116,6 +120,7 @@ class ConsoleProxyManager(manager.Manager): pool_info['password'] = self.driver.fix_pool_password( pool_info['password']) pool_info['host'] = self.host + pool_info['public_hostname'] = FLAGS.console_public_hostname pool_info['console_type'] = self.driver.console_type pool_info['compute_host'] = instance_host pool = self.db.console_pool_create(context, pool_info) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index dd75927d0..0c55ee75e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -546,6 +546,7 @@ class ConsolePool(BASE, NovaBase): username = Column(String(255)) password = Column(String(255)) console_type = Column(String(255)) + public_hostname = Column(String(255)) host = Column(String(255)) compute_host = Column(String(255)) -- cgit From b55940e8e3d977960ff60f4cb7cff4b6ea2e8fb8 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 22:11:05 -0600 Subject: fix some glitches due to someone removing instanc.internal_id (not that I mind) remove accidental change to nova-combined script --- nova/console/api.py | 23 +++++++++-------------- nova/console/driver.py | 2 ++ nova/db/sqlalchemy/api.py | 6 +++--- 3 files changed, 14 insertions(+), 17 deletions(-) (limited to 'nova') diff --git a/nova/console/api.py b/nova/console/api.py index 54317e3b3..93e28ad64 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -36,20 +36,16 @@ class ConsoleAPI(base.Base): def __init__(self, **kwargs): super(ConsoleAPI, self).__init__(**kwargs) - def get_consoles(self, context, instance_internal_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) - return self.db.console_get_all_by_instance(context, instance['id']) + def get_consoles(self, context, instance_id): + return self.db.console_get_all_by_instance(context, instance_id) - def get_console(self, context, instance_internal_id, console_id): - return self.db.console_get(context, console_id, instance_internal_id) + def get_console(self, context, instance_id, console_id): + return self.db.console_get(context, console_id, instance_id) - def delete_console(self, context, instance_internal_id, console_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) + def delete_console(self, context, instance_id, console_id): console = self.db.console_get(context, console_id, - instance['id']) + instance_id) pool = console['pool'] rpc.cast(context, self.db.queue_get_for(context, @@ -58,9 +54,8 @@ class ConsoleAPI(base.Base): {"method": "remove_console", "args": {"console_id": console['id']}}) - def create_console(self, context, instance_internal_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) + def create_console(self, context, instance_id): + instance = self.db.instance_get(context, instance_id) #NOTE(mdragon): If we wanted to return this the console info # here, as we would need to do a call. # They can just do an index later to fetch @@ -69,7 +64,7 @@ class ConsoleAPI(base.Base): rpc.cast(context, self._get_console_topic(context, instance['host']), {"method": "add_console", - "args": {"instance_id": instance['id']}}) + "args": {"instance_id": instance_id}}) def _get_console_topic(self, context, instance_host): topic = self.db.queue_get_for(context, diff --git a/nova/console/driver.py b/nova/console/driver.py index d2dafb1f3..c4cf5e5e9 100644 --- a/nova/console/driver.py +++ b/nova/console/driver.py @@ -19,6 +19,8 @@ ConsoleProxy base class that all ConsoleProxies should inherit from """ +import os + from nova import exception diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7b70566eb..6f4d068f1 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1918,7 +1918,7 @@ def console_get(context, console_id, instance_id=None): query = query.filter_by(instance_id=instance_id) result = query.options(joinedload('pool')).first() if not result: - idesc = _(" on instance %(instance_id)s") if instance_id else "" - raise exception.NotFound(_("No console with id %(instance)s") % - {'instance': idesc}) + idesc = (_("on instance %s") % instance_id) if instance_id else "" + raise exception.NotFound(_("No console with id %(console_id)s %(instance)s") % + {'instance': idesc, 'console_id': console_id}) return result -- cgit From 1ce25cab7f1818aababb18d60959f44602f2e17c Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Thu, 6 Jan 2011 13:19:58 -0600 Subject: pep8 fix --- nova/db/sqlalchemy/api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 6f4d068f1..c3dee4329 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1919,6 +1919,8 @@ def console_get(context, console_id, instance_id=None): result = query.options(joinedload('pool')).first() if not result: idesc = (_("on instance %s") % instance_id) if instance_id else "" - raise exception.NotFound(_("No console with id %(console_id)s %(instance)s") % - {'instance': idesc, 'console_id': console_id}) + raise exception.NotFound(_("No console with id %(console_id)s" + " %(instance)s") % + {'instance': idesc, + 'console_id': console_id}) return result -- cgit From 59b3e0f2700d6a9067bffe045ea335b7abc35a27 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 6 Jan 2011 15:08:14 -0500 Subject: pep8 --- nova/log.py | 3 +-- nova/tests/test_log.py | 9 ++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/log.py b/nova/log.py index 97472b20b..6f5377e87 100644 --- a/nova/log.py +++ b/nova/log.py @@ -74,7 +74,6 @@ flags.DEFINE_bool('use_syslog', False, 'output to syslog') flags.DEFINE_string('logfile', None, 'output to named file') - # A list of things we want to replicate from logging. # levels CRITICAL = logging.CRITICAL @@ -112,7 +111,7 @@ def _dictify_context(context): return None if not isinstance(context, dict) \ and getattr(context, 'to_dict', None): - context = context.to_dict() + context = context.to_dict() return context diff --git a/nova/tests/test_log.py b/nova/tests/test_log.py index d8dd38708..beb1d97cf 100644 --- a/nova/tests/test_log.py +++ b/nova/tests/test_log.py @@ -35,15 +35,16 @@ class RootLoggerTestCase(test.TrialTestCase): def test_handles_context_kwarg(self): self.log.info("foo", context=_fake_context()) - self.assert_(True) # didn't raise exception + self.assert_(True) # didn't raise exception def test_module_level_methods_handle_context_arg(self): log.info("foo", context=_fake_context()) - self.assert_(True) # didn't raise exception + self.assert_(True) # didn't raise exception def test_module_level_audit_handles_context_arg(self): log.audit("foo", context=_fake_context()) - self.assert_(True) # didn't raise exception + self.assert_(True) # didn't raise exception + class NovaFormatterTestCase(test.TrialTestCase): def setUp(self): @@ -76,6 +77,7 @@ class NovaFormatterTestCase(test.TrialTestCase): self.log.debug("baz") self.assertEqual("NOCTXT: baz --DBG\n", self.stream.getvalue()) + class NovaLoggerTestCase(test.TrialTestCase): def setUp(self): super(NovaLoggerTestCase, self).setUp() @@ -93,6 +95,7 @@ class NovaLoggerTestCase(test.TrialTestCase): l = log.getLogger('nova-test.foo') self.assertEqual(log.AUDIT, l.level) + class VerboseLoggerTestCase(test.TrialTestCase): def setUp(self): super(VerboseLoggerTestCase, self).setUp() -- 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 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'nova') 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): -- 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 (limited to 'nova') 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 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(-) (limited to 'nova') 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. --- nova/version.py | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) (limited to 'nova') 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()) -- cgit From 5e34b63b874b9c75215b9eeabc8e8e951a866fe7 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Fri, 7 Jan 2011 15:12:34 +0000 Subject: Fixing headers line by wrapping the headers in single quotes --- nova/virt/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/images.py b/nova/virt/images.py index 2d03da4b4..048608e3d 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -89,7 +89,7 @@ def _fetch_s3_image(image, path, user, project): else: cmd = ['/usr/bin/curl', '--fail', '--silent', url] for (k, v) in headers.iteritems(): - cmd += ['-H', '%s: %s' % (k, v)] + cmd += ['-H', '\'%s: %s\'' % (k, v)] cmd += ['-o', path] cmd_out = ' '.join(cmd) -- cgit From 2ea4af0f3059ef2aee6e25db8849a39248983d30 Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Fri, 7 Jan 2011 18:11:04 +0000 Subject: Bug #699912: When failing to connect to a data store, Nova doesn't log which data store it tried to connect to Log FLAGS.sql_connection when failing to connect, to aid debugging. --- nova/db/sqlalchemy/__init__.py | 5 +++-- nova/service.py | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/__init__.py b/nova/db/sqlalchemy/__init__.py index 22aa1cfe6..367fdda8b 100644 --- a/nova/db/sqlalchemy/__init__.py +++ b/nova/db/sqlalchemy/__init__.py @@ -39,5 +39,6 @@ for i in xrange(FLAGS.sql_max_retries): models.register_models() break except OperationalError: - logging.exception(_("Data store is unreachable." - " Trying again in %d seconds.") % FLAGS.sql_retry_interval) + logging.exception(_("Data store %s is unreachable." + " Trying again in %d seconds.") % + (FLAGS.sql_connection, FLAGS.sql_retry_interval)) diff --git a/nova/service.py b/nova/service.py index 7203430c6..a48d00e45 100644 --- a/nova/service.py +++ b/nova/service.py @@ -211,9 +211,10 @@ class Service(object): try: models.register_models() except OperationalError: - logging.exception(_("Data store is unreachable." - " Trying again in %d seconds.") % - FLAGS.sql_retry_interval) + logging.exception(_("Data store %s is unreachable." + " Trying again in %d seconds.") % + (FLAGS.sql_connection, + FLAGS.sql_retry_interval)) time.sleep(FLAGS.sql_retry_interval) -- cgit From 01bb7d0c941c1cdb27e354c7f037fe0056a87770 Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Fri, 7 Jan 2011 18:15:29 +0000 Subject: Bug #699910: Nova RPC layer silently swallows exceptions Log exceptions thrown during message handling. --- nova/rpc.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index 844088348..ae5e77921 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -193,6 +193,7 @@ class AdapterConsumer(TopicConsumer): if msg_id: msg_reply(msg_id, rval, None) except Exception as e: + logging.exception("Exception during message handling") if msg_id: msg_reply(msg_id, None, sys.exc_info()) return -- 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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 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(-) (limited to 'nova') 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(+) (limited to 'nova') 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 b2d6bb841857599096467470ec704e6696317829 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Fri, 7 Jan 2011 19:04:22 -0600 Subject: change API classname to match the way other API's are done. --- nova/api/openstack/consoles.py | 4 ++-- nova/console/__init__.py | 1 + nova/console/api.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index e108bab86..9ebdbe710 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -17,9 +17,9 @@ from webob import exc +from nova import console from nova import exception from nova import wsgi -from nova.console import api as console_api from nova.api.openstack import faults @@ -52,7 +52,7 @@ class Controller(wsgi.Controller): 'console': []}}} def __init__(self): - self.console_api = console_api.ConsoleAPI() + self.console_api = console.API() super(Controller, self).__init__() def index(self, req, server_id): diff --git a/nova/console/__init__.py b/nova/console/__init__.py index 491df075b..dfc72cd61 100644 --- a/nova/console/__init__.py +++ b/nova/console/__init__.py @@ -10,3 +10,4 @@ multitenant VM console access .. moduleauthor:: Monsyne Dragon """ +from nova.console.api import API diff --git a/nova/console/api.py b/nova/console/api.py index 93e28ad64..3850d2c44 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -30,11 +30,11 @@ from nova import rpc FLAGS = flags.FLAGS -class ConsoleAPI(base.Base): +class API(base.Base): """API for spining up or down console proxy connections""" def __init__(self, **kwargs): - super(ConsoleAPI, self).__init__(**kwargs) + super(API, self).__init__(**kwargs) def get_consoles(self, context, instance_id): return self.db.console_get_all_by_instance(context, instance_id) -- 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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 3b4582b5db905a6dcadda31be27c9f340d7fe5cf Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 9 Jan 2011 18:08:54 -0800 Subject: Moved get_my_ip into flags because that is the only thing it is being used for and use it to set a new flag called my_ip --- nova/flags.py | 26 +++++++++++++++++++------- nova/network/linux_net.py | 2 +- nova/network/manager.py | 2 +- nova/tests/api/openstack/fakes.py | 2 +- nova/utils.py | 13 ------------- 5 files changed, 22 insertions(+), 23 deletions(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index f5c2d4233..0e6d3176c 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -200,10 +200,22 @@ def DECLARE(name, module_string, flag_values=FLAGS): "%s not defined by %s" % (name, module_string)) +def _get_my_ip(): + """Returns the actual ip of the local machine.""" + try: + csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + csock.connect(('8.8.8.8', 80)) + (addr, port) = csock.getsockname() + csock.close() + return addr + except socket.gaierror as ex: + return "127.0.0.1" + + # __GLOBAL FLAGS ONLY__ # Define any app-specific flags in their own files, docs at: -# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39 - +# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#a9 +DEFINE_string('my_ip', _get_my_ip(), 'host ip address') DEFINE_list('region_list', [], 'list of region=url pairs separated by commas') @@ -211,10 +223,10 @@ DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('glance_port', 9292, 'glance port') -DEFINE_string('glance_host', '127.0.0.1', 'glance host') +DEFINE_string('glance_host', '$my_ip', 'glance host') DEFINE_integer('s3_port', 3333, 's3 port') -DEFINE_string('s3_host', '127.0.0.1', 's3 host (for infrastructure)') -DEFINE_string('s3_dmz', '127.0.0.1', 's3 dmz ip (for instances)') +DEFINE_string('s3_host', '$my_ip', 's3 host (for infrastructure)') +DEFINE_string('s3_dmz', '$my_ip', 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') @@ -234,8 +246,8 @@ DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('ec2_prefix', 'http', 'prefix for ec2') -DEFINE_string('cc_host', '127.0.0.1', 'ip of api server') -DEFINE_string('cc_dmz', '127.0.0.1', 'internal ip of api server') +DEFINE_string('cc_host', '$my_ip', 'ip of api server') +DEFINE_string('cc_dmz', '$my_ip', 'internal ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index c525d5dc8..a0648ca25 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -46,7 +46,7 @@ flags.DEFINE_string('vlan_interface', 'eth0', 'network device for vlans') flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'), 'location of nova-dhcpbridge') -flags.DEFINE_string('routing_source_ip', utils.get_my_ip(), +flags.DEFINE_string('routing_source_ip', '$my_ip', 'Public IP of network host') flags.DEFINE_bool('use_nova_chains', False, 'use the nova_ routing chains instead of default') diff --git a/nova/network/manager.py b/nova/network/manager.py index fd286f210..c75ecc671 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -74,7 +74,7 @@ flags.DEFINE_string('flat_network_dhcp_start', '10.0.0.2', 'Dhcp start for FlatDhcp') flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks') flags.DEFINE_integer('num_networks', 1000, 'Number of networks to support') -flags.DEFINE_string('vpn_ip', utils.get_my_ip(), +flags.DEFINE_string('vpn_ip', '$my_ip', 'Public IP for the cloudpipe VPN servers') flags.DEFINE_integer('vpn_start', 1000, 'First Vpn port for private networks') flags.DEFINE_integer('network_size', 256, diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 291a0e468..194304e79 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -107,7 +107,7 @@ def stub_out_rate_limiting(stubs): def stub_out_networking(stubs): def get_my_ip(): return '127.0.0.1' - stubs.Set(nova.utils, 'get_my_ip', get_my_ip) + stubs.Set(nova.flags, '_get_my_ip', get_my_ip) def stub_out_compute_api_snapshot(stubs): diff --git a/nova/utils.py b/nova/utils.py index cc632b835..aadbec532 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -195,19 +195,6 @@ def last_octet(address): return int(address.split(".")[-1]) -def get_my_ip(): - """Returns the actual ip of the local machine.""" - try: - csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - csock.connect(('8.8.8.8', 80)) - (addr, port) = csock.getsockname() - csock.close() - return addr - except socket.gaierror as ex: - LOG.warn(_("Couldn't get IP, using 127.0.0.1 %s"), ex) - return "127.0.0.1" - - def utcnow(): """Overridable version of datetime.datetime.utcnow.""" if utcnow.override_time: -- 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(-) (limited to 'nova') 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(-) (limited to 'nova') 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 d09511edeef2a8f6dc866ea3011bd8cc4632ac38 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 10 Jan 2011 04:20:11 -0400 Subject: Fixed xenapi_conn wait_for_task to properly terminate LoopingCall on exception --- nova/tests/xenapi/stubs.py | 24 ++++++++++++++++++++++++ nova/virt/xenapi_conn.py | 15 +++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 55f751f11..d7a9a5f3e 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -41,9 +41,33 @@ def stubout_instance_snapshot(stubs): rv = done.wait() return rv + def fake_loop(self): + pass + stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task', fake_wait_for_task) + stubs.Set(xenapi_conn.XenAPISession, 'stop_loop', fake_loop) + + from nova.virt.xenapi.fake import create_vdi + name_label = "instance-%s" % instance_id + #TODO: create fake SR record + sr_ref = "fakesr" + vdi_ref = create_vdi(name_label=name_label, read_only=False, + sr_ref=sr_ref, sharable=False) + vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) + vdi_uuid = vdi_rec['uuid'] + return vdi_uuid + + stubs.Set(vm_utils.VMHelper, 'fetch_image', fake_fetch_image) + + def fake_parse_xmlrpc_value(val): + return val + + stubs.Set(xenapi_conn, '_parse_xmlrpc_value', fake_parse_xmlrpc_value) + + def fake_wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, + original_parent_uuid): from nova.virt.xenapi.fake import create_vdi name_label = "instance-%s" % instance_id #TODO: create fake SR record diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 3aaaf09ed..ad32e890d 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -198,6 +198,7 @@ class XenAPISession(object): self.XenAPI = self.get_imported_xenapi() self._session = self._create_session(url) self._session.login_with_password(user, pw) + self.loop = None def get_imported_xenapi(self): """Stubout point. This can be replaced with a mock xenapi module.""" @@ -234,14 +235,19 @@ class XenAPISession(object): def wait_for_task(self, id, task): """Return the result of the given task. The task is polled - until it completes.""" + until it completes. Not re-entrant.""" done = event.Event() - loop = utils.LoopingCall(self._poll_task, id, task, done) - loop.start(FLAGS.xenapi_task_poll_interval, now=True) + self.loop = utils.LoopingCall(self._poll_task, id, task, done) + self.loop.start(FLAGS.xenapi_task_poll_interval, now=True) rv = done.wait() - loop.stop() + self.loop.stop() return rv + def stop_loop(self): + # Had to break this call out to support unit tests. + if self.loop: + self.loop.stop() + def _create_session(self, url): """Stubout point. This can be replaced with a mock session.""" return self.XenAPI.Session(url) @@ -278,6 +284,7 @@ class XenAPISession(object): except self.XenAPI.Failure, exc: LOG.warn(exc) done.send_exception(*sys.exc_info()) + self.stop_loop() def _unwrap_plugin_exceptions(self, func, *args, **kwargs): """Parse exception details""" -- cgit From f9d1a59fc10425b0a9b82edca857c771e6be9809 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 10 Jan 2011 04:40:57 -0400 Subject: Fixed xenapi_conn wait_for_task to properly terminate LoopingCall on exception --- nova/virt/xenapi_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ad32e890d..f4293ef3b 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -244,7 +244,7 @@ class XenAPISession(object): return rv def stop_loop(self): - # Had to break this call out to support unit tests. + #NOTE(sandy-walsh) Had to break this call out to support unit tests. if self.loop: self.loop.stop() -- cgit From 72e9f0819837da68c52f5604e83385037fdcdfb2 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 10 Jan 2011 05:12:48 -0400 Subject: Fixed xenapi_conn wait_for_task to properly terminate LoopingCall on exception --- nova/tests/xenapi/stubs.py | 2 +- nova/virt/xenapi_conn.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index d7a9a5f3e..292bd9ba9 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -47,7 +47,7 @@ def stubout_instance_snapshot(stubs): stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task', fake_wait_for_task) - stubs.Set(xenapi_conn.XenAPISession, 'stop_loop', fake_loop) + stubs.Set(xenapi_conn.XenAPISession, '_stop_loop', fake_loop) from nova.virt.xenapi.fake import create_vdi name_label = "instance-%s" % instance_id diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index f4293ef3b..b8ab9245f 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -243,7 +243,8 @@ class XenAPISession(object): self.loop.stop() return rv - def stop_loop(self): + def _stop_loop(self): + """Stop polling for task to finish.""" #NOTE(sandy-walsh) Had to break this call out to support unit tests. if self.loop: self.loop.stop() @@ -284,7 +285,7 @@ class XenAPISession(object): except self.XenAPI.Failure, exc: LOG.warn(exc) done.send_exception(*sys.exc_info()) - self.stop_loop() + self._stop_loop() def _unwrap_plugin_exceptions(self, func, *args, **kwargs): """Parse exception details""" -- 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(-) (limited to 'nova') 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 From ec8e7773b79ed52aa2950db185ead881c77632f7 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 10 Jan 2011 10:57:13 -0500 Subject: Fix describe_availablity_zones versobse. --- nova/api/ec2/cloud.py | 3 ++- nova/db/api.py | 5 +++++ nova/db/sqlalchemy/api.py | 12 ++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 5360f2de7..359498d11 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -44,6 +44,7 @@ from nova.compute import instance_types FLAGS = flags.FLAGS +flags.DECLARE('service_down_time', 'nova.scheduler.driver') LOG = logging.getLogger("nova.api.cloud") @@ -200,7 +201,7 @@ class CloudController(object): 'zoneState': 'available'}]} services = db.service_get_all(context) - now = db.get_time() + now = datetime.datetime.utcnow() hosts = [] for host in [service['host'] for service in services]: if not host in hosts: diff --git a/nova/db/api.py b/nova/db/api.py index 8b0242c9a..a4d26ec85 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -81,6 +81,11 @@ def service_get(context, service_id): return IMPL.service_get(context, service_id) +def service_get_all(context): + """Get a list of all services on any machine on any topic of any type""" + return IMPL.service_get_all(context) + + def service_get_all_by_topic(context, topic): """Get all compute services for a given topic.""" return IMPL.service_get_all_by_topic(context, topic) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index eb87355b6..e475b4d8c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -134,6 +134,18 @@ def service_get(context, service_id, session=None): return result +@require_admin_context +def service_get_all(context, session=None): + if not session: + session = get_session() + + result = session.query(models.Service).\ + filter_by(deleted=can_read_deleted(context)).\ + all() + + return result + + @require_admin_context def service_get_all_by_topic(context, topic): session = get_session() -- cgit From 94f3782eb27fd63c64845f9ab59039d07ac7ba8c Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Mon, 10 Jan 2011 14:59:32 -0600 Subject: remove uneeded superclass --- nova/console/driver.py | 60 -------------------------------------------------- nova/console/fake.py | 3 +-- nova/console/xvp.py | 7 ++++-- 3 files changed, 6 insertions(+), 64 deletions(-) delete mode 100644 nova/console/driver.py (limited to 'nova') diff --git a/nova/console/driver.py b/nova/console/driver.py deleted file mode 100644 index c4cf5e5e9..000000000 --- a/nova/console/driver.py +++ /dev/null @@ -1,60 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 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. - -""" -ConsoleProxy base class that all ConsoleProxies should inherit from -""" - -import os - -from nova import exception - - -class ConsoleProxy(object): - """The base class for all ConsoleProxy driver classes.""" - - @property - def console_type(self): - raise NotImplementedError("Must specify type in subclass") - - def setup_console(self, context, console): - """Sets up actual proxies""" - raise NotImplementedError("Must implement setup in subclass") - - def teardown_console(self, context, console): - """Tears down actual proxies""" - raise NotImplementedError("Must implement teardown in subclass") - - def init_host(self): - """Start up any config'ed consoles on start""" - pass - - def generate_password(self, length=8): - """Returns random console password""" - return os.urandom(length * 2).encode('base64')[:length] - - def get_port(self, context): - """get available port for consoles that need one""" - return None - - def fix_pool_password(self, password): - """Trim password to length, and any other massaging""" - return password - - def fix_console_password(self, password): - """Trim password to length, and any other massaging""" - return password diff --git a/nova/console/fake.py b/nova/console/fake.py index 46782bdb3..7a90d5221 100644 --- a/nova/console/fake.py +++ b/nova/console/fake.py @@ -20,10 +20,9 @@ Fake ConsoleProxy driver for tests. """ from nova import exception -from nova.console import driver -class FakeConsoleProxy(driver.ConsoleProxy): +class FakeConsoleProxy(object): """Fake ConsoleProxy driver.""" @property diff --git a/nova/console/xvp.py b/nova/console/xvp.py index 161b5ce20..2a76223da 100644 --- a/nova/console/xvp.py +++ b/nova/console/xvp.py @@ -32,7 +32,6 @@ from nova import db from nova import exception from nova import flags from nova import utils -from nova.console import driver flags.DEFINE_string('console_xvp_conf_template', utils.abspath('console/xvp.conf.template'), @@ -52,7 +51,7 @@ flags.DEFINE_integer('console_xvp_multiplex_port', FLAGS = flags.FLAGS -class XVPConsoleProxy(driver.ConsoleProxy): +class XVPConsoleProxy(object): """Sets up XVP config, and manages xvp daemon""" def __init__(self): @@ -92,6 +91,10 @@ class XVPConsoleProxy(driver.ConsoleProxy): """Trim password to length, and encode""" return self._xvp_encrypt(password) + def generate_password(self, length=8): + """Returns random console password""" + return os.urandom(length * 2).encode('base64')[:length] + def _rebuild_xvp_conf(self, context): logging.debug("Rebuilding xvp conf") pools = [pool for pool in -- cgit