From 76461c83d1807981a11f8570ba815ecb4979480e Mon Sep 17 00:00:00 2001
From: Michael Still <mikal@stillhq.com>
Date: Wed, 6 Feb 2013 13:24:09 +1100
Subject: [PATCH] libvirt: Use uuid for instance directory name

This is done in a backwards compatible manner to deal with instances
started under the old regieme where we used a less unique name.

Resolves bug 1023865.

Change-Id: I4c9b7b291a30a7f3c2c9305c8fa346dc582bee88
---
 nova/tests/fake_libvirt_utils.py |  6 +++---
 nova/tests/test_imagebackend.py  | 34 +++++++++++++++++++++++++-------
 nova/tests/test_imagecache.py    | 13 +++++++-----
 nova/tests/test_libvirt.py       | 30 +++++++++++++++++-----------
 nova/tests/test_virt_drivers.py  |  5 +++++
 nova/virt/libvirt/driver.py      |  3 +--
 nova/virt/libvirt/imagecache.py  |  7 ++++++-
 nova/virt/libvirt/utils.py       | 16 ++++++++-------
 8 files changed, 77 insertions(+), 37 deletions(-)

diff --git a/nova/tests/fake_libvirt_utils.py b/nova/tests/fake_libvirt_utils.py
index 285a4b7e3b0e..9dc1db76c4fb 100644
--- a/nova/tests/fake_libvirt_utils.py
+++ b/nova/tests/fake_libvirt_utils.py
@@ -18,6 +18,7 @@ import os
 import StringIO
 
 from nova.openstack.common import cfg
+from nova.virt.libvirt import utils as libvirt_utils
 
 
 CONF = cfg.CONF
@@ -141,9 +142,8 @@ def fetch_image(context, target, image_id, user_id, project_id):
     pass
 
 
-def get_instance_path(instance):
-    # TODO(mikal): we should really just call the real one here
-    return os.path.join(CONF.instances_path, instance['name'])
+def get_instance_path(instance, forceold=False):
+    return libvirt_utils.get_instance_path(instance, forceold=forceold)
 
 
 def pick_disk_driver_name(is_block_dev=False):
diff --git a/nova/tests/test_imagebackend.py b/nova/tests/test_imagebackend.py
index 87e51819d8c9..bd88fcd608b1 100644
--- a/nova/tests/test_imagebackend.py
+++ b/nova/tests/test_imagebackend.py
@@ -19,16 +19,16 @@ import fixtures
 import os
 
 from nova.openstack.common import cfg
+from nova.openstack.common import uuidutils
 from nova import test
 from nova.tests import fake_libvirt_utils
 from nova.virt.libvirt import imagebackend
-from nova.virt.libvirt import utils as libvirt_utils
 
 CONF = cfg.CONF
 
 
 class _ImageTestCase(object):
-    INSTANCES_PATH = '/fake'
+    INSTANCES_PATH = '/instances_path'
 
     def mock_create_image(self, image):
         def create_image(fn, base, size, *args, **kwargs):
@@ -39,14 +39,19 @@ class _ImageTestCase(object):
         super(_ImageTestCase, self).setUp()
         self.flags(disable_process_locking=True,
                    instances_path=self.INSTANCES_PATH)
-        self.INSTANCE = {'name': 'instance'}
+        self.INSTANCE = {'name': 'instance',
+                         'uuid': uuidutils.generate_uuid()}
         self.NAME = 'fake.vm'
         self.TEMPLATE = 'template'
 
+        self.OLD_STYLE_INSTANCE_PATH = \
+            fake_libvirt_utils.get_instance_path(self.INSTANCE, forceold=True)
         self.PATH = os.path.join(
-            libvirt_utils.get_instance_path(self.INSTANCE), self.NAME)
-        self.TEMPLATE_DIR = os.path.join(CONF.instances_path,
-                                         '_base')
+            fake_libvirt_utils.get_instance_path(self.INSTANCE), self.NAME)
+
+        # TODO(mikal): rename template_dir to base_dir and template_path
+        # to cached_image_path. This will be less confusing.
+        self.TEMPLATE_DIR = os.path.join(CONF.instances_path, '_base')
         self.TEMPLATE_PATH = os.path.join(self.TEMPLATE_DIR, 'template')
 
         self.useFixture(fixtures.MonkeyPatch(
@@ -55,6 +60,8 @@ class _ImageTestCase(object):
 
     def test_cache(self):
         self.mox.StubOutWithMock(os.path, 'exists')
+        if self.OLD_STYLE_INSTANCE_PATH:
+            os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False)
         os.path.exists(self.TEMPLATE_DIR).AndReturn(False)
         os.path.exists(self.PATH).AndReturn(False)
         os.path.exists(self.TEMPLATE_PATH).AndReturn(False)
@@ -72,6 +79,8 @@ class _ImageTestCase(object):
 
     def test_cache_image_exists(self):
         self.mox.StubOutWithMock(os.path, 'exists')
+        if self.OLD_STYLE_INSTANCE_PATH:
+            os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False)
         os.path.exists(self.TEMPLATE_DIR).AndReturn(True)
         os.path.exists(self.PATH).AndReturn(True)
         os.path.exists(self.TEMPLATE_PATH).AndReturn(True)
