From 7e379f6a77f53ad4d6ddc98fbb30cd853933bb08 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 14 Sep 2011 14:38:40 -0500 Subject: Initial pass at automatically confirming resizes after a given window. --- nova/compute/manager.py | 16 ++++++++++++++-- nova/db/api.py | 5 +++++ nova/db/sqlalchemy/api.py | 22 +++++++++++++++++++--- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7915830ec..6ddbb20b0 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -81,6 +81,9 @@ flags.DEFINE_integer('live_migration_retry_count', 30, flags.DEFINE_integer("rescue_timeout", 0, "Automatically unrescue an instance after N seconds." " Set to 0 to disable.") +flags.DEFINE_integer("resize_confirm_window", 0, + "Automatically confirm resizes after N seconds." + " Set to 0 to disable.") flags.DEFINE_integer('host_state_interval', 120, 'Interval in seconds for querying the host status') @@ -1644,14 +1647,23 @@ class ComputeManager(manager.SchedulerDependentManager): self.driver.poll_rescued_instances(FLAGS.rescue_timeout) except Exception as ex: LOG.warning(_("Error during poll_rescued_instances: %s"), - unicode(ex)) + unicode(ex)) + error_list.append(ex) + + try: + if FLAGS.resize_confirm_window > 0: + self.driver.poll_unconfirmed_resizes( + FLAGS.resize_confirm_window) + except Exception as ex: + LOG.warning(_("Error during poll_unconfirmed_resizes: %s"), + unicode(ex)) error_list.append(ex) try: self._report_driver_status() except Exception as ex: LOG.warning(_("Error during report_driver_status(): %s"), - unicode(ex)) + unicode(ex)) error_list.append(ex) try: diff --git a/nova/db/api.py b/nova/db/api.py index a9d2dc065..53cdc42c2 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -321,6 +321,11 @@ def migration_get_by_instance_and_status(context, instance_uuid, status): status) +def migration_get_all_unconfirmed(context, confirm_window): + """Finds all unconfirmed migrations within the confirmation window.""" + return IMPL.migration_get_all_unconfirmed(context, confirm_window) + + #################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e5a661c7f..58e6aef6e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -15,9 +15,10 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -""" -Implementation of SQLAlchemy backend. -""" + +"""Implementation of SQLAlchemy backend.""" + +import datetime import re import warnings @@ -3194,6 +3195,21 @@ def migration_get_by_instance_and_status(context, instance_uuid, status): return result +@require_admin_context +def migration_get_all_unconfirmed(context, confirm_window, session=None): + confirm_window = datetime.datetime.utcnow() - datetime.timedelta( + seconds=confirm_window) + + if not session: + session = get_session() + + results = session.query(models.Migration).\ + filter(models.Migration.updated_at <= confirm_window).\ + filter_by(status="VERIFY_RESIZE").all() + + return results + + ################## -- cgit From 2477ebf17add4c0e44ad9dd9a7ac3b632a8929f4 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 15 Sep 2011 13:44:49 -0500 Subject: Added virt-level support for polling unconfirmed resizes. --- nova/virt/driver.py | 5 +++++ nova/virt/fake.py | 3 +++ nova/virt/hyperv.py | 3 +++ nova/virt/libvirt/connection.py | 4 ++++ nova/virt/xenapi/vmops.py | 21 +++++++++++++++++++++ nova/virt/xenapi_conn.py | 4 ++++ 6 files changed, 40 insertions(+) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 301346c6b..fc47d8d2d 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -469,6 +469,11 @@ class ComputeDriver(object): # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() + def poll_unconfirmed_resizes(self, resize_confirm_window): + """Poll for unconfirmed resizes""" + # TODO(Vek): Need to pass context in for access to auth_token + raise NotImplementedError() + def host_power_action(self, host, action): """Reboots, shuts down or powers up the host.""" raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 3596d8353..96f521ee7 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -130,6 +130,9 @@ class FakeConnection(driver.ComputeDriver): def poll_rescued_instances(self, timeout): pass + def poll_unconfirmed_resizes(self, resize_confirm_window): + pass + def migrate_disk_and_power_off(self, instance, dest): pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 76925b405..fbf898317 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -487,6 +487,9 @@ class HyperVConnection(driver.ComputeDriver): def poll_rescued_instances(self, timeout): pass + def poll_unconfirmed_resizes(self, resize_confirm_window): + pass + def update_available_resource(self, ctxt, host): """This method is supported only by libvirt.""" return diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 18e643ea8..d480c723f 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -584,6 +584,10 @@ class LibvirtConnection(driver.ComputeDriver): def poll_rescued_instances(self, timeout): pass + @exception.wrap_exception() + def poll_unconfirmed_resizes(self, resize_confirm_window): + pass + # NOTE(ilyaalekseyev): Implementation like in multinics # for xenapi(tr3buchet) @exception.wrap_exception() diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 6b56d668e..180e746f1 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -38,6 +38,7 @@ from nova import ipv6 from nova import log as logging from nova import utils +from nova.compute import api as compute from nova.compute import power_state from nova.virt import driver from nova.virt.xenapi.network_utils import NetworkHelper @@ -77,6 +78,7 @@ class VMOps(object): """ def __init__(self, session): self.XenAPI = session.get_imported_xenapi() + self.compute_api = compute.API() self._session = session self.poll_rescue_last_ran = None VMHelper.XenAPI = self.XenAPI @@ -1041,6 +1043,25 @@ class VMOps(object): self._session.call_xenapi("VM.start", original_vm_ref, False, False) + def poll_unconfirmed_resizes(self, resize_confirm_window): + """Poll for unconfirmed resizes. + + Look for any unconfirmed resizes that are older than + `resize_confirm_window` and automatically confirm them. + """ + ctxt = nova_context.get_admin_context() + migrations = db.migration_get_all_unconfirmed(ctxt, + resize_confirm_window) + + migrations_info = dict(migration_count=len(migrations), + confirm_window=FLAGS.resize_confirm_window) + LOG.info(_("Found %(migration_count)d unconfirmed migrations older " + "than %(confirm_window)d seconds") % migrations_info) + + for migration in migrations: + LOG.info(_("Automatically confirming migration %d"), migration.id) + self.compute_api.confirm_resize(ctxt, migration.instance_uuid) + def get_info(self, instance): """Return data about VM instance.""" vm_ref = self._get_vm_opaque_ref(instance) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index f6dbc19f8..7fc683a9f 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -254,6 +254,10 @@ class XenAPIConnection(driver.ComputeDriver): """Poll for rescued instances""" self._vmops.poll_rescued_instances(timeout) + def poll_unconfirmed_resizes(self, resize_confirm_window): + """Poll for unconfirmed resizes""" + self._vmops.poll_unconfirmed_resizes(resize_confirm_window) + def reset_network(self, instance): """reset networking for specified instance""" self._vmops.reset_network(instance) -- cgit From 62516cac832bd24003f1bf29b4a03e7f5d9a1579 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 15 Sep 2011 14:09:14 -0500 Subject: Added a unit test. --- nova/tests/test_virt_drivers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py index 440d3401b..8e20e999f 100644 --- a/nova/tests/test_virt_drivers.py +++ b/nova/tests/test_virt_drivers.py @@ -176,6 +176,10 @@ class _VirtDriverTestCase(test.TestCase): def test_poll_rescued_instances(self): self.connection.poll_rescued_instances(10) + @catch_notimplementederror + def test_poll_unconfirmed_resizes(self): + self.connection.poll_unconfirmed_resizes(10) + @catch_notimplementederror def test_migrate_disk_and_power_off(self): instance_ref = test_utils.get_test_instance() -- cgit From 90f01055a92153709a90115688a8fce3d3029976 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 16 Sep 2011 13:06:21 -0500 Subject: Only log migration info if they exist. --- nova/virt/xenapi/vmops.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 180e746f1..55a6a4a78 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1055,8 +1055,10 @@ class VMOps(object): migrations_info = dict(migration_count=len(migrations), confirm_window=FLAGS.resize_confirm_window) - LOG.info(_("Found %(migration_count)d unconfirmed migrations older " - "than %(confirm_window)d seconds") % migrations_info) + + if migrations_info["migration_count"] > 0: + LOG.info(_("Found %(migration_count)d unconfirmed migrations " + "older than %(confirm_window)d seconds") % migrations_info) for migration in migrations: LOG.info(_("Automatically confirming migration %d"), migration.id) -- cgit From bd5a0a2bf42686efb81bb08de5d6938e2c076f0b Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 19 Sep 2011 11:10:50 -0500 Subject: Corrected the status in DB call. --- nova/db/sqlalchemy/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 779f20ae1..6fe46eb88 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3210,7 +3210,7 @@ def migration_get_all_unconfirmed(context, confirm_window, session=None): results = session.query(models.Migration).\ filter(models.Migration.updated_at <= confirm_window).\ - filter_by(status="VERIFY_RESIZE").all() + filter_by(status="FINISHED").all() return results -- cgit From 66977838d02ad51ced525321b21e648bcc2065bc Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 19 Sep 2011 11:58:20 -0500 Subject: Added unit test. --- nova/tests/test_db_api.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index 60d7abd8c..c99709ffa 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -18,6 +18,8 @@ """Unit tests for the DB API""" +import datetime + from nova import test from nova import context from nova import db @@ -95,3 +97,26 @@ class DbApiTestCase(test.TestCase): self.assertEqual(result[0].id, inst2.id) self.assertEqual(result[1].id, inst1.id) self.assertTrue(result[1].deleted) + + def test_migration_get_all_unconfirmed(self): + ctxt = context.get_admin_context() + + # Ensure no migrations are returned. + results = db.migration_get_all_unconfirmed(ctxt, 10) + self.assertEqual(0, len(results)) + + # Ensure one migration older than 10 seconds is returned. + updated_at = datetime.datetime(2000, 01, 01, 12, 00, 00) + values = {"status": "FINISHED", "updated_at": updated_at} + migration = db.migration_create(ctxt, values) + results = db.migration_get_all_unconfirmed(ctxt, 10) + self.assertEqual(1, len(results)) + db.migration_update(ctxt, migration.id, {"status": "CONFIRMED"}) + + # Ensure the new migration is not returned. + updated_at = datetime.datetime.utcnow() + values = {"status": "FINISHED", "updated_at": updated_at} + migration = db.migration_create(ctxt, values) + results = db.migration_get_all_unconfirmed(ctxt, 10) + self.assertEqual(0, len(results)) + db.migration_update(ctxt, migration.id, {"status": "CONFIRMED"}) -- cgit