diff options
| author | Chris Behrens <cbehrens@codestud.com> | 2011-05-13 16:45:42 +0000 |
|---|---|---|
| committer | Chris Behrens <cbehrens@codestud.com> | 2011-05-13 16:45:42 +0000 |
| commit | be83d7317ee55ae0ef436f09f25beef2eccee5a6 (patch) | |
| tree | 87d4f53c8bd7c5c4056b131b3fc039ee0740f1d6 | |
| parent | 7dc76508d38370f3cf68029fae2ffb7749a580b2 (diff) | |
| parent | 0805521c79f934ba54f839be64a2c43ed177612d (diff) | |
| download | nova-be83d7317ee55ae0ef436f09f25beef2eccee5a6.tar.gz nova-be83d7317ee55ae0ef436f09f25beef2eccee5a6.tar.xz nova-be83d7317ee55ae0ef436f09f25beef2eccee5a6.zip | |
Merged trunk
34 files changed, 283 insertions, 120 deletions
@@ -44,12 +44,14 @@ Josh Kearney <josh@jk0.org> Josh Kleinpeter <josh@kleinpeter.org> Joshua McKenty <jmckenty@gmail.com> Justin Santa Barbara <justin@fathomdb.com> +Justin Shepherd <jshepher@rackspace.com> Kei Masumoto <masumotok@nttdata.co.jp> Ken Pepple <ken.pepple@gmail.com> Kevin Bringard <kbringard@attinteractive.com> Kevin L. Mitchell <kevin.mitchell@rackspace.com> Koji Iida <iida.koji@lab.ntt.co.jp> Lorin Hochstein <lorin@isi.edu> +Lvov Maxim <usrleon@gmail.com> Mark Washenberger <mark.washenberger@rackspace.com> Masanori Itoh <itoumsn@nttdata.co.jp> Matt Dietz <matt.dietz@rackspace.com> diff --git a/bin/nova-manage b/bin/nova-manage index 2f6af6e2d..a36ec86d0 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -972,7 +972,7 @@ class ImageCommands(object): try: internal_id = ec2utils.ec2_id_to_id(old_image_id) image = self.image_service.show(context, internal_id) - except exception.NotFound: + except (exception.InvalidEc2Id, exception.ImageNotFound): image = self.image_service.show_by_name(context, old_image_id) return image['id'] diff --git a/nova/CA/openssl.cnf.tmpl b/nova/CA/openssl.cnf.tmpl index b80fadf40..f87d9f3b2 100644 --- a/nova/CA/openssl.cnf.tmpl +++ b/nova/CA/openssl.cnf.tmpl @@ -46,7 +46,7 @@ policy = policy_match # RHEL 6 and Fedora 14 (using openssl-1.0.0-4.el6.x86_64 or # openssl-1.0.0d-1.fc14.x86_64) [ policy_match ] -countryName = match +countryName = supplied stateOrProvinceName = supplied organizationName = optional organizationalUnitName = optional diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 092b80fa2..be5dd38a0 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -906,7 +906,7 @@ class CloudController(object): try: internal_id = ec2utils.ec2_id_to_id(ec2_id) return self.image_service.show(context, internal_id) - except ValueError: + except (exception.InvalidEc2Id, exception.ImageNotFound): try: return self.image_service.show_by_name(context, ec2_id) except exception.NotFound: diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index 1ac48163c..163aa4ed2 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -21,7 +21,10 @@ from nova import exception def ec2_id_to_id(ec2_id): """Convert an ec2 ID (i-[base 16 number]) to an instance id (int)""" - return int(ec2_id.split('-')[-1], 16) + try: + return int(ec2_id.split('-')[-1], 16) + except ValueError: + raise exception.InvalidEc2Id(ec2_id=ec2_id) def id_to_ec2_id(instance_id, template='i-%08x'): diff --git a/nova/compute/api.py b/nova/compute/api.py index 63884be97..a12f8d515 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -19,6 +19,7 @@ """Handles all requests relating to instances (guest vms).""" import datetime +import eventlet import re import time @@ -42,6 +43,8 @@ LOG = logging.getLogger('nova.compute.api') FLAGS = flags.FLAGS flags.DECLARE('vncproxy_topic', 'nova.vnc') +flags.DEFINE_integer('find_host_timeout', 30, + 'Timeout after NN seconds when looking for a host.') def generate_default_hostname(instance_id): @@ -484,7 +487,7 @@ class API(base.Base): def _find_host(self, context, instance_id): """Find the host associated with an instance.""" - for attempts in xrange(10): + for attempts in xrange(FLAGS.find_host_timeout): instance = self.get(context, instance_id) host = instance["host"] if host: @@ -493,6 +496,15 @@ class API(base.Base): raise exception.Error(_("Unable to find host for Instance %s") % instance_id) + def _set_admin_password(self, context, instance_id, password): + """Set the root/admin password for the given instance.""" + host = self._find_host(context, instance_id) + + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "set_admin_password", + "args": {"instance_id": instance_id, "new_pass": password}}) + def snapshot(self, context, instance_id, name): """Snapshot the given instance. @@ -646,12 +658,8 @@ class API(base.Base): def set_admin_password(self, context, instance_id, password=None): """Set the root/admin password for the given instance.""" - host = self._find_host(context, instance_id) - - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "set_admin_password", - "args": {"instance_id": instance_id, "new_pass": password}}) + eventlet.spawn_n(self._set_admin_password(context, instance_id, + password)) def inject_file(self, context, instance_id): """Write a file to the given instance.""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ae5b50ef3..923feaa59 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -77,7 +77,8 @@ flags.DEFINE_integer("rescue_timeout", 0, " Set to 0 to disable.") flags.DEFINE_bool('auto_assign_floating_ip', False, 'Autoassigning floating ip to VM') - +flags.DEFINE_integer('host_state_interval', 120, + 'Interval in seconds for querying the host status') LOG = logging.getLogger('nova.compute.manager') @@ -131,6 +132,7 @@ class ComputeManager(manager.SchedulerDependentManager): self.network_manager = utils.import_object(FLAGS.network_manager) self.volume_manager = utils.import_object(FLAGS.volume_manager) self.network_api = network.API() + self._last_host_check = 0 super(ComputeManager, self).__init__(service_name="compute", *args, **kwargs) @@ -425,6 +427,12 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.audit(_("Instance %s: Root password set"), instance_ref["name"]) break + except NotImplementedError: + # NOTE(dprince): if the driver doesn't implement + # set_admin_password we break to avoid a loop + LOG.warn(_('set_admin_password is not implemented ' + 'by this driver.')) + break except Exception, e: # Catch all here because this could be anything. LOG.exception(e) @@ -1094,6 +1102,13 @@ class ComputeManager(manager.SchedulerDependentManager): error_list.append(ex) try: + self._report_driver_status() + except Exception as ex: + LOG.warning(_("Error during report_driver_status(): %s"), + unicode(ex)) + error_list.append(ex) + + try: self._poll_instance_states(context) except Exception as ex: LOG.warning(_("Error during instance poll: %s"), @@ -1102,6 +1117,16 @@ class ComputeManager(manager.SchedulerDependentManager): return error_list + def _report_driver_status(self): + curr_time = time.time() + if curr_time - self._last_host_check > FLAGS.host_state_interval: + self._last_host_check = curr_time + LOG.info(_("Updating host status")) + # This will grab info about the host and queue it + # to be sent to the Schedulers. + self.update_service_capabilities( + self.driver.get_host_stats(refresh=True)) + def _poll_instance_states(self, context): vm_instances = self.driver.list_instances_detail() vm_instances = dict((vm.name, vm) for vm in vm_instances) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py b/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py index 9e7ab3554..63bbaccc1 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py @@ -17,15 +17,13 @@ # under the License. ## Table code mostly autogenerated by genmodel.py -from sqlalchemy import * -from migrate import * - +from sqlalchemy import Boolean, Column, DateTime, ForeignKey +from sqlalchemy import ForeignKeyConstraint, Integer, MetaData, String +from sqlalchemy import Table, Text from nova import log as logging - meta = MetaData() - auth_tokens = Table('auth_tokens', meta, Column('created_at', DateTime(timezone=False)), Column('updated_at', DateTime(timezone=False)), diff --git a/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py b/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py index 413536a59..9bb8a8ada 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py @@ -16,15 +16,12 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - +from sqlalchemy import Boolean, Column, DateTime, ForeignKey +from sqlalchemy import Integer, MetaData, String, Table, Text from nova import log as logging - meta = MetaData() - # Just for the ForeignKey and column creation to succeed, these are not the # actual definitions of instances or services. instances = Table('instances', meta, diff --git a/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py b/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py index 5ba7910f1..8e0de4d2b 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py @@ -15,15 +15,10 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - -from nova import log as logging - +from sqlalchemy import Column, Integer, MetaData, String, Table meta = MetaData() - networks = Table('networks', meta, Column('id', Integer(), primary_key=True, nullable=False), ) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py b/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py index ade981687..0abea374c 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py @@ -13,15 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - +from sqlalchemy import Boolean, Column, DateTime, Integer +from sqlalchemy import MetaData, String, Table from nova import log as logging - meta = MetaData() - # # New Tables # diff --git a/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py b/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py index 4cb07e0d8..a1a86e3b4 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py @@ -15,15 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer +from sqlalchemy import MetaData, String, Table from nova import log as logging - meta = MetaData() - # Just for the ForeignKey and column creation to succeed, these are not the # actual definitions of instances or services. instances = Table('instances', meta, diff --git a/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py b/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py index 705fc8ff3..4627d3332 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py @@ -15,11 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - -from nova import log as logging - +from sqlalchemy import Column, Integer, MetaData, String, Table meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py index 427934d53..6f2668040 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py @@ -13,15 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - -from nova import log as logging - +from sqlalchemy import Column, Integer, MetaData, String, Table meta = MetaData() - # Table stub-definitions # Just for the ForeignKey and column creation to succeed, these are not the # actual definitions of instances or services. diff --git a/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py b/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py index 5e2cb69d9..63999f6ff 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py @@ -13,15 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - -from nova import api -from nova import db +from sqlalchemy import Boolean, Column, DateTime, Integer +from sqlalchemy import MetaData, String, Table from nova import log as logging -import datetime - meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py index 4fda525f1..0f2d0079a 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py @@ -15,12 +15,10 @@ # License for the specific language governing permissions and limitations # under the License.from sqlalchemy import * -from sqlalchemy import * -from migrate import * - +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer +from sqlalchemy import MetaData, String, Table from nova import log as logging - meta = MetaData() # Just for the ForeignKey and column creation to succeed, these are not the diff --git a/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py index eb3066894..a5b80586e 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py @@ -14,12 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from sqlalchemy.sql import text -from migrate import * - -from nova import log as logging - +from sqlalchemy import Column, Integer, MetaData, String, Table meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py b/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py index 23ccccb4e..b2b0256d2 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py @@ -16,10 +16,9 @@ # License for the specific language governing permissions and limitations # under the License. -from migrate import * +from sqlalchemy import Boolean, Column, DateTime, Integer, MetaData +from sqlalchemy import Table, Text from nova import log as logging -from sqlalchemy import * - meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py b/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py index e87085668..10d250522 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py @@ -13,15 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from migrate import * - -from nova import log as logging - +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer +from sqlalchemy import MetaData, String, Table meta = MetaData() - # Table stub-definitions # Just for the ForeignKey and column creation to succeed, these are not the # actual definitions of instances or services. diff --git a/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py index 3fb92e85c..7246839b7 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py @@ -15,11 +15,7 @@ # License for the specific language governing permissions and limitations # under the License.from sqlalchemy import * -from sqlalchemy import * -from migrate import * - -from nova import log as logging - +from sqlalchemy import Column, Integer, MetaData, Table meta = MetaData() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py index 334d1f255..62216be12 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py @@ -14,16 +14,11 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from sqlalchemy.sql import text -from migrate import * - +from sqlalchemy import Column, Integer, MetaData, String, Table #from nova import log as logging - meta = MetaData() - c_instance_type = Column('instance_type', String(length=255, convert_unicode=False, assert_unicode=None, unicode_error=None, diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py index 29b26b3dd..375760c84 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -15,14 +15,10 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import * -from sqlalchemy.sql import text -from migrate import * - +from sqlalchemy import Boolean, Column, MetaData, Table meta = MetaData() - c_auto_assigned = Column('auto_assigned', Boolean, default=False) diff --git a/nova/exception.py b/nova/exception.py index 9905fb19b..cf6069454 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -244,6 +244,10 @@ class InstanceUnacceptable(Invalid): message = _("Instance %(instance_id)s is unacceptable") + ": %(reason)s" +class InvalidEc2Id(Invalid): + message = _("Ec2 id %(ec2_id)s is unacceptable.") + + class NotFound(NovaException): message = _("Resource could not be found.") diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index ed8e65c77..483f3225c 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -96,8 +96,8 @@ class FlavorFilter(HostFilter): selected_hosts = [] for host, services in zone_manager.service_states.iteritems(): capabilities = services.get('compute', {}) - host_ram_mb = capabilities['host_memory']['free'] - disk_bytes = capabilities['disk']['available'] + host_ram_mb = capabilities['host_memory_free'] + disk_bytes = capabilities['disk_available'] if host_ram_mb >= instance_type['memory_mb'] and \ disk_bytes >= instance_type['local_gb']: selected_hosts.append((host, capabilities)) @@ -106,16 +106,16 @@ class FlavorFilter(HostFilter): #host entries (currently) are like: # {'host_name-description': 'Default install of XenServer', # 'host_hostname': 'xs-mini', -# 'host_memory': {'total': 8244539392, -# 'overhead': 184225792, -# 'free': 3868327936, -# 'free-computed': 3840843776}, +# 'host_memory_total': 8244539392, +# 'host_memory_overhead': 184225792, +# 'host_memory_free': 3868327936, +# 'host_memory_free_computed': 3840843776}, # 'host_other-config': {}, # 'host_ip_address': '192.168.1.109', # 'host_cpu_info': {}, -# 'disk': {'available': 32954957824, -# 'total': 50394562560, -# 'used': 17439604736}, +# 'disk_available': 32954957824, +# 'disk_total': 50394562560, +# 'disk_used': 17439604736}, # 'host_uuid': 'cedb9b39-9388-41df-8891-c5c9a0c0fe5f', # 'host_name-label': 'xs-mini'} @@ -221,8 +221,8 @@ class JsonFilter(HostFilter): required_ram = instance_type['memory_mb'] required_disk = instance_type['local_gb'] query = ['and', - ['>=', '$compute.host_memory.free', required_ram], - ['>=', '$compute.disk.available', required_disk] + ['>=', '$compute.host_memory_free', required_ram], + ['>=', '$compute.disk_available', required_disk] ] return (self._full_name(), json.dumps(query)) diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index fa0e56597..97f401b87 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -28,10 +28,12 @@ import StringIO import webob from nova import context +from nova import exception from nova import test from nova.api import ec2 -from nova.api.ec2 import cloud from nova.api.ec2 import apirequest +from nova.api.ec2 import cloud +from nova.api.ec2 import ec2utils from nova.auth import manager @@ -101,6 +103,21 @@ class XmlConversionTestCase(test.TestCase): self.assertEqual(conv('-0'), 0) +class Ec2utilsTestCase(test.TestCase): + def test_ec2_id_to_id(self): + self.assertEqual(ec2utils.ec2_id_to_id('i-0000001e'), 30) + self.assertEqual(ec2utils.ec2_id_to_id('ami-1d'), 29) + + def test_bad_ec2_id(self): + self.assertRaises(exception.InvalidEc2Id, + ec2utils.ec2_id_to_id, + 'badone') + + def test_id_to_ec2_id(self): + self.assertEqual(ec2utils.id_to_ec2_id(30), 'i-0000001e') + self.assertEqual(ec2utils.id_to_ec2_id(29, 'ami-%08x'), 'ami-0000001d') + + class ApiEc2TestCase(test.TestCase): """Unit test for the cloud controller on an EC2 API""" def setUp(self): diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 393110791..55e7ae0c4 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -21,6 +21,7 @@ Tests For Compute import datetime import mox +import stubout from nova import compute from nova import context @@ -52,6 +53,10 @@ class FakeTime(object): self.counter += t +def nop_report_driver_status(self): + pass + + class ComputeTestCase(test.TestCase): """Test case for compute""" def setUp(self): @@ -649,6 +654,10 @@ class ComputeTestCase(test.TestCase): def test_run_kill_vm(self): """Detect when a vm is terminated behind the scenes""" + self.stubs = stubout.StubOutForTesting() + self.stubs.Set(compute_manager.ComputeManager, + '_report_driver_status', nop_report_driver_status) + instance_id = self._create_instance() self.compute.run_instance(self.context, instance_id) diff --git a/nova/tests/test_host_filter.py b/nova/tests/test_host_filter.py index 31e40ae1d..c029d41e6 100644 --- a/nova/tests/test_host_filter.py +++ b/nova/tests/test_host_filter.py @@ -43,16 +43,16 @@ class HostFilterTestCase(test.TestCase): # which means ... don't go above 10 hosts. return {'host_name-description': 'XenServer %s' % multiplier, 'host_hostname': 'xs-%s' % multiplier, - 'host_memory': {'total': 100, - 'overhead': 10, - 'free': 10 + multiplier * 10, - 'free-computed': 10 + multiplier * 10}, + 'host_memory_total': 100, + 'host_memory_overhead': 10, + 'host_memory_free': 10 + multiplier * 10, + 'host_memory_free-computed': 10 + multiplier * 10, 'host_other-config': {}, 'host_ip_address': '192.168.1.%d' % (100 + multiplier), 'host_cpu_info': {}, - 'disk': {'available': 100 + multiplier * 100, - 'total': 1000, - 'used': 0}, + 'disk_available': 100 + multiplier * 100, + 'disk_total': 1000, + 'disk_used': 0, 'host_uuid': 'xxx-%d' % multiplier, 'host_name-label': 'xs-%s' % multiplier} @@ -131,12 +131,12 @@ class HostFilterTestCase(test.TestCase): raw = ['or', ['and', - ['<', '$compute.host_memory.free', 30], - ['<', '$compute.disk.available', 300] + ['<', '$compute.host_memory_free', 30], + ['<', '$compute.disk_available', 300] ], ['and', - ['>', '$compute.host_memory.free', 70], - ['>', '$compute.disk.available', 700] + ['>', '$compute.host_memory_free', 70], + ['>', '$compute.disk_available', 700] ] ] cooked = json.dumps(raw) @@ -149,7 +149,7 @@ class HostFilterTestCase(test.TestCase): self.assertEquals('host%02d' % index, host) raw = ['not', - ['=', '$compute.host_memory.free', 30], + ['=', '$compute.host_memory_free', 30], ] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) @@ -160,7 +160,7 @@ class HostFilterTestCase(test.TestCase): for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts): self.assertEquals('host%02d' % index, host) - raw = ['in', '$compute.host_memory.free', 20, 40, 60, 80, 100] + raw = ['in', '$compute.host_memory_free', 20, 40, 60, 80, 100] cooked = json.dumps(raw) hosts = driver.filter_hosts(self.zone_manager, cooked) diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index e7b5c826e..8f7e83c3e 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -17,9 +17,9 @@ import os import tempfile +from nova import exception from nova import test from nova import utils -from nova import exception class ExecuteTestCase(test.TestCase): diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a4e679817..be1e35697 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -18,6 +18,7 @@ import eventlet import functools +import json import os import re import stubout @@ -688,3 +689,52 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_VHD self.fake_instance.kernel_id = None self.assert_disk_type(vm_utils.ImageType.DISK_VHD) + + +class FakeXenApi(object): + """Fake XenApi for testing HostState.""" + + class FakeSR(object): + def get_record(self, ref): + return {'virtual_allocation': 10000, + 'physical_utilisation': 20000} + + SR = FakeSR() + + +class FakeSession(object): + """Fake Session class for HostState testing.""" + + def async_call_plugin(self, *args): + return None + + def wait_for_task(self, *args): + vm = {'total': 10, + 'overhead': 20, + 'free': 30, + 'free-computed': 40} + return json.dumps({'host_memory': vm}) + + def get_xenapi(self): + return FakeXenApi() + + +class HostStateTestCase(test.TestCase): + """Tests HostState, which holds metrics from XenServer that get + reported back to the Schedulers.""" + + def _fake_safe_find_sr(self, session): + """None SR ref since we're ignoring it in FakeSR.""" + return None + + def test_host_state(self): + self.stubs = stubout.StubOutForTesting() + self.stubs.Set(vm_utils, 'safe_find_sr', self._fake_safe_find_sr) + host_state = xenapi_conn.HostState(FakeSession()) + stats = host_state._stats + self.assertEquals(stats['disk_total'], 10000) + self.assertEquals(stats['disk_used'], 20000) + self.assertEquals(stats['host_memory_total'], 10) + self.assertEquals(stats['host_memory_overhead'], 20) + self.assertEquals(stats['host_memory_free'], 30) + self.assertEquals(stats['host_memory_free_computed'], 40) diff --git a/nova/utils.py b/nova/utils.py index 44ee1b623..0c469b1de 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -232,9 +232,12 @@ def default_flagfile(filename='nova.conf'): # turn relative filename into an absolute path script_dir = os.path.dirname(inspect.stack()[-1][1]) filename = os.path.abspath(os.path.join(script_dir, filename)) - if os.path.exists(filename): - flagfile = ['--flagfile=%s' % filename] - sys.argv = sys.argv[:1] + flagfile + sys.argv[1:] + if not os.path.exists(filename): + filename = "./nova.conf" + if not os.path.exists(filename): + filename = '/etc/nova/nova.conf' + flagfile = ['--flagfile=%s' % filename] + sys.argv = sys.argv[:1] + flagfile + sys.argv[1:] def debug(arg): diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 9026e737e..1142e97a4 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -486,3 +486,11 @@ class HyperVConnection(driver.ComputeDriver): def update_available_resource(self, ctxt, host): """This method is supported only by libvirt.""" return + + def update_host_status(self): + """See xenapi_conn.py implementation.""" + pass + + def get_host_stats(self, refresh=False): + """See xenapi_conn.py implementation.""" + pass diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9780c69a6..555e44ce2 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1582,6 +1582,14 @@ class LibvirtConnection(driver.ComputeDriver): """See comments of same method in firewall_driver.""" self.firewall_driver.unfilter_instance(instance_ref) + def update_host_status(self): + """See xenapi_conn.py implementation.""" + pass + + def get_host_stats(self, refresh=False): + """See xenapi_conn.py implementation.""" + pass + class FirewallDriver(object): def prepare_instance_filter(self, instance, network_info=None): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 5dc6b034f..eb572f295 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -57,6 +57,8 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block. - suffix "_rec" for record objects """ +import json +import random import sys import urlparse import xmlrpclib @@ -67,10 +69,12 @@ from eventlet import timeout from nova import context from nova import db +from nova import exception from nova import utils from nova import flags from nova import log as logging from nova.virt import driver +from nova.virt.xenapi import vm_utils from nova.virt.xenapi.vmops import VMOps from nova.virt.xenapi.volumeops import VolumeOps @@ -168,6 +172,13 @@ class XenAPIConnection(driver.ComputeDriver): session = XenAPISession(url, user, pw) self._vmops = VMOps(session) self._volumeops = VolumeOps(session) + self._host_state = None + + @property + def HostState(self): + if not self._host_state: + self._host_state = HostState(self.session) + return self._host_state def init_host(self, host): #FIXME(armando): implement this @@ -315,6 +326,16 @@ class XenAPIConnection(driver.ComputeDriver): """This method is supported only by libvirt.""" raise NotImplementedError('This method is supported only by libvirt.') + def update_host_status(self): + """Update the status info of the host, and return those values + to the calling program.""" + return self.HostState.update_status() + + def get_host_stats(self, refresh=False): + """Return the current state of the host. If 'refresh' is + True, run the update first.""" + return self.HostState.get_host_stats(refresh=refresh) + class XenAPISession(object): """The session to invoke XenAPI SDK calls""" @@ -430,6 +451,65 @@ class XenAPISession(object): raise +class HostState(object): + """Manages information about the XenServer host this compute + node is running on. + """ + def __init__(self, session): + super(HostState, self).__init__() + self._session = session + self._stats = {} + self.update_status() + + def get_host_stats(self, refresh=False): + """Return the current state of the host. If 'refresh' is + True, run the update first. + """ + if refresh: + self.update_status() + return self._stats + + def update_status(self): + """Since under Xenserver, a compute node runs on a given host, + we can get host status information using xenapi. + """ + LOG.debug(_("Updating host stats")) + # Make it something unlikely to match any actual instance ID + task_id = random.randint(-80000, -70000) + task = self._session.async_call_plugin("xenhost", "host_data", {}) + task_result = self._session.wait_for_task(task, task_id) + if not task_result: + task_result = json.dumps("") + try: + data = json.loads(task_result) + except ValueError as e: + # Invalid JSON object + LOG.error(_("Unable to get updated status: %s") % e) + return + # Get the SR usage + try: + sr_ref = vm_utils.safe_find_sr(self._session) + except exception.NotFound as e: + # No SR configured + LOG.error(_("Unable to get SR for this host: %s") % e) + return + sr_rec = self._session.get_xenapi().SR.get_record(sr_ref) + total = int(sr_rec["virtual_allocation"]) + used = int(sr_rec["physical_utilisation"]) + data["disk_total"] = total + data["disk_used"] = used + data["disk_available"] = total - used + host_memory = data.get('host_memory', None) + if host_memory: + data["host_memory_total"] = host_memory.get('total', 0) + data["host_memory_overhead"] = host_memory.get('overhead', 0) + data["host_memory_free"] = host_memory.get('free', 0) + data["host_memory_free_computed"] = \ + host_memory.get('free-computed', 0) + del data['host_memory'] + self._stats = data + + def _parse_xmlrpc_value(val): """Parse the given value as if it were an XML-RPC value. This is sometimes used as the format for the task.result field.""" diff --git a/tools/pip-requires b/tools/pip-requires index e438c2a41..f7eb1703e 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -33,3 +33,4 @@ nova-adminclient suds==0.4 coverage nosexcover +GitPython |