@@ -84,6 +93,8 @@ class _ImageTestCase(object):
 
     def test_cache_base_dir_exists(self):
         self.mox.StubOutWithMock(os.path, 'exists')
+        if self.OLD_STYLE_INSTANCE_PATH:
+            os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False)
         os.path.exists(self.TEMPLATE_DIR).AndReturn(True)
         os.path.exists(self.PATH).AndReturn(False)
         os.path.exists(self.TEMPLATE_PATH).AndReturn(False)
@@ -100,6 +111,8 @@ class _ImageTestCase(object):
 
     def test_cache_template_exists(self):
         self.mox.StubOutWithMock(os.path, 'exists')
+        if self.OLD_STYLE_INSTANCE_PATH:
+            os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False)
         os.path.exists(self.TEMPLATE_DIR).AndReturn(True)
         os.path.exists(self.PATH).AndReturn(False)
         os.path.exists(self.TEMPLATE_PATH).AndReturn(True)
@@ -197,6 +210,11 @@ class Qcow2TestCase(_ImageTestCase, test.TestCase):
     def test_create_image_with_size(self):
         fn = self.prepare_mocks()
         fn(target=self.TEMPLATE_PATH)
+        self.mox.StubOutWithMock(os.path, 'exists')
+        if self.OLD_STYLE_INSTANCE_PATH:
+            os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False)
+        os.path.exists(self.TEMPLATE_PATH).AndReturn(False)
+        os.path.exists(self.PATH).AndReturn(False)
         imagebackend.libvirt_utils.create_cow_image(self.TEMPLATE_PATH,
                                                     self.PATH)
         imagebackend.disk.extend(self.PATH, self.SIZE)
@@ -218,6 +236,7 @@ class LvmTestCase(_ImageTestCase, test.TestCase):
         super(LvmTestCase, self).setUp()
         self.flags(libvirt_images_volume_group=self.VG)
         self.LV = '%s_%s' % (self.INSTANCE['name'], self.NAME)
+        self.OLD_STYLE_INSTANCE_PATH = None
         self.PATH = os.path.join('/dev', self.VG, self.LV)
 
         self.disk = imagebackend.disk
@@ -344,7 +363,8 @@ class LvmTestCase(_ImageTestCase, test.TestCase):
 
 
 class BackendTestCase(test.TestCase):
-    INSTANCE = {'name': 'fake-instance'}
+    INSTANCE = {'name': 'fake-instance',
+                'uuid': uuidutils.generate_uuid()}
     NAME = 'fake-name.suffix'
 
     def get_image(self, use_cow, image_type):
diff --git a/nova/tests/test_imagecache.py b/nova/tests/test_imagecache.py
index 611519514321..389612c64807 100644
--- a/nova/tests/test_imagecache.py
+++ b/nova/tests/test_imagecache.py
@@ -47,10 +47,10 @@ class ImageCacheManagerTestCase(test.TestCase):
 
     def setUp(self):
         super(ImageCacheManagerTestCase, self).setUp()
-        self.stock_instance_names = {'instance-00000001': '123',
-                                     'instance-00000002': '456',
-                                     'instance-00000003': '789',
-                                     'banana-42-hamster': '444'}
+        self.stock_instance_names = set(['instance-00000001',
+                                         'instance-00000002',
+                                         'instance-00000003',
+                                         'banana-42-hamster'])
 
     def test_read_stored_checksum_missing(self):
         self.stubs.Set(os.path, 'exists', lambda x: False)
@@ -181,6 +181,9 @@ class ImageCacheManagerTestCase(test.TestCase):
         self.assertTrue(image_cache_manager.used_images['2'] ==
                         (1, 1, ['inst-2', 'inst-3']))
 
+        self.assertTrue('inst-1' in image_cache_manager.instance_names)
+        self.assertTrue('123' in image_cache_manager.instance_names)
+
         self.assertEqual(len(image_cache_manager.image_popularity), 2)
         self.assertEqual(image_cache_manager.image_popularity['1'], 1)
         self.assertEqual(image_cache_manager.image_popularity['2'], 2)
@@ -200,7 +203,7 @@ class ImageCacheManagerTestCase(test.TestCase):
         self.assertTrue(image_cache_manager.used_images['1'] ==
                         (1, 0, ['inst-1']))
         self.assertTrue(image_cache_manager.instance_names ==
-                        set(['inst-1', 'inst-1_resize']))
+                        set(['inst-1', '123', 'inst-1_resize', '123_resize']))
 
         self.assertEqual(len(image_cache_manager.image_popularity), 1)
         self.assertEqual(image_cache_manager.image_popularity['1'], 1)
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index 8a2130c808a9..4dfc3c488570 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -43,6 +43,7 @@ from nova.openstack.common import fileutils
 from nova.openstack.common import importutils
 from nova.openstack.common import jsonutils
 from nova.openstack.common import log as logging
+from nova.openstack.common import uuidutils
 from nova import test
 from nova.tests import fake_libvirt_utils
 from nova.tests import fake_network
@@ -135,7 +136,8 @@ class FakeVirtDomain(object):
 class CacheConcurrencyTestCase(test.TestCase):
     def setUp(self):
         super(CacheConcurrencyTestCase, self).setUp()
-        self.flags(instances_path='nova.compute.manager')
+
+        self.flags(instances_path=self.useFixture(fixtures.TempDir()).path)
 
         # utils.synchronized() will create the lock_path for us if it
         # doesn't already exist. It will also delete it when it's done,
@@ -165,19 +167,18 @@ class CacheConcurrencyTestCase(test.TestCase):
             fake_libvirt_utils))
 
     def tearDown(self):
-        # Make sure the lock_path for this test is cleaned up
-        if os.path.exists(self.lock_path):
-            shutil.rmtree(self.lock_path)
-
         super(CacheConcurrencyTestCase, self).tearDown()
 
     def test_same_fname_concurrency(self):
         # Ensures that the same fname cache runs at a sequentially.
+        uuid = uuidutils.generate_uuid()
+
         backend = imagebackend.Backend(False)
         wait1 = eventlet.event.Event()
         done1 = eventlet.event.Event()
         sig1 = eventlet.event.Event()
-        thr1 = eventlet.spawn(backend.image({'name': 'instance'},
+        thr1 = eventlet.spawn(backend.image({'name': 'instance',
+                                             'uuid': uuid},
                                             'name').cache,
                 _concurrency, 'fname', None,
                 signal=sig1, wait=wait1, done=done1)
@@ -188,7 +189,8 @@ class CacheConcurrencyTestCase(test.TestCase):
         wait2 = eventlet.event.Event()
         done2 = eventlet.event.Event()
         sig2 = eventlet.event.Event()
-        thr2 = eventlet.spawn(backend.image({'name': 'instance'},
+        thr2 = eventlet.spawn(backend.image({'name': 'instance',
+                                             'uuid': uuid},
                                             'name').cache,
                 _concurrency, 'fname', None,
                 signal=sig2, wait=wait2, done=done2)
@@ -209,11 +211,14 @@ class CacheConcurrencyTestCase(test.TestCase):
 
     def test_different_fname_concurrency(self):
         # Ensures that two different fname caches are concurrent.
+        uuid = uuidutils.generate_uuid()
+
         backend = imagebackend.Backend(False)
         wait1 = eventlet.event.Event()
         done1 = eventlet.event.Event()
         sig1 = eventlet.event.Event()
-        thr1 = eventlet.spawn(backend.image({'name': 'instance'},
+        thr1 = eventlet.spawn(backend.image({'name': 'instance',
+                                             'uuid': uuid},
                                             'name').cache,
                 _concurrency, 'fname2', None,
                 signal=sig1, wait=wait1, done=done1)
@@ -224,7 +229,8 @@ class CacheConcurrencyTestCase(test.TestCase):
         wait2 = eventlet.event.Event()
         done2 = eventlet.event.Event()
         sig2 = eventlet.event.Event()
-        thr2 = eventlet.spawn(backend.image({'name': 'instance'},
+        thr2 = eventlet.spawn(backend.image({'name': 'instance',
+                                             'uuid': uuid},
                                             'name').cache,
                 _concurrency, 'fname1', None,
                 signal=sig2, wait=wait2, done=done2)
@@ -2353,8 +2359,8 @@ class LibvirtConnTestCase(test.TestCase):
             ret = conn.pre_live_migration(c, inst_ref, vol, nw_info,
                                           migrate_data)
             self.assertEqual(ret, None)
-            self.assertTrue(os.path.exists('%s/%s/' %
-                           (tmpdir, inst_ref['name'])))
+            self.assertTrue(os.path.exists('%s/%s/' % (tmpdir,
+                                                       inst_ref['uuid'])))
         db.instance_destroy(self.context, inst_ref['uuid'])
 
     def test_pre_block_migration_works_correctly(self):
@@ -2390,7 +2396,7 @@ class LibvirtConnTestCase(test.TestCase):
                                      dummyjson)
 
             self.assertTrue(os.path.exists('%s/%s/' %
-                                           (tmpdir, instance_ref['name'])))
+                                           (tmpdir, instance_ref['uuid'])))
 
         db.instance_destroy(self.context, instance_ref['uuid'])
 
diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py
index a94fdc3c59ae..5a46beffb9b2 100644
--- a/nova/tests/test_virt_drivers.py
+++ b/nova/tests/test_virt_drivers.py
@@ -184,11 +184,16 @@ class VirtDriverLoaderTestCase(_FakeDriverBackendTestCase, test.TestCase):
 class _VirtDriverTestCase(_FakeDriverBackendTestCase):
     def setUp(self):
         super(_VirtDriverTestCase, self).setUp()
+
+        self.flags(instances_path=self.useFixture(fixtures.TempDir()).path)
         self.connection = importutils.import_object(self.driver_module,
                                                     fake.FakeVirtAPI())
         self.ctxt = test_utils.get_test_admin_context()
         self.image_service = fake_image.FakeImageService()
 
+    def tearDown(self):
+        super(_VirtDriverTestCase, self).tearDown()
+
     def _get_running_instance(self):
         instance_ref = test_utils.get_test_instance()
         network_info = test_utils.get_test_network_info()
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 386fe836cb5b..dd07be83cbf6 100755
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -1977,8 +1977,7 @@ class LibvirtDriver(driver.ComputeDriver):
         xml = conf.to_xml()
 
         if write_to_disk:
-            instance_dir = os.path.join(CONF.instances_path,
-                                        instance["name"])
+            instance_dir = libvirt_utils.get_instance_path(instance)
             xml_path = os.path.join(instance_dir, 'libvirt.xml')
             libvirt_utils.write_to_file(xml_path, xml)
 
diff --git a/nova/virt/libvirt/imagecache.py b/nova/virt/libvirt/imagecache.py
index ea7bded959a6..adc7691c651a 100644
--- a/nova/virt/libvirt/imagecache.py
+++ b/nova/virt/libvirt/imagecache.py
@@ -220,7 +220,7 @@ class ImageCacheManager(object):
 
         self.used_images = {}
         self.image_popularity = {}
-        self.instance_names = {}
+        self.instance_names = set()
 
         self.active_base_files = []
         self.corrupt_base_files = []
@@ -263,7 +263,11 @@ class ImageCacheManager(object):
         self.instance_names = set()
 
         for instance in all_instances:
+            # NOTE(mikal): "instance name" here means "the name of a directory
+            # which might contain an instance" and therefore needs to include
+            # historical permutations as well as the current one.
             self.instance_names.add(instance['name'])
+            self.instance_names.add(instance['uuid'])
 
             resize_states = [task_states.RESIZE_PREP,
                              task_states.RESIZE_MIGRATING,
@@ -272,6 +276,7 @@ class ImageCacheManager(object):
             if instance['task_state'] in resize_states or \
                 instance['vm_state'] == vm_states.RESIZED:
                 self.instance_names.add(instance['name'] + '_resize')
+                self.instance_names.add(instance['uuid'] + '_resize')
 
             image_ref_str = str(instance['image_ref'])
             local, remote, insts = self.used_images.get(image_ref_str,
diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py
index b8e0cafecb33..4f2b55670d41 100755
--- a/nova/virt/libvirt/utils.py
+++ b/nova/virt/libvirt/utils.py
@@ -518,17 +518,19 @@ def fetch_image(context, target, image_id, user_id, project_id):
     images.fetch_to_raw(context, image_id, target, user_id, project_id)
 
 
-def get_instance_path(instance):
+def get_instance_path(instance, forceold=False):
     """Determine the correct path for instance storage.
 
-    This used to be calculated all over the place. This method centralizes
-    this into one location, which will make it easier to change the
-    algorithm used to name instance storage directories.
+    This method determines the directory name for instance storage, while
+    handling the fact that we changed the naming style to something more
+    unique in the grizzly release.
 
     :param instance: the instance we want a path for
+    :param forceold: force the use of the pre-grizzly format
 
     :returns: a path to store information about that instance
     """
-    # TODO(mikal): we should use UUID instead of name, as name isn't
-    # nessesarily unique
-    return os.path.join(CONF.instances_path, instance['name'])
+    pre_grizzly_name = os.path.join(CONF.instances_path, instance['name'])
+    if forceold or os.path.exists(pre_grizzly_name):
+        return pre_grizzly_name
+    return os.path.join(CONF.instances_path, instance['uuid'])